2012-07-10 18:38:10 +04:00
/*
* Copyright 2012 Alexandre Pereira da Silva < aletes . xgr @ gmail . com >
*
* 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 ; version 2.
*
*/
# 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/of_address.h>
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/slab.h>
struct lpc32xx_pwm_chip {
struct pwm_chip chip ;
struct clk * clk ;
void __iomem * base ;
} ;
# define PWM_ENABLE (1 << 31)
# define PWM_RELOADV(x) (((x) & 0xFF) << 8)
# define PWM_DUTY(x) ((x) & 0xFF)
# define to_lpc32xx_pwm_chip(_chip) \
container_of ( _chip , struct lpc32xx_pwm_chip , chip )
static int lpc32xx_pwm_config ( struct pwm_chip * chip , struct pwm_device * pwm ,
int duty_ns , int period_ns )
{
struct lpc32xx_pwm_chip * lpc32xx = to_lpc32xx_pwm_chip ( chip ) ;
unsigned long long c ;
int period_cycles , duty_cycles ;
2013-04-23 10:02:49 +04:00
u32 val ;
2012-07-10 18:38:10 +04:00
c = clk_get_rate ( lpc32xx - > clk ) / 256 ;
c = c * period_ns ;
do_div ( c , NSEC_PER_SEC ) ;
/* Handle high and low extremes */
if ( c = = 0 )
c = 1 ;
if ( c > 255 )
c = 0 ; /* 0 set division by 256 */
period_cycles = c ;
2012-11-14 15:58:13 +04:00
/* The duty-cycle value is as follows:
*
* DUTY - CYCLE HIGH LEVEL
* 1 99.9 %
* 25 90.0 %
* 128 50.0 %
* 220 10.0 %
* 255 0.1 %
* 0 0.0 %
*
* In other words , the register value is duty - cycle % 256 with
* duty - cycle in the range 1 - 256.
*/
2012-07-10 18:38:10 +04:00
c = 256 * duty_ns ;
do_div ( c , period_ns ) ;
2012-11-14 15:58:13 +04:00
if ( c > 255 )
c = 255 ;
duty_cycles = 256 - c ;
2012-07-10 18:38:10 +04:00
2013-04-23 10:02:49 +04:00
val = readl ( lpc32xx - > base + ( pwm - > hwpwm < < 2 ) ) ;
val & = ~ 0xFFFF ;
val | = PWM_RELOADV ( period_cycles ) | PWM_DUTY ( duty_cycles ) ;
writel ( val , lpc32xx - > base + ( pwm - > hwpwm < < 2 ) ) ;
2012-07-10 18:38:10 +04:00
return 0 ;
}
static int lpc32xx_pwm_enable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct lpc32xx_pwm_chip * lpc32xx = to_lpc32xx_pwm_chip ( chip ) ;
2013-04-23 10:01:31 +04:00
u32 val ;
int ret ;
2015-12-06 14:32:00 +03:00
ret = clk_prepare_enable ( lpc32xx - > clk ) ;
2013-04-23 10:01:31 +04:00
if ( ret )
return ret ;
val = readl ( lpc32xx - > base + ( pwm - > hwpwm < < 2 ) ) ;
val | = PWM_ENABLE ;
writel ( val , lpc32xx - > base + ( pwm - > hwpwm < < 2 ) ) ;
2012-07-10 18:38:10 +04:00
2013-04-23 10:01:31 +04:00
return 0 ;
2012-07-10 18:38:10 +04:00
}
static void lpc32xx_pwm_disable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct lpc32xx_pwm_chip * lpc32xx = to_lpc32xx_pwm_chip ( chip ) ;
2013-04-23 10:01:31 +04:00
u32 val ;
val = readl ( lpc32xx - > base + ( pwm - > hwpwm < < 2 ) ) ;
val & = ~ PWM_ENABLE ;
writel ( val , lpc32xx - > base + ( pwm - > hwpwm < < 2 ) ) ;
2012-07-10 18:38:10 +04:00
2015-12-06 14:32:00 +03:00
clk_disable_unprepare ( lpc32xx - > clk ) ;
2012-07-10 18:38:10 +04:00
}
static const struct pwm_ops lpc32xx_pwm_ops = {
. config = lpc32xx_pwm_config ,
. enable = lpc32xx_pwm_enable ,
. disable = lpc32xx_pwm_disable ,
. owner = THIS_MODULE ,
} ;
static int lpc32xx_pwm_probe ( struct platform_device * pdev )
{
struct lpc32xx_pwm_chip * lpc32xx ;
struct resource * res ;
int ret ;
lpc32xx = devm_kzalloc ( & pdev - > dev , sizeof ( * lpc32xx ) , GFP_KERNEL ) ;
if ( ! lpc32xx )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2013-01-21 14:09:16 +04:00
lpc32xx - > base = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( lpc32xx - > base ) )
return PTR_ERR ( lpc32xx - > base ) ;
2012-07-10 18:38:10 +04:00
lpc32xx - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( lpc32xx - > clk ) )
return PTR_ERR ( lpc32xx - > clk ) ;
lpc32xx - > chip . dev = & pdev - > dev ;
lpc32xx - > chip . ops = & lpc32xx_pwm_ops ;
2015-12-06 14:31:59 +03:00
lpc32xx - > chip . npwm = 1 ;
2012-11-14 15:58:15 +04:00
lpc32xx - > chip . base = - 1 ;
2012-07-10 18:38:10 +04:00
ret = pwmchip_add ( & lpc32xx - > chip ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to add PWM chip, error %d \n " , ret ) ;
return ret ;
}
platform_set_drvdata ( pdev , lpc32xx ) ;
return 0 ;
}
2012-11-19 22:26:09 +04:00
static int lpc32xx_pwm_remove ( struct platform_device * pdev )
2012-07-10 18:38:10 +04:00
{
struct lpc32xx_pwm_chip * lpc32xx = platform_get_drvdata ( pdev ) ;
2012-11-14 15:58:14 +04:00
unsigned int i ;
for ( i = 0 ; i < lpc32xx - > chip . npwm ; i + + )
pwm_disable ( & lpc32xx - > chip . pwms [ i ] ) ;
2012-07-10 18:38:10 +04:00
return pwmchip_remove ( & lpc32xx - > chip ) ;
}
2013-04-18 12:04:14 +04:00
static const struct of_device_id lpc32xx_pwm_dt_ids [ ] = {
2012-07-10 18:38:10 +04:00
{ . compatible = " nxp,lpc3220-pwm " , } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , lpc32xx_pwm_dt_ids ) ;
static struct platform_driver lpc32xx_pwm_driver = {
. driver = {
. name = " lpc32xx-pwm " ,
2013-09-30 07:26:40 +04:00
. of_match_table = lpc32xx_pwm_dt_ids ,
2012-07-10 18:38:10 +04:00
} ,
. probe = lpc32xx_pwm_probe ,
2012-11-19 22:21:28 +04:00
. remove = lpc32xx_pwm_remove ,
2012-07-10 18:38:10 +04:00
} ;
module_platform_driver ( lpc32xx_pwm_driver ) ;
MODULE_ALIAS ( " platform:lpc32xx-pwm " ) ;
MODULE_AUTHOR ( " Alexandre Pereira da Silva <aletes.xgr@gmail.com> " ) ;
MODULE_DESCRIPTION ( " LPC32XX PWM Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;