2010-12-23 15:11:21 +03:00
/*
2012-07-01 04:29:28 +04:00
* drivers / pwm / pwm - vt8500 . c
2010-12-23 15:11:21 +03:00
*
* Copyright ( C ) 2010 Alexey Charkov < alchark @ gmail . com >
*
* This software is licensed under the terms of the GNU General Public
* License version 2 , as published by the Free Software Foundation , and
* may be copied , distributed , and modified under those terms .
*
* 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 .
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/pwm.h>
# include <linux/delay.h>
# include <asm/div64.h>
# define VT8500_NR_PWMS 4
2012-03-15 13:04:37 +04:00
struct vt8500_chip {
struct pwm_chip chip ;
void __iomem * base ;
2010-12-23 15:11:21 +03:00
} ;
2012-03-15 13:04:37 +04:00
# define to_vt8500_chip(chip) container_of(chip, struct vt8500_chip, chip)
2010-12-23 15:11:21 +03:00
# define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
static inline void pwm_busy_wait ( void __iomem * reg , u8 bitmask )
{
int loops = msecs_to_loops ( 10 ) ;
while ( ( readb ( reg ) & bitmask ) & & - - loops )
cpu_relax ( ) ;
if ( unlikely ( ! loops ) )
2012-08-10 08:42:09 +04:00
pr_warn ( " Waiting for status bits 0x%x to clear timed out \n " ,
2010-12-23 15:11:21 +03:00
bitmask ) ;
}
2012-03-15 13:04:37 +04:00
static int vt8500_pwm_config ( struct pwm_chip * chip , struct pwm_device * pwm ,
int duty_ns , int period_ns )
2010-12-23 15:11:21 +03:00
{
2012-03-15 13:04:37 +04:00
struct vt8500_chip * vt8500 = to_vt8500_chip ( chip ) ;
2010-12-23 15:11:21 +03:00
unsigned long long c ;
unsigned long period_cycles , prescale , pv , dc ;
c = 25000000 / 2 ; /* wild guess --- need to implement clocks */
c = c * period_ns ;
do_div ( c , 1000000000 ) ;
period_cycles = c ;
if ( period_cycles < 1 )
period_cycles = 1 ;
prescale = ( period_cycles - 1 ) / 4096 ;
pv = period_cycles / ( prescale + 1 ) - 1 ;
if ( pv > 4095 )
pv = 4095 ;
if ( prescale > 1023 )
return - EINVAL ;
c = ( unsigned long long ) pv * duty_ns ;
do_div ( c , period_ns ) ;
dc = c ;
2012-03-15 13:04:37 +04:00
pwm_busy_wait ( vt8500 - > base + 0x40 + pwm - > hwpwm , ( 1 < < 1 ) ) ;
writel ( prescale , vt8500 - > base + 0x4 + ( pwm - > hwpwm < < 4 ) ) ;
2010-12-23 15:11:21 +03:00
2012-03-15 13:04:37 +04:00
pwm_busy_wait ( vt8500 - > base + 0x40 + pwm - > hwpwm , ( 1 < < 2 ) ) ;
writel ( pv , vt8500 - > base + 0x8 + ( pwm - > hwpwm < < 4 ) ) ;
2010-12-23 15:11:21 +03:00
2012-03-15 13:04:37 +04:00
pwm_busy_wait ( vt8500 - > base + 0x40 + pwm - > hwpwm , ( 1 < < 3 ) ) ;
writel ( dc , vt8500 - > base + 0xc + ( pwm - > hwpwm < < 4 ) ) ;
2010-12-23 15:11:21 +03:00
return 0 ;
}
2012-03-15 13:04:37 +04:00
static int vt8500_pwm_enable ( struct pwm_chip * chip , struct pwm_device * pwm )
2010-12-23 15:11:21 +03:00
{
2012-03-15 13:04:37 +04:00
struct vt8500_chip * vt8500 = to_vt8500_chip ( chip ) ;
2010-12-23 15:11:21 +03:00
2012-03-15 13:04:37 +04:00
pwm_busy_wait ( vt8500 - > base + 0x40 + pwm - > hwpwm , ( 1 < < 0 ) ) ;
writel ( 5 , vt8500 - > base + ( pwm - > hwpwm < < 4 ) ) ;
return 0 ;
2010-12-23 15:11:21 +03:00
}
2012-03-15 13:04:37 +04:00
static void vt8500_pwm_disable ( struct pwm_chip * chip , struct pwm_device * pwm )
2010-12-23 15:11:21 +03:00
{
2012-03-15 13:04:37 +04:00
struct vt8500_chip * vt8500 = to_vt8500_chip ( chip ) ;
2010-12-23 15:11:21 +03:00
2012-03-15 13:04:37 +04:00
pwm_busy_wait ( vt8500 - > base + 0x40 + pwm - > hwpwm , ( 1 < < 0 ) ) ;
writel ( 0 , vt8500 - > base + ( pwm - > hwpwm < < 4 ) ) ;
2010-12-23 15:11:21 +03:00
}
2012-03-15 13:04:37 +04:00
static struct pwm_ops vt8500_pwm_ops = {
. enable = vt8500_pwm_enable ,
. disable = vt8500_pwm_disable ,
. config = vt8500_pwm_config ,
. owner = THIS_MODULE ,
} ;
2010-12-23 15:11:21 +03:00
static int __devinit pwm_probe ( struct platform_device * pdev )
{
2012-03-15 13:04:37 +04:00
struct vt8500_chip * chip ;
2010-12-23 15:11:21 +03:00
struct resource * r ;
2012-03-15 13:04:37 +04:00
int ret ;
2010-12-23 15:11:21 +03:00
2012-07-01 04:29:28 +04:00
chip = devm_kzalloc ( & pdev - > dev , sizeof ( * chip ) , GFP_KERNEL ) ;
2012-03-15 13:04:37 +04:00
if ( chip = = NULL ) {
2010-12-23 15:11:21 +03:00
dev_err ( & pdev - > dev , " failed to allocate memory \n " ) ;
return - ENOMEM ;
}
2012-03-15 13:04:37 +04:00
chip - > chip . dev = & pdev - > dev ;
chip - > chip . ops = & vt8500_pwm_ops ;
chip - > chip . base = - 1 ;
chip - > chip . npwm = VT8500_NR_PWMS ;
2010-12-23 15:11:21 +03:00
r = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( r = = NULL ) {
dev_err ( & pdev - > dev , " no memory resource defined \n " ) ;
2012-07-01 04:29:28 +04:00
return - ENODEV ;
2010-12-23 15:11:21 +03:00
}
2012-07-01 04:29:28 +04:00
chip - > base = devm_request_and_ioremap ( & pdev - > dev , r ) ;
if ( chip - > base = = NULL )
return - EADDRNOTAVAIL ;
2010-12-23 15:11:21 +03:00
2012-03-15 13:04:37 +04:00
ret = pwmchip_add ( & chip - > chip ) ;
if ( ret < 0 )
2012-07-01 04:29:28 +04:00
return ret ;
2010-12-23 15:11:21 +03:00
2012-03-15 13:04:37 +04:00
platform_set_drvdata ( pdev , chip ) ;
return ret ;
2010-12-23 15:11:21 +03:00
}
static int __devexit pwm_remove ( struct platform_device * pdev )
{
2012-03-15 13:04:37 +04:00
struct vt8500_chip * chip ;
2010-12-23 15:11:21 +03:00
2012-03-15 13:04:37 +04:00
chip = platform_get_drvdata ( pdev ) ;
if ( chip = = NULL )
2010-12-23 15:11:21 +03:00
return - ENODEV ;
2012-07-01 04:29:28 +04:00
return pwmchip_remove ( & chip - > chip ) ;
2010-12-23 15:11:21 +03:00
}
static struct platform_driver pwm_driver = {
. driver = {
. name = " vt8500-pwm " ,
. owner = THIS_MODULE ,
} ,
. probe = pwm_probe ,
. remove = __devexit_p ( pwm_remove ) ,
} ;
static int __init pwm_init ( void )
{
return platform_driver_register ( & pwm_driver ) ;
}
arch_initcall ( pwm_init ) ;
static void __exit pwm_exit ( void )
{
platform_driver_unregister ( & pwm_driver ) ;
}
module_exit ( pwm_exit ) ;
MODULE_LICENSE ( " GPL " ) ;