2008-04-14 00:44:04 +04:00
/*
* linux / arch / arm / mach - pxa / pwm . c
*
* simple driver for PWM ( Pulse Width Modulator ) controller
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* 2008 - 02 - 13 initial version
* eric miao < eric . miao @ marvell . com >
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/platform_device.h>
# include <linux/err.h>
# include <linux/clk.h>
# include <linux/io.h>
# include <linux/pwm.h>
# include <asm/div64.h>
/* PWM registers and bits definitions */
# define PWMCR (0x00)
# define PWMDCR (0x04)
# define PWMPCR (0x08)
# define PWMCR_SD (1 << 6)
# define PWMDCR_FD (1 << 10)
struct pwm_device {
struct list_head node ;
struct platform_device * pdev ;
const char * label ;
struct clk * clk ;
2008-06-11 02:02:31 +04:00
int clk_enabled ;
2008-04-14 00:44:04 +04:00
void __iomem * mmio_base ;
unsigned int use_count ;
unsigned int pwm_id ;
} ;
/*
* period_ns = 10 ^ 9 * ( PRESCALE + 1 ) * ( PV + 1 ) / PWM_CLK_RATE
* duty_ns = 10 ^ 9 * ( PRESCALE + 1 ) * DC / PWM_CLK_RATE
*/
int pwm_config ( struct pwm_device * pwm , int duty_ns , int period_ns )
{
unsigned long long c ;
unsigned long period_cycles , prescale , pv , dc ;
if ( pwm = = NULL | | period_ns = = 0 | | duty_ns > period_ns )
return - EINVAL ;
c = clk_get_rate ( pwm - > clk ) ;
c = c * period_ns ;
do_div ( c , 1000000000 ) ;
period_cycles = c ;
2008-10-15 00:28:51 +04:00
if ( period_cycles < 1 )
2008-04-14 00:44:04 +04:00
period_cycles = 1 ;
prescale = ( period_cycles - 1 ) / 1024 ;
pv = period_cycles / ( prescale + 1 ) - 1 ;
if ( prescale > 63 )
return - EINVAL ;
if ( duty_ns = = period_ns )
dc = PWMDCR_FD ;
else
dc = ( pv + 1 ) * duty_ns / period_ns ;
/* NOTE: the clock to PWM has to be enabled first
* before writing to the registers
*/
clk_enable ( pwm - > clk ) ;
__raw_writel ( prescale , pwm - > mmio_base + PWMCR ) ;
__raw_writel ( dc , pwm - > mmio_base + PWMDCR ) ;
__raw_writel ( pv , pwm - > mmio_base + PWMPCR ) ;
clk_disable ( pwm - > clk ) ;
return 0 ;
}
EXPORT_SYMBOL ( pwm_config ) ;
int pwm_enable ( struct pwm_device * pwm )
{
2008-06-11 02:02:31 +04:00
int rc = 0 ;
if ( ! pwm - > clk_enabled ) {
rc = clk_enable ( pwm - > clk ) ;
if ( ! rc )
pwm - > clk_enabled = 1 ;
}
return rc ;
2008-04-14 00:44:04 +04:00
}
EXPORT_SYMBOL ( pwm_enable ) ;
void pwm_disable ( struct pwm_device * pwm )
{
2008-06-11 02:02:31 +04:00
if ( pwm - > clk_enabled ) {
clk_disable ( pwm - > clk ) ;
pwm - > clk_enabled = 0 ;
}
2008-04-14 00:44:04 +04:00
}
EXPORT_SYMBOL ( pwm_disable ) ;
static DEFINE_MUTEX ( pwm_lock ) ;
static LIST_HEAD ( pwm_list ) ;
struct pwm_device * pwm_request ( int pwm_id , const char * label )
{
struct pwm_device * pwm ;
int found = 0 ;
mutex_lock ( & pwm_lock ) ;
list_for_each_entry ( pwm , & pwm_list , node ) {
2008-07-01 17:18:27 +04:00
if ( pwm - > pwm_id = = pwm_id ) {
2008-04-14 00:44:04 +04:00
found = 1 ;
break ;
}
}
2008-07-01 17:18:27 +04:00
if ( found ) {
if ( pwm - > use_count = = 0 ) {
pwm - > use_count + + ;
pwm - > label = label ;
} else
pwm = ERR_PTR ( - EBUSY ) ;
} else
pwm = ERR_PTR ( - ENOENT ) ;
2008-04-14 00:44:04 +04:00
2008-07-01 17:18:27 +04:00
mutex_unlock ( & pwm_lock ) ;
return pwm ;
2008-04-14 00:44:04 +04:00
}
EXPORT_SYMBOL ( pwm_request ) ;
void pwm_free ( struct pwm_device * pwm )
{
mutex_lock ( & pwm_lock ) ;
if ( pwm - > use_count ) {
pwm - > use_count - - ;
pwm - > label = NULL ;
} else
pr_warning ( " PWM device already freed \n " ) ;
mutex_unlock ( & pwm_lock ) ;
}
EXPORT_SYMBOL ( pwm_free ) ;
static inline void __add_pwm ( struct pwm_device * pwm )
{
mutex_lock ( & pwm_lock ) ;
list_add_tail ( & pwm - > node , & pwm_list ) ;
mutex_unlock ( & pwm_lock ) ;
}
static struct pwm_device * pwm_probe ( struct platform_device * pdev ,
unsigned int pwm_id , struct pwm_device * parent_pwm )
{
struct pwm_device * pwm ;
struct resource * r ;
int ret = 0 ;
pwm = kzalloc ( sizeof ( struct pwm_device ) , GFP_KERNEL ) ;
if ( pwm = = NULL ) {
dev_err ( & pdev - > dev , " failed to allocate memory \n " ) ;
return ERR_PTR ( - ENOMEM ) ;
}
2008-11-11 20:52:32 +03:00
pwm - > clk = clk_get ( & pdev - > dev , NULL ) ;
2008-04-14 00:44:04 +04:00
if ( IS_ERR ( pwm - > clk ) ) {
ret = PTR_ERR ( pwm - > clk ) ;
goto err_free ;
}
2008-06-11 02:02:31 +04:00
pwm - > clk_enabled = 0 ;
2008-04-14 00:44:04 +04:00
pwm - > use_count = 0 ;
pwm - > pwm_id = pwm_id ;
pwm - > pdev = pdev ;
if ( parent_pwm ! = NULL ) {
/* registers for the second PWM has offset of 0x10 */
pwm - > mmio_base = parent_pwm - > mmio_base + 0x10 ;
__add_pwm ( pwm ) ;
return pwm ;
}
r = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( r = = NULL ) {
dev_err ( & pdev - > dev , " no memory resource defined \n " ) ;
ret = - ENODEV ;
goto err_free_clk ;
}
r = request_mem_region ( r - > start , r - > end - r - > start + 1 , pdev - > name ) ;
if ( r = = NULL ) {
dev_err ( & pdev - > dev , " failed to request memory resource \n " ) ;
ret = - EBUSY ;
goto err_free_clk ;
}
pwm - > mmio_base = ioremap ( r - > start , r - > end - r - > start + 1 ) ;
if ( pwm - > mmio_base = = NULL ) {
dev_err ( & pdev - > dev , " failed to ioremap() registers \n " ) ;
ret = - ENODEV ;
goto err_free_mem ;
}
__add_pwm ( pwm ) ;
platform_set_drvdata ( pdev , pwm ) ;
return pwm ;
err_free_mem :
release_mem_region ( r - > start , r - > end - r - > start + 1 ) ;
err_free_clk :
clk_put ( pwm - > clk ) ;
err_free :
kfree ( pwm ) ;
return ERR_PTR ( ret ) ;
}
static int __devinit pxa25x_pwm_probe ( struct platform_device * pdev )
{
struct pwm_device * pwm = pwm_probe ( pdev , pdev - > id , NULL ) ;
if ( IS_ERR ( pwm ) )
return PTR_ERR ( pwm ) ;
return 0 ;
}
static int __devinit pxa27x_pwm_probe ( struct platform_device * pdev )
{
struct pwm_device * pwm ;
2008-06-30 21:09:03 +04:00
pwm = pwm_probe ( pdev , pdev - > id , NULL ) ;
2008-04-14 00:44:04 +04:00
if ( IS_ERR ( pwm ) )
return PTR_ERR ( pwm ) ;
2008-06-30 21:09:03 +04:00
pwm = pwm_probe ( pdev , pdev - > id + 2 , pwm ) ;
2008-04-14 00:44:04 +04:00
if ( IS_ERR ( pwm ) )
return PTR_ERR ( pwm ) ;
return 0 ;
}
static int __devexit pwm_remove ( struct platform_device * pdev )
{
struct pwm_device * pwm ;
struct resource * r ;
pwm = platform_get_drvdata ( pdev ) ;
if ( pwm = = NULL )
return - ENODEV ;
mutex_lock ( & pwm_lock ) ;
list_del ( & pwm - > node ) ;
mutex_unlock ( & pwm_lock ) ;
iounmap ( pwm - > mmio_base ) ;
r = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
release_mem_region ( r - > start , r - > end - r - > start + 1 ) ;
clk_put ( pwm - > clk ) ;
kfree ( pwm ) ;
return 0 ;
}
static struct platform_driver pxa25x_pwm_driver = {
. driver = {
. name = " pxa25x-pwm " ,
} ,
. probe = pxa25x_pwm_probe ,
. remove = __devexit_p ( pwm_remove ) ,
} ;
static struct platform_driver pxa27x_pwm_driver = {
. driver = {
. name = " pxa27x-pwm " ,
} ,
. probe = pxa27x_pwm_probe ,
. remove = __devexit_p ( pwm_remove ) ,
} ;
static int __init pwm_init ( void )
{
int ret = 0 ;
ret = platform_driver_register ( & pxa25x_pwm_driver ) ;
if ( ret ) {
printk ( KERN_ERR " failed to register pxa25x_pwm_driver \n " ) ;
return ret ;
}
ret = platform_driver_register ( & pxa27x_pwm_driver ) ;
if ( ret ) {
printk ( KERN_ERR " failed to register pxa27x_pwm_driver \n " ) ;
return ret ;
}
return ret ;
}
arch_initcall ( pwm_init ) ;
static void __exit pwm_exit ( void )
{
platform_driver_unregister ( & pxa25x_pwm_driver ) ;
platform_driver_unregister ( & pxa27x_pwm_driver ) ;
}
module_exit ( pwm_exit ) ;
2008-06-05 13:45:02 +04:00
MODULE_LICENSE ( " GPL v2 " ) ;