2018-07-09 20:55:58 +03:00
// SPDX-License-Identifier: GPL-2.0+
2012-04-04 06:50:52 +04:00
/*
* Copyright 2012 Freescale Semiconductor , Inc .
*/
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/slab.h>
2012-06-26 12:58:09 +04:00
# include <linux/stmp_device.h>
2012-04-04 06:50:52 +04:00
# define SET 0x4
# define CLR 0x8
# define TOG 0xc
# define PWM_CTRL 0x0
# define PWM_ACTIVE0 0x10
# define PWM_PERIOD0 0x20
# define PERIOD_PERIOD(p) ((p) & 0xffff)
# define PERIOD_PERIOD_MAX 0x10000
# define PERIOD_ACTIVE_HIGH (3 << 16)
2019-10-04 16:32:04 +03:00
# define PERIOD_ACTIVE_LOW (2 << 16)
# define PERIOD_INACTIVE_HIGH (3 << 18)
2012-04-04 06:50:52 +04:00
# define PERIOD_INACTIVE_LOW (2 << 18)
2019-10-04 16:32:02 +03:00
# define PERIOD_POLARITY_NORMAL (PERIOD_ACTIVE_HIGH | PERIOD_INACTIVE_LOW)
2019-10-04 16:32:04 +03:00
# define PERIOD_POLARITY_INVERSE (PERIOD_ACTIVE_LOW | PERIOD_INACTIVE_HIGH)
2012-04-04 06:50:52 +04:00
# define PERIOD_CDIV(div) (((div) & 0x7) << 20)
# define PERIOD_CDIV_MAX 8
2019-10-04 16:32:06 +03:00
static const u8 cdiv_shift [ PERIOD_CDIV_MAX ] = {
0 , 1 , 2 , 3 , 4 , 6 , 8 , 10
2015-03-11 15:08:12 +03:00
} ;
2012-04-04 06:50:52 +04:00
struct mxs_pwm_chip {
struct pwm_chip chip ;
struct clk * clk ;
void __iomem * base ;
} ;
# define to_mxs_pwm_chip(_chip) container_of(_chip, struct mxs_pwm_chip, chip)
2019-10-04 16:32:02 +03:00
static int mxs_pwm_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
const struct pwm_state * state )
{
struct mxs_pwm_chip * mxs = to_mxs_pwm_chip ( chip ) ;
int ret , div = 0 ;
unsigned int period_cycles , duty_cycles ;
unsigned long rate ;
unsigned long long c ;
2019-10-04 16:32:04 +03:00
unsigned int pol_bits ;
2019-10-04 16:32:02 +03:00
/*
* If the PWM channel is disabled , make sure to turn on the
* clock before calling clk_get_rate ( ) and writing to the
* registers . Otherwise , just keep it enabled .
*/
if ( ! pwm_is_enabled ( pwm ) ) {
ret = clk_prepare_enable ( mxs - > clk ) ;
if ( ret )
return ret ;
}
if ( ! state - > enabled & & pwm_is_enabled ( pwm ) )
writel ( 1 < < pwm - > hwpwm , mxs - > base + PWM_CTRL + CLR ) ;
rate = clk_get_rate ( mxs - > clk ) ;
while ( 1 ) {
2019-10-04 16:32:06 +03:00
c = rate > > cdiv_shift [ div ] ;
2019-10-04 16:32:02 +03:00
c = c * state - > period ;
do_div ( c , 1000000000 ) ;
if ( c < PERIOD_PERIOD_MAX )
break ;
div + + ;
if ( div > = PERIOD_CDIV_MAX )
return - EINVAL ;
}
period_cycles = c ;
c * = state - > duty_cycle ;
do_div ( c , state - > period ) ;
duty_cycles = c ;
/*
* The data sheet the says registers must be written to in
* this order ( ACTIVEn , then PERIODn ) . Also , the new settings
* only take effect at the beginning of a new period , avoiding
* glitches .
*/
2019-10-04 16:32:04 +03:00
pol_bits = state - > polarity = = PWM_POLARITY_NORMAL ?
PERIOD_POLARITY_NORMAL : PERIOD_POLARITY_INVERSE ;
2019-10-04 16:32:02 +03:00
writel ( duty_cycles < < 16 ,
mxs - > base + PWM_ACTIVE0 + pwm - > hwpwm * 0x20 ) ;
2019-10-04 16:32:04 +03:00
writel ( PERIOD_PERIOD ( period_cycles ) | pol_bits | PERIOD_CDIV ( div ) ,
2019-10-04 16:32:02 +03:00
mxs - > base + PWM_PERIOD0 + pwm - > hwpwm * 0x20 ) ;
if ( state - > enabled ) {
if ( ! pwm_is_enabled ( pwm ) ) {
/*
* The clock was enabled above . Just enable
* the channel in the control register .
*/
writel ( 1 < < pwm - > hwpwm , mxs - > base + PWM_CTRL + SET ) ;
}
} else {
clk_disable_unprepare ( mxs - > clk ) ;
}
return 0 ;
}
2012-04-04 06:50:52 +04:00
static const struct pwm_ops mxs_pwm_ops = {
2019-10-04 16:32:02 +03:00
. apply = mxs_pwm_apply ,
2012-04-04 06:50:52 +04:00
. owner = THIS_MODULE ,
} ;
static int mxs_pwm_probe ( struct platform_device * pdev )
{
struct device_node * np = pdev - > dev . of_node ;
struct mxs_pwm_chip * mxs ;
int ret ;
mxs = devm_kzalloc ( & pdev - > dev , sizeof ( * mxs ) , GFP_KERNEL ) ;
if ( ! mxs )
return - ENOMEM ;
2019-07-18 04:32:05 +03:00
mxs - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2013-01-21 14:09:16 +04:00
if ( IS_ERR ( mxs - > base ) )
return PTR_ERR ( mxs - > base ) ;
2012-04-04 06:50:52 +04:00
2012-06-26 12:58:10 +04:00
mxs - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( mxs - > clk ) )
return PTR_ERR ( mxs - > clk ) ;
2012-04-04 06:50:52 +04:00
mxs - > chip . dev = & pdev - > dev ;
mxs - > chip . ops = & mxs_pwm_ops ;
2019-10-04 16:32:04 +03:00
mxs - > chip . of_xlate = of_pwm_xlate_with_flags ;
mxs - > chip . of_pwm_n_cells = 3 ;
2012-04-04 06:50:52 +04:00
mxs - > chip . base = - 1 ;
2017-01-04 11:40:54 +03:00
2012-04-04 06:50:52 +04:00
ret = of_property_read_u32 ( np , " fsl,pwm-number " , & mxs - > chip . npwm ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to get pwm number: %d \n " , ret ) ;
2012-06-26 12:58:10 +04:00
return ret ;
2012-04-04 06:50:52 +04:00
}
ret = pwmchip_add ( & mxs - > chip ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to add pwm chip %d \n " , ret ) ;
2012-06-26 12:58:10 +04:00
return ret ;
2012-04-04 06:50:52 +04:00
}
platform_set_drvdata ( pdev , mxs ) ;
2013-07-10 06:25:37 +04:00
ret = stmp_reset_block ( mxs - > base ) ;
if ( ret )
goto pwm_remove ;
2012-04-04 06:50:52 +04:00
return 0 ;
2013-07-10 06:25:37 +04:00
pwm_remove :
pwmchip_remove ( & mxs - > chip ) ;
return ret ;
2012-04-04 06:50:52 +04:00
}
2012-11-19 22:26:09 +04:00
static int mxs_pwm_remove ( struct platform_device * pdev )
2012-04-04 06:50:52 +04:00
{
struct mxs_pwm_chip * mxs = platform_get_drvdata ( pdev ) ;
2012-07-01 08:58:00 +04:00
return pwmchip_remove ( & mxs - > chip ) ;
2012-04-04 06:50:52 +04:00
}
2013-04-18 12:04:14 +04:00
static const struct of_device_id mxs_pwm_dt_ids [ ] = {
2012-06-26 12:58:08 +04:00
{ . compatible = " fsl,imx23-pwm " , } ,
2012-04-04 06:50:52 +04:00
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , mxs_pwm_dt_ids ) ;
static struct platform_driver mxs_pwm_driver = {
. driver = {
. name = " mxs-pwm " ,
2013-09-30 07:26:39 +04:00
. of_match_table = mxs_pwm_dt_ids ,
2012-04-04 06:50:52 +04:00
} ,
. probe = mxs_pwm_probe ,
2012-11-19 22:21:28 +04:00
. remove = mxs_pwm_remove ,
2012-04-04 06:50:52 +04:00
} ;
module_platform_driver ( mxs_pwm_driver ) ;
MODULE_ALIAS ( " platform:mxs-pwm " ) ;
MODULE_AUTHOR ( " Shawn Guo <shawn.guo@linaro.org> " ) ;
MODULE_DESCRIPTION ( " Freescale MXS PWM Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;