2008-04-13 21:44:04 +01: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>
2009-04-13 15:59:03 +08:00
# define HAS_SECONDARY_PWM 0x10
2009-04-13 18:51:31 +08:00
# define PWM_ID_BASE(d) ((d) & 0xf)
2009-04-13 15:59:03 +08:00
static const struct platform_device_id pwm_id_table [ ] = {
/* PWM has_secondary_pwm? */
{ " pxa25x-pwm " , 0 } ,
2009-04-13 18:51:31 +08:00
{ " pxa27x-pwm " , 0 | HAS_SECONDARY_PWM } ,
2009-04-13 18:29:52 +08:00
{ " pxa168-pwm " , 1 } ,
{ " pxa910-pwm " , 1 } ,
2009-04-13 15:59:03 +08:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( platform , pwm_id_table ) ;
2008-04-13 21:44:04 +01:00
/* 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 ;
2009-04-13 15:59:03 +08:00
struct pwm_device * secondary ;
struct platform_device * pdev ;
2008-04-13 21:44:04 +01:00
const char * label ;
struct clk * clk ;
2008-06-10 23:02:31 +01:00
int clk_enabled ;
2008-04-13 21:44:04 +01: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-14 21:28:51 +01:00
if ( period_cycles < 1 )
2008-04-13 21:44:04 +01: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-10 23:02:31 +01:00
int rc = 0 ;
if ( ! pwm - > clk_enabled ) {
rc = clk_enable ( pwm - > clk ) ;
if ( ! rc )
pwm - > clk_enabled = 1 ;
}
return rc ;
2008-04-13 21:44:04 +01:00
}
EXPORT_SYMBOL ( pwm_enable ) ;
void pwm_disable ( struct pwm_device * pwm )
{
2008-06-10 23:02:31 +01:00
if ( pwm - > clk_enabled ) {
clk_disable ( pwm - > clk ) ;
pwm - > clk_enabled = 0 ;
}
2008-04-13 21:44:04 +01: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 14:18:27 +01:00
if ( pwm - > pwm_id = = pwm_id ) {
2008-04-13 21:44:04 +01:00
found = 1 ;
break ;
}
}
2008-07-01 14:18:27 +01: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-13 21:44:04 +01:00
2008-07-01 14:18:27 +01:00
mutex_unlock ( & pwm_lock ) ;
return pwm ;
2008-04-13 21:44:04 +01: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 ) ;
}
2009-04-13 15:59:03 +08:00
static int __devinit pwm_probe ( struct platform_device * pdev )
2008-04-13 21:44:04 +01:00
{
2009-04-13 15:59:03 +08:00
struct platform_device_id * id = platform_get_device_id ( pdev ) ;
struct pwm_device * pwm , * secondary = NULL ;
2008-04-13 21:44:04 +01:00
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 " ) ;
2009-04-13 15:59:03 +08:00
return - ENOMEM ;
2008-04-13 21:44:04 +01:00
}
2008-11-11 17:52:32 +00:00
pwm - > clk = clk_get ( & pdev - > dev , NULL ) ;
2008-04-13 21:44:04 +01:00
if ( IS_ERR ( pwm - > clk ) ) {
ret = PTR_ERR ( pwm - > clk ) ;
goto err_free ;
}
2008-06-10 23:02:31 +01:00
pwm - > clk_enabled = 0 ;
2008-04-13 21:44:04 +01:00
pwm - > use_count = 0 ;
2009-04-13 18:51:31 +08:00
pwm - > pwm_id = PWM_ID_BASE ( id - > driver_data ) + pdev - > id ;
2008-04-13 21:44:04 +01:00
pwm - > pdev = pdev ;
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 ;
}
2009-04-13 15:59:03 +08:00
if ( id - > driver_data & HAS_SECONDARY_PWM ) {
secondary = kzalloc ( sizeof ( struct pwm_device ) , GFP_KERNEL ) ;
if ( secondary = = NULL ) {
ret = - ENOMEM ;
goto err_free_mem ;
}
* secondary = * pwm ;
pwm - > secondary = secondary ;
/* registers for the second PWM has offset of 0x10 */
secondary - > mmio_base = pwm - > mmio_base + 0x10 ;
secondary - > pwm_id = pdev - > id + 2 ;
}
2008-04-13 21:44:04 +01:00
__add_pwm ( pwm ) ;
2009-04-13 15:59:03 +08:00
if ( secondary )
__add_pwm ( secondary ) ;
2008-04-13 21:44:04 +01:00
platform_set_drvdata ( pdev , pwm ) ;
2009-04-13 15:59:03 +08:00
return 0 ;
2008-04-13 21:44:04 +01:00
err_free_mem :
release_mem_region ( r - > start , r - > end - r - > start + 1 ) ;
err_free_clk :
clk_put ( pwm - > clk ) ;
err_free :
kfree ( pwm ) ;
2009-04-13 15:59:03 +08:00
return ret ;
2008-04-13 21:44:04 +01:00
}
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 ) ;
2009-04-13 15:59:03 +08:00
if ( pwm - > secondary ) {
list_del ( & pwm - > secondary - > node ) ;
kfree ( pwm - > secondary ) ;
}
2008-04-13 21:44:04 +01:00
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 ;
}
2009-04-13 15:59:03 +08:00
static struct platform_driver pwm_driver = {
2008-04-13 21:44:04 +01:00
. driver = {
. name = " pxa25x-pwm " ,
2009-04-13 15:59:03 +08:00
. owner = THIS_MODULE ,
2008-04-13 21:44:04 +01:00
} ,
2009-04-13 15:59:03 +08:00
. probe = pwm_probe ,
2008-04-13 21:44:04 +01:00
. remove = __devexit_p ( pwm_remove ) ,
2009-04-13 15:59:03 +08:00
. id_table = pwm_id_table ,
2008-04-13 21:44:04 +01:00
} ;
static int __init pwm_init ( void )
{
2009-04-13 15:59:03 +08:00
return platform_driver_register ( & pwm_driver ) ;
2008-04-13 21:44:04 +01:00
}
arch_initcall ( pwm_init ) ;
static void __exit pwm_exit ( void )
{
2009-04-13 15:59:03 +08:00
platform_driver_unregister ( & pwm_driver ) ;
2008-04-13 21:44:04 +01:00
}
module_exit ( pwm_exit ) ;
2008-06-05 10:45:02 +01:00
MODULE_LICENSE ( " GPL v2 " ) ;