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
*
2012-10-27 05:49:57 +04:00
* Copyright ( C ) 2012 Tony Prisk < linux @ prisktech . co . nz >
* Copyright ( C ) 2010 Alexey Charkov < alchark @ gmail . com >
2010-12-23 15:11:21 +03:00
*
* 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>
2012-10-27 05:49:57 +04:00
# include <linux/clk.h>
2010-12-23 15:11:21 +03:00
# include <asm/div64.h>
2012-10-27 05:49:57 +04:00
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/of_address.h>
/*
* SoC architecture allocates register space for 4 PWMs but only
* 2 are currently implemented .
*/
# define VT8500_NR_PWMS 2
2010-12-23 15:11:21 +03:00
2012-03-15 13:04:37 +04:00
struct vt8500_chip {
struct pwm_chip chip ;
void __iomem * base ;
2012-10-27 05:49:57 +04:00
struct clk * clk ;
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 ;
2012-10-27 05:49:57 +04:00
c = clk_get_rate ( vt8500 - > clk ) ;
2010-12-23 15:11:21 +03:00
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-10-27 05:49:57 +04:00
int err ;
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-10-27 05:49:57 +04:00
err = clk_enable ( vt8500 - > clk ) ;
2012-11-19 21:44:45 +04:00
if ( err < 0 ) {
2012-10-27 05:49:57 +04:00
dev_err ( chip - > dev , " failed to enable clock \n " ) ;
return err ;
} ;
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 ) ) ;
2012-10-27 05:49:57 +04:00
clk_disable ( vt8500 - > clk ) ;
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
2012-10-27 05:49:57 +04:00
static const struct of_device_id vt8500_pwm_dt_ids [ ] = {
{ . compatible = " via,vt8500-pwm " , } ,
{ /* Sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , vt8500_pwm_dt_ids ) ;
static int vt8500_pwm_probe ( struct platform_device * pdev )
2010-12-23 15:11:21 +03:00
{
2012-03-15 13:04:37 +04:00
struct vt8500_chip * chip ;
2010-12-23 15:11:21 +03:00
struct resource * r ;
2012-10-27 05:49:57 +04:00
struct device_node * np = pdev - > dev . of_node ;
2012-03-15 13:04:37 +04:00
int ret ;
2010-12-23 15:11:21 +03:00
2012-10-27 05:49:57 +04:00
if ( ! np ) {
dev_err ( & pdev - > dev , " invalid devicetree node \n " ) ;
return - EINVAL ;
}
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
2012-10-27 05:49:57 +04:00
chip - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( chip - > clk ) ) {
dev_err ( & pdev - > dev , " clock source not specified \n " ) ;
return PTR_ERR ( chip - > clk ) ;
}
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 ) ;
2012-10-27 05:49:57 +04:00
if ( ! chip - > base )
2012-07-01 04:29:28 +04:00
return - EADDRNOTAVAIL ;
2010-12-23 15:11:21 +03:00
2012-10-27 05:49:57 +04:00
ret = clk_prepare ( chip - > clk ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to prepare clock \n " ) ;
return ret ;
}
2012-03-15 13:04:37 +04:00
ret = pwmchip_add ( & chip - > chip ) ;
2012-10-27 05:49:57 +04:00
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to add PWM chip \n " ) ;
2012-07-01 04:29:28 +04:00
return ret ;
2012-10-27 05:49:57 +04:00
}
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
}
2012-10-27 05:49:57 +04:00
static int vt8500_pwm_remove ( struct platform_device * pdev )
2010-12-23 15:11:21 +03:00
{
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-10-27 05:49:57 +04:00
clk_unprepare ( chip - > clk ) ;
2012-07-01 04:29:28 +04:00
return pwmchip_remove ( & chip - > chip ) ;
2010-12-23 15:11:21 +03:00
}
2012-10-27 05:49:57 +04:00
static struct platform_driver vt8500_pwm_driver = {
. probe = vt8500_pwm_probe ,
. remove = vt8500_pwm_remove ,
2010-12-23 15:11:21 +03:00
. driver = {
. name = " vt8500-pwm " ,
. owner = THIS_MODULE ,
2012-10-27 05:49:57 +04:00
. of_match_table = vt8500_pwm_dt_ids ,
2010-12-23 15:11:21 +03:00
} ,
} ;
2012-10-27 05:49:57 +04:00
module_platform_driver ( vt8500_pwm_driver ) ;
2010-12-23 15:11:21 +03:00
2012-10-27 05:49:57 +04:00
MODULE_DESCRIPTION ( " VT8500 PWM Driver " ) ;
MODULE_AUTHOR ( " Tony Prisk <linux@prisktech.co.nz> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;