2015-10-02 17:59:47 +03:00
/*
* Marvell Berlin PWM driver
*
* Copyright ( C ) 2015 Marvell Technology Group Ltd .
*
* Author : Antoine Tenart < antoine . tenart @ free - electrons . com >
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# include <linux/clk.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/pwm.h>
2015-11-25 12:41:25 +03:00
# include <linux/slab.h>
2015-10-02 17:59:47 +03:00
# define BERLIN_PWM_EN 0x0
# define BERLIN_PWM_ENABLE BIT(0)
# define BERLIN_PWM_CONTROL 0x4
# define BERLIN_PWM_PRESCALE_MASK 0x7
# define BERLIN_PWM_PRESCALE_MAX 4096
# define BERLIN_PWM_INVERT_POLARITY BIT(3)
# define BERLIN_PWM_DUTY 0x8
# define BERLIN_PWM_TCNT 0xc
# define BERLIN_PWM_MAX_TCNT 65535
2015-11-25 12:41:25 +03:00
struct berlin_pwm_channel {
u32 enable ;
u32 ctrl ;
u32 duty ;
u32 tcnt ;
} ;
2015-10-02 17:59:47 +03:00
struct berlin_pwm_chip {
struct pwm_chip chip ;
struct clk * clk ;
void __iomem * base ;
} ;
static inline struct berlin_pwm_chip * to_berlin_pwm_chip ( struct pwm_chip * chip )
{
return container_of ( chip , struct berlin_pwm_chip , chip ) ;
}
static const u32 prescaler_table [ ] = {
1 , 4 , 8 , 16 , 64 , 256 , 1024 , 4096
} ;
static inline u32 berlin_pwm_readl ( struct berlin_pwm_chip * chip ,
unsigned int channel , unsigned long offset )
{
return readl_relaxed ( chip - > base + channel * 0x10 + offset ) ;
}
static inline void berlin_pwm_writel ( struct berlin_pwm_chip * chip ,
unsigned int channel , u32 value ,
unsigned long offset )
{
writel_relaxed ( value , chip - > base + channel * 0x10 + offset ) ;
}
2015-11-25 12:41:25 +03:00
static int berlin_pwm_request ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct berlin_pwm_channel * channel ;
channel = kzalloc ( sizeof ( * channel ) , GFP_KERNEL ) ;
if ( ! channel )
return - ENOMEM ;
return pwm_set_chip_data ( pwm , channel ) ;
}
static void berlin_pwm_free ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct berlin_pwm_channel * channel = pwm_get_chip_data ( pwm ) ;
pwm_set_chip_data ( pwm , NULL ) ;
kfree ( channel ) ;
}
2015-10-02 17:59:47 +03:00
static int berlin_pwm_config ( struct pwm_chip * chip , struct pwm_device * pwm_dev ,
int duty_ns , int period_ns )
{
struct berlin_pwm_chip * pwm = to_berlin_pwm_chip ( chip ) ;
unsigned int prescale ;
u32 value , duty , period ;
u64 cycles , tmp ;
cycles = clk_get_rate ( pwm - > clk ) ;
cycles * = period_ns ;
do_div ( cycles , NSEC_PER_SEC ) ;
for ( prescale = 0 ; prescale < ARRAY_SIZE ( prescaler_table ) ; prescale + + ) {
tmp = cycles ;
do_div ( tmp , prescaler_table [ prescale ] ) ;
if ( tmp < = BERLIN_PWM_MAX_TCNT )
break ;
}
if ( tmp > BERLIN_PWM_MAX_TCNT )
return - ERANGE ;
period = tmp ;
cycles = tmp * duty_ns ;
do_div ( cycles , period_ns ) ;
duty = cycles ;
value = berlin_pwm_readl ( pwm , pwm_dev - > hwpwm , BERLIN_PWM_CONTROL ) ;
value & = ~ BERLIN_PWM_PRESCALE_MASK ;
value | = prescale ;
berlin_pwm_writel ( pwm , pwm_dev - > hwpwm , value , BERLIN_PWM_CONTROL ) ;
berlin_pwm_writel ( pwm , pwm_dev - > hwpwm , duty , BERLIN_PWM_DUTY ) ;
berlin_pwm_writel ( pwm , pwm_dev - > hwpwm , period , BERLIN_PWM_TCNT ) ;
return 0 ;
}
static int berlin_pwm_set_polarity ( struct pwm_chip * chip ,
struct pwm_device * pwm_dev ,
enum pwm_polarity polarity )
{
struct berlin_pwm_chip * pwm = to_berlin_pwm_chip ( chip ) ;
u32 value ;
value = berlin_pwm_readl ( pwm , pwm_dev - > hwpwm , BERLIN_PWM_CONTROL ) ;
if ( polarity = = PWM_POLARITY_NORMAL )
value & = ~ BERLIN_PWM_INVERT_POLARITY ;
else
value | = BERLIN_PWM_INVERT_POLARITY ;
berlin_pwm_writel ( pwm , pwm_dev - > hwpwm , value , BERLIN_PWM_CONTROL ) ;
return 0 ;
}
static int berlin_pwm_enable ( struct pwm_chip * chip , struct pwm_device * pwm_dev )
{
struct berlin_pwm_chip * pwm = to_berlin_pwm_chip ( chip ) ;
u32 value ;
value = berlin_pwm_readl ( pwm , pwm_dev - > hwpwm , BERLIN_PWM_EN ) ;
value | = BERLIN_PWM_ENABLE ;
berlin_pwm_writel ( pwm , pwm_dev - > hwpwm , value , BERLIN_PWM_EN ) ;
return 0 ;
}
static void berlin_pwm_disable ( struct pwm_chip * chip ,
struct pwm_device * pwm_dev )
{
struct berlin_pwm_chip * pwm = to_berlin_pwm_chip ( chip ) ;
u32 value ;
value = berlin_pwm_readl ( pwm , pwm_dev - > hwpwm , BERLIN_PWM_EN ) ;
value & = ~ BERLIN_PWM_ENABLE ;
berlin_pwm_writel ( pwm , pwm_dev - > hwpwm , value , BERLIN_PWM_EN ) ;
}
static const struct pwm_ops berlin_pwm_ops = {
2015-11-25 12:41:25 +03:00
. request = berlin_pwm_request ,
. free = berlin_pwm_free ,
2015-10-02 17:59:47 +03:00
. config = berlin_pwm_config ,
. set_polarity = berlin_pwm_set_polarity ,
. enable = berlin_pwm_enable ,
. disable = berlin_pwm_disable ,
. owner = THIS_MODULE ,
} ;
static const struct of_device_id berlin_pwm_match [ ] = {
{ . compatible = " marvell,berlin-pwm " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , berlin_pwm_match ) ;
static int berlin_pwm_probe ( struct platform_device * pdev )
{
struct berlin_pwm_chip * pwm ;
struct resource * res ;
int ret ;
pwm = devm_kzalloc ( & pdev - > dev , sizeof ( * pwm ) , GFP_KERNEL ) ;
if ( ! pwm )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
pwm - > base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( pwm - > base ) )
return PTR_ERR ( pwm - > base ) ;
pwm - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( pwm - > clk ) )
return PTR_ERR ( pwm - > clk ) ;
ret = clk_prepare_enable ( pwm - > clk ) ;
if ( ret )
return ret ;
pwm - > chip . dev = & pdev - > dev ;
pwm - > chip . ops = & berlin_pwm_ops ;
pwm - > chip . base = - 1 ;
pwm - > chip . npwm = 4 ;
pwm - > chip . can_sleep = true ;
pwm - > chip . of_xlate = of_pwm_xlate_with_flags ;
pwm - > chip . of_pwm_n_cells = 3 ;
ret = pwmchip_add ( & pwm - > chip ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to add PWM chip: %d \n " , ret ) ;
clk_disable_unprepare ( pwm - > clk ) ;
return ret ;
}
platform_set_drvdata ( pdev , pwm ) ;
return 0 ;
}
static int berlin_pwm_remove ( struct platform_device * pdev )
{
struct berlin_pwm_chip * pwm = platform_get_drvdata ( pdev ) ;
int ret ;
ret = pwmchip_remove ( & pwm - > chip ) ;
clk_disable_unprepare ( pwm - > clk ) ;
return ret ;
}
2015-11-25 12:41:25 +03:00
# ifdef CONFIG_PM_SLEEP
static int berlin_pwm_suspend ( struct device * dev )
{
struct berlin_pwm_chip * pwm = dev_get_drvdata ( dev ) ;
unsigned int i ;
for ( i = 0 ; i < pwm - > chip . npwm ; i + + ) {
struct berlin_pwm_channel * channel ;
channel = pwm_get_chip_data ( & pwm - > chip . pwms [ i ] ) ;
if ( ! channel )
continue ;
channel - > enable = berlin_pwm_readl ( pwm , i , BERLIN_PWM_ENABLE ) ;
channel - > ctrl = berlin_pwm_readl ( pwm , i , BERLIN_PWM_CONTROL ) ;
channel - > duty = berlin_pwm_readl ( pwm , i , BERLIN_PWM_DUTY ) ;
channel - > tcnt = berlin_pwm_readl ( pwm , i , BERLIN_PWM_TCNT ) ;
}
clk_disable_unprepare ( pwm - > clk ) ;
return 0 ;
}
static int berlin_pwm_resume ( struct device * dev )
{
struct berlin_pwm_chip * pwm = dev_get_drvdata ( dev ) ;
unsigned int i ;
int ret ;
ret = clk_prepare_enable ( pwm - > clk ) ;
if ( ret )
return ret ;
for ( i = 0 ; i < pwm - > chip . npwm ; i + + ) {
struct berlin_pwm_channel * channel ;
channel = pwm_get_chip_data ( & pwm - > chip . pwms [ i ] ) ;
if ( ! channel )
continue ;
berlin_pwm_writel ( pwm , i , channel - > ctrl , BERLIN_PWM_CONTROL ) ;
berlin_pwm_writel ( pwm , i , channel - > duty , BERLIN_PWM_DUTY ) ;
berlin_pwm_writel ( pwm , i , channel - > tcnt , BERLIN_PWM_TCNT ) ;
berlin_pwm_writel ( pwm , i , channel - > enable , BERLIN_PWM_ENABLE ) ;
}
return 0 ;
}
# endif
static SIMPLE_DEV_PM_OPS ( berlin_pwm_pm_ops , berlin_pwm_suspend ,
berlin_pwm_resume ) ;
2015-10-02 17:59:47 +03:00
static struct platform_driver berlin_pwm_driver = {
. probe = berlin_pwm_probe ,
. remove = berlin_pwm_remove ,
. driver = {
. name = " berlin-pwm " ,
. of_match_table = berlin_pwm_match ,
2015-11-25 12:41:25 +03:00
. pm = & berlin_pwm_pm_ops ,
2015-10-02 17:59:47 +03:00
} ,
} ;
module_platform_driver ( berlin_pwm_driver ) ;
MODULE_AUTHOR ( " Antoine Tenart <antoine.tenart@free-electrons.com> " ) ;
MODULE_DESCRIPTION ( " Marvell Berlin PWM driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;