2011-12-21 10:47:07 +04:00
/*
* drivers / pwm / pwm - tegra . c
*
* Tegra pulse - width - modulation controller driver
*
* Copyright ( c ) 2010 , NVIDIA Corporation .
* Based on arch / arm / plat - mxc / pwm . c by Sascha Hauer < s . hauer @ pengutronix . de >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful , but WITHOUT
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
*/
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of.h>
2016-06-22 14:47:23 +03:00
# include <linux/of_device.h>
2011-12-21 10:47:07 +04:00
# include <linux/pwm.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
2016-06-22 14:47:19 +03:00
# include <linux/reset.h>
2011-12-21 10:47:07 +04:00
# define PWM_ENABLE (1 << 31)
# define PWM_DUTY_WIDTH 8
# define PWM_DUTY_SHIFT 16
# define PWM_SCALE_WIDTH 13
# define PWM_SCALE_SHIFT 0
2016-06-22 14:47:23 +03:00
struct tegra_pwm_soc {
unsigned int num_channels ;
} ;
2011-12-21 10:47:07 +04:00
struct tegra_pwm_chip {
2016-07-11 12:26:52 +03:00
struct pwm_chip chip ;
struct device * dev ;
2011-12-21 10:47:07 +04:00
2016-07-11 12:26:52 +03:00
struct clk * clk ;
2016-06-22 14:47:19 +03:00
struct reset_control * rst ;
2011-12-21 10:47:07 +04:00
2016-07-11 12:27:29 +03:00
void __iomem * regs ;
2016-06-22 14:47:23 +03:00
const struct tegra_pwm_soc * soc ;
2011-12-21 10:47:07 +04:00
} ;
static inline struct tegra_pwm_chip * to_tegra_pwm_chip ( struct pwm_chip * chip )
{
return container_of ( chip , struct tegra_pwm_chip , chip ) ;
}
static inline u32 pwm_readl ( struct tegra_pwm_chip * chip , unsigned int num )
{
2016-07-11 12:27:29 +03:00
return readl ( chip - > regs + ( num < < 4 ) ) ;
2011-12-21 10:47:07 +04:00
}
static inline void pwm_writel ( struct tegra_pwm_chip * chip , unsigned int num ,
unsigned long val )
{
2016-07-11 12:27:29 +03:00
writel ( val , chip - > regs + ( num < < 4 ) ) ;
2011-12-21 10:47:07 +04:00
}
static int tegra_pwm_config ( struct pwm_chip * chip , struct pwm_device * pwm ,
int duty_ns , int period_ns )
{
struct tegra_pwm_chip * pc = to_tegra_pwm_chip ( chip ) ;
2016-06-22 14:47:21 +03:00
unsigned long long c = duty_ns ;
2011-12-21 10:47:07 +04:00
unsigned long rate , hz ;
2017-04-07 12:34:00 +03:00
unsigned long long ns100 = NSEC_PER_SEC ;
2011-12-21 10:47:07 +04:00
u32 val = 0 ;
int err ;
/*
* Convert from duty_ns / period_ns to a fixed number of duty ticks
* per ( 1 < < PWM_DUTY_WIDTH ) cycles and make sure to round to the
* nearest integer during division .
*/
2016-06-22 14:47:21 +03:00
c * = ( 1 < < PWM_DUTY_WIDTH ) ;
2017-04-07 12:33:59 +03:00
c = DIV_ROUND_CLOSEST_ULL ( c , period_ns ) ;
2011-12-21 10:47:07 +04:00
val = ( u32 ) c < < PWM_DUTY_SHIFT ;
/*
* Compute the prescaler value for which ( 1 < < PWM_DUTY_WIDTH )
* cycles at the PWM clock rate will take period_ns nanoseconds .
*/
rate = clk_get_rate ( pc - > clk ) > > PWM_DUTY_WIDTH ;
2017-04-07 12:34:00 +03:00
/* Consider precision in PWM_SCALE_WIDTH rate calculation */
ns100 * = 100 ;
hz = DIV_ROUND_CLOSEST_ULL ( ns100 , period_ns ) ;
rate = DIV_ROUND_CLOSEST ( rate * 100 , hz ) ;
2011-12-21 10:47:07 +04:00
/*
* Since the actual PWM divider is the register ' s frequency divider
* field minus 1 , we need to decrement to get the correct value to
* write to the register .
*/
if ( rate > 0 )
rate - - ;
/*
* Make sure that the rate will fit in the register ' s frequency
* divider field .
*/
if ( rate > > PWM_SCALE_WIDTH )
return - EINVAL ;
val | = rate < < PWM_SCALE_SHIFT ;
/*
* If the PWM channel is disabled , make sure to turn on the clock
* before writing the register . Otherwise , keep it enabled .
*/
2015-07-01 11:21:47 +03:00
if ( ! pwm_is_enabled ( pwm ) ) {
2011-12-21 10:47:07 +04:00
err = clk_prepare_enable ( pc - > clk ) ;
if ( err < 0 )
return err ;
} else
val | = PWM_ENABLE ;
pwm_writel ( pc , pwm - > hwpwm , val ) ;
/*
* If the PWM is not enabled , turn the clock off again to save power .
*/
2015-07-01 11:21:47 +03:00
if ( ! pwm_is_enabled ( pwm ) )
2011-12-21 10:47:07 +04:00
clk_disable_unprepare ( pc - > clk ) ;
return 0 ;
}
static int tegra_pwm_enable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct tegra_pwm_chip * pc = to_tegra_pwm_chip ( chip ) ;
int rc = 0 ;
u32 val ;
rc = clk_prepare_enable ( pc - > clk ) ;
if ( rc < 0 )
return rc ;
val = pwm_readl ( pc , pwm - > hwpwm ) ;
val | = PWM_ENABLE ;
pwm_writel ( pc , pwm - > hwpwm , val ) ;
return 0 ;
}
static void tegra_pwm_disable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct tegra_pwm_chip * pc = to_tegra_pwm_chip ( chip ) ;
u32 val ;
val = pwm_readl ( pc , pwm - > hwpwm ) ;
val & = ~ PWM_ENABLE ;
pwm_writel ( pc , pwm - > hwpwm , val ) ;
clk_disable_unprepare ( pc - > clk ) ;
}
static const struct pwm_ops tegra_pwm_ops = {
. config = tegra_pwm_config ,
. enable = tegra_pwm_enable ,
. disable = tegra_pwm_disable ,
. owner = THIS_MODULE ,
} ;
static int tegra_pwm_probe ( struct platform_device * pdev )
{
struct tegra_pwm_chip * pwm ;
struct resource * r ;
int ret ;
pwm = devm_kzalloc ( & pdev - > dev , sizeof ( * pwm ) , GFP_KERNEL ) ;
2014-04-23 13:41:10 +04:00
if ( ! pwm )
2011-12-21 10:47:07 +04:00
return - ENOMEM ;
2016-06-22 14:47:23 +03:00
pwm - > soc = of_device_get_match_data ( & pdev - > dev ) ;
2011-12-21 10:47:07 +04:00
pwm - > dev = & pdev - > dev ;
r = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2016-07-11 12:27:29 +03:00
pwm - > regs = devm_ioremap_resource ( & pdev - > dev , r ) ;
if ( IS_ERR ( pwm - > regs ) )
return PTR_ERR ( pwm - > regs ) ;
2011-12-21 10:47:07 +04:00
platform_set_drvdata ( pdev , pwm ) ;
2012-07-01 09:00:51 +04:00
pwm - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
2011-12-21 10:47:07 +04:00
if ( IS_ERR ( pwm - > clk ) )
return PTR_ERR ( pwm - > clk ) ;
2016-06-22 14:47:19 +03:00
pwm - > rst = devm_reset_control_get ( & pdev - > dev , " pwm " ) ;
if ( IS_ERR ( pwm - > rst ) ) {
ret = PTR_ERR ( pwm - > rst ) ;
dev_err ( & pdev - > dev , " Reset control is not found: %d \n " , ret ) ;
return ret ;
}
reset_control_deassert ( pwm - > rst ) ;
2011-12-21 10:47:07 +04:00
pwm - > chip . dev = & pdev - > dev ;
pwm - > chip . ops = & tegra_pwm_ops ;
pwm - > chip . base = - 1 ;
2016-06-22 14:47:23 +03:00
pwm - > chip . npwm = pwm - > soc - > num_channels ;
2011-12-21 10:47:07 +04:00
ret = pwmchip_add ( & pwm - > chip ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " pwmchip_add() failed: %d \n " , ret ) ;
2016-06-22 14:47:19 +03:00
reset_control_assert ( pwm - > rst ) ;
2011-12-21 10:47:07 +04:00
return ret ;
}
return 0 ;
}
2012-11-19 22:26:09 +04:00
static int tegra_pwm_remove ( struct platform_device * pdev )
2011-12-21 10:47:07 +04:00
{
struct tegra_pwm_chip * pc = platform_get_drvdata ( pdev ) ;
2016-07-11 12:08:29 +03:00
unsigned int i ;
2016-06-22 14:47:19 +03:00
int err ;
2011-12-21 10:47:07 +04:00
if ( WARN_ON ( ! pc ) )
return - ENODEV ;
2016-06-22 14:47:19 +03:00
err = clk_prepare_enable ( pc - > clk ) ;
if ( err < 0 )
return err ;
2016-07-11 12:08:29 +03:00
for ( i = 0 ; i < pc - > chip . npwm ; i + + ) {
2011-12-21 10:47:07 +04:00
struct pwm_device * pwm = & pc - > chip . pwms [ i ] ;
2015-07-01 11:21:47 +03:00
if ( ! pwm_is_enabled ( pwm ) )
2011-12-21 10:47:07 +04:00
if ( clk_prepare_enable ( pc - > clk ) < 0 )
continue ;
pwm_writel ( pc , i , 0 ) ;
clk_disable_unprepare ( pc - > clk ) ;
}
2016-06-22 14:47:19 +03:00
reset_control_assert ( pc - > rst ) ;
clk_disable_unprepare ( pc - > clk ) ;
2012-07-01 09:00:51 +04:00
return pwmchip_remove ( & pc - > chip ) ;
2011-12-21 10:47:07 +04:00
}
2016-06-22 14:47:23 +03:00
static const struct tegra_pwm_soc tegra20_pwm_soc = {
. num_channels = 4 ,
} ;
static const struct tegra_pwm_soc tegra186_pwm_soc = {
. num_channels = 1 ,
} ;
2013-04-18 12:04:14 +04:00
static const struct of_device_id tegra_pwm_of_match [ ] = {
2016-06-22 14:47:23 +03:00
{ . compatible = " nvidia,tegra20-pwm " , . data = & tegra20_pwm_soc } ,
{ . compatible = " nvidia,tegra186-pwm " , . data = & tegra186_pwm_soc } ,
2011-12-21 11:04:13 +04:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , tegra_pwm_of_match ) ;
2011-12-21 10:47:07 +04:00
static struct platform_driver tegra_pwm_driver = {
. driver = {
. name = " tegra-pwm " ,
2013-02-16 02:02:22 +04:00
. of_match_table = tegra_pwm_of_match ,
2011-12-21 10:47:07 +04:00
} ,
. probe = tegra_pwm_probe ,
2012-11-19 22:21:28 +04:00
. remove = tegra_pwm_remove ,
2011-12-21 10:47:07 +04:00
} ;
module_platform_driver ( tegra_pwm_driver ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " NVIDIA Corporation " ) ;
MODULE_ALIAS ( " platform:tegra-pwm " ) ;