2019-05-28 19:57:18 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2013-12-13 10:41:49 +04:00
/*
* Driver for Atmel Pulse Width Modulation Controller
*
* Copyright ( C ) 2013 Atmel Corporation
* Bo Shen < voice . shen @ atmel . com >
2019-08-24 03:10:36 +03:00
*
* Links to reference manuals for the supported PWM chips can be found in
* Documentation / arm / microchip . rst .
2019-08-24 03:10:39 +03:00
*
* Limitations :
* - Periods start with the inactive level .
* - Hardware has to be stopped in general to update settings .
*
* Software bugs / possible improvements :
* - When atmel_pwm_apply ( ) is called with state - > enabled = false a change in
* state - > polarity isn ' t honored .
* - Instead of sleeping to wait for a completed period , the interrupt
* functionality could be used .
2013-12-13 10:41:49 +04:00
*/
# include <linux/clk.h>
2015-05-25 19:11:49 +03:00
# include <linux/delay.h>
2013-12-13 10:41:49 +04:00
# include <linux/err.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/slab.h>
/* The following is global registers for PWM controller */
# define PWM_ENA 0x04
# define PWM_DIS 0x08
# define PWM_SR 0x0C
2015-05-25 19:11:49 +03:00
# define PWM_ISR 0x1C
2013-12-13 10:41:49 +04:00
/* Bit field in SR */
# define PWM_SR_ALL_CH_ON 0x0F
/* The following register is PWM channel related registers */
# define PWM_CH_REG_OFFSET 0x200
# define PWM_CH_REG_SIZE 0x20
# define PWM_CMR 0x0
/* Bit field in CMR */
# define PWM_CMR_CPOL (1 << 9)
# define PWM_CMR_UPD_CDTY (1 << 10)
2014-03-14 18:19:08 +04:00
# define PWM_CMR_CPRE_MSK 0xF
2013-12-13 10:41:49 +04:00
/* The following registers for PWM v1 */
# define PWMV1_CDTY 0x04
# define PWMV1_CPRD 0x08
# define PWMV1_CUPD 0x10
/* The following registers for PWM v2 */
# define PWMV2_CDTY 0x04
# define PWMV2_CDTYUPD 0x08
# define PWMV2_CPRD 0x0C
# define PWMV2_CPRDUPD 0x10
2019-08-24 03:10:37 +03:00
# define PWM_MAX_PRES 10
2017-03-22 16:29:34 +03:00
struct atmel_pwm_registers {
u8 period ;
u8 period_upd ;
u8 duty ;
u8 duty_upd ;
} ;
2019-02-25 19:44:37 +03:00
struct atmel_pwm_config {
2019-08-24 03:10:38 +03:00
u32 period_bits ;
2019-02-25 19:44:37 +03:00
} ;
2019-02-25 19:44:33 +03:00
struct atmel_pwm_data {
struct atmel_pwm_registers regs ;
2019-02-25 19:44:37 +03:00
struct atmel_pwm_config cfg ;
2019-02-25 19:44:33 +03:00
} ;
2013-12-13 10:41:49 +04:00
struct atmel_pwm_chip {
struct pwm_chip chip ;
struct clk * clk ;
void __iomem * base ;
2019-02-25 19:44:33 +03:00
const struct atmel_pwm_data * data ;
2013-12-13 10:41:49 +04:00
2021-04-21 12:26:08 +03:00
/*
* The hardware supports a mechanism to update a channel ' s duty cycle at
* the end of the currently running period . When such an update is
* pending we delay disabling the PWM until the new configuration is
* active because otherwise pmw_config ( duty_cycle = 0 ) ; pwm_disable ( ) ;
* might not result in an inactive output .
* This bitmask tracks for which channels an update is pending in
* hardware .
*/
u32 update_pending ;
/* Protects .update_pending */
spinlock_t lock ;
2013-12-13 10:41:49 +04:00
} ;
static inline struct atmel_pwm_chip * to_atmel_pwm_chip ( struct pwm_chip * chip )
{
return container_of ( chip , struct atmel_pwm_chip , chip ) ;
}
static inline u32 atmel_pwm_readl ( struct atmel_pwm_chip * chip ,
unsigned long offset )
{
return readl_relaxed ( chip - > base + offset ) ;
}
static inline void atmel_pwm_writel ( struct atmel_pwm_chip * chip ,
unsigned long offset , unsigned long val )
{
writel_relaxed ( val , chip - > base + offset ) ;
}
static inline u32 atmel_pwm_ch_readl ( struct atmel_pwm_chip * chip ,
unsigned int ch , unsigned long offset )
{
unsigned long base = PWM_CH_REG_OFFSET + ch * PWM_CH_REG_SIZE ;
2019-08-24 03:10:40 +03:00
return atmel_pwm_readl ( chip , base + offset ) ;
2013-12-13 10:41:49 +04:00
}
static inline void atmel_pwm_ch_writel ( struct atmel_pwm_chip * chip ,
unsigned int ch , unsigned long offset ,
unsigned long val )
{
unsigned long base = PWM_CH_REG_OFFSET + ch * PWM_CH_REG_SIZE ;
2019-08-24 03:10:40 +03:00
atmel_pwm_writel ( chip , base + offset , val ) ;
2013-12-13 10:41:49 +04:00
}
2021-04-21 12:26:08 +03:00
static void atmel_pwm_update_pending ( struct atmel_pwm_chip * chip )
{
/*
* Each channel that has its bit in ISR set started a new period since
* ISR was cleared and so there is no more update pending . Note that
* reading ISR clears it , so this needs to handle all channels to not
* loose information .
*/
u32 isr = atmel_pwm_readl ( chip , PWM_ISR ) ;
chip - > update_pending & = ~ isr ;
}
static void atmel_pwm_set_pending ( struct atmel_pwm_chip * chip , unsigned int ch )
{
spin_lock ( & chip - > lock ) ;
/*
* Clear pending flags in hardware because otherwise there might still
* be a stale flag in ISR .
*/
atmel_pwm_update_pending ( chip ) ;
chip - > update_pending | = ( 1 < < ch ) ;
spin_unlock ( & chip - > lock ) ;
}
static int atmel_pwm_test_pending ( struct atmel_pwm_chip * chip , unsigned int ch )
{
int ret = 0 ;
spin_lock ( & chip - > lock ) ;
if ( chip - > update_pending & ( 1 < < ch ) ) {
atmel_pwm_update_pending ( chip ) ;
if ( chip - > update_pending & ( 1 < < ch ) )
ret = 1 ;
}
spin_unlock ( & chip - > lock ) ;
return ret ;
}
static int atmel_pwm_wait_nonpending ( struct atmel_pwm_chip * chip , unsigned int ch )
{
unsigned long timeout = jiffies + 2 * HZ ;
int ret ;
while ( ( ret = atmel_pwm_test_pending ( chip , ch ) ) & &
time_before ( jiffies , timeout ) )
usleep_range ( 10 , 100 ) ;
return ret ? - ETIMEDOUT : 0 ;
}
2017-03-22 16:29:34 +03:00
static int atmel_pwm_calculate_cprd_and_pres ( struct pwm_chip * chip ,
2021-04-20 12:51:18 +03:00
unsigned long clkrate ,
2017-03-22 16:29:34 +03:00
const struct pwm_state * state ,
unsigned long * cprd , u32 * pres )
2013-12-13 10:41:49 +04:00
{
struct atmel_pwm_chip * atmel_pwm = to_atmel_pwm_chip ( chip ) ;
2017-03-22 16:29:34 +03:00
unsigned long long cycles = state - > period ;
2019-08-24 03:10:38 +03:00
int shift ;
2013-12-13 10:41:49 +04:00
2014-09-23 17:30:21 +04:00
/* Calculate the period cycles and prescale value */
2021-04-20 12:51:18 +03:00
cycles * = clkrate ;
2017-03-22 16:29:34 +03:00
do_div ( cycles , NSEC_PER_SEC ) ;
2013-12-13 10:41:49 +04:00
2019-08-24 03:10:38 +03:00
/*
* The register for the period length is cfg . period_bits bits wide .
* So for each bit the number of clock cycles is wider divide the input
* clock frequency by two using pres and shift cprd accordingly .
*/
shift = fls ( cycles ) - atmel_pwm - > data - > cfg . period_bits ;
2014-09-23 17:30:21 +04:00
2019-08-24 03:10:38 +03:00
if ( shift > PWM_MAX_PRES ) {
2014-09-23 17:30:21 +04:00
dev_err ( chip - > dev , " pres exceeds the maximum value \n " ) ;
return - EINVAL ;
2019-08-24 03:10:38 +03:00
} else if ( shift > 0 ) {
* pres = shift ;
cycles > > = * pres ;
} else {
* pres = 0 ;
2013-12-13 10:41:49 +04:00
}
2017-03-22 16:29:34 +03:00
* cprd = cycles ;
2013-12-13 10:41:49 +04:00
2017-03-22 16:29:34 +03:00
return 0 ;
2013-12-13 10:41:49 +04:00
}
2017-03-22 16:29:34 +03:00
static void atmel_pwm_calculate_cdty ( const struct pwm_state * state ,
2021-04-20 12:51:18 +03:00
unsigned long clkrate , unsigned long cprd ,
u32 pres , unsigned long * cdty )
2013-12-13 10:41:49 +04:00
{
2017-03-22 16:29:34 +03:00
unsigned long long cycles = state - > duty_cycle ;
2013-12-13 10:41:49 +04:00
2021-04-20 12:51:18 +03:00
cycles * = clkrate ;
do_div ( cycles , NSEC_PER_SEC ) ;
cycles > > = pres ;
2017-03-22 16:29:34 +03:00
* cdty = cprd - cycles ;
2013-12-13 10:41:49 +04:00
}
2017-03-22 16:29:34 +03:00
static void atmel_pwm_update_cdty ( struct pwm_chip * chip , struct pwm_device * pwm ,
unsigned long cdty )
2013-12-13 10:41:49 +04:00
{
struct atmel_pwm_chip * atmel_pwm = to_atmel_pwm_chip ( chip ) ;
u32 val ;
2019-02-25 19:44:33 +03:00
if ( atmel_pwm - > data - > regs . duty_upd = =
atmel_pwm - > data - > regs . period_upd ) {
2017-03-22 16:29:34 +03:00
val = atmel_pwm_ch_readl ( atmel_pwm , pwm - > hwpwm , PWM_CMR ) ;
val & = ~ PWM_CMR_UPD_CDTY ;
atmel_pwm_ch_writel ( atmel_pwm , pwm - > hwpwm , PWM_CMR , val ) ;
2013-12-13 10:41:49 +04:00
}
2017-03-22 16:29:34 +03:00
atmel_pwm_ch_writel ( atmel_pwm , pwm - > hwpwm ,
2019-02-25 19:44:33 +03:00
atmel_pwm - > data - > regs . duty_upd , cdty ) ;
2021-04-21 12:26:08 +03:00
atmel_pwm_set_pending ( atmel_pwm , pwm - > hwpwm ) ;
2013-12-13 10:41:49 +04:00
}
2017-03-22 16:29:34 +03:00
static void atmel_pwm_set_cprd_cdty ( struct pwm_chip * chip ,
struct pwm_device * pwm ,
unsigned long cprd , unsigned long cdty )
2013-12-13 10:41:49 +04:00
{
struct atmel_pwm_chip * atmel_pwm = to_atmel_pwm_chip ( chip ) ;
2017-03-22 16:29:34 +03:00
atmel_pwm_ch_writel ( atmel_pwm , pwm - > hwpwm ,
2019-02-25 19:44:33 +03:00
atmel_pwm - > data - > regs . duty , cdty ) ;
2017-03-22 16:29:34 +03:00
atmel_pwm_ch_writel ( atmel_pwm , pwm - > hwpwm ,
2019-02-25 19:44:33 +03:00
atmel_pwm - > data - > regs . period , cprd ) ;
2013-12-13 10:41:49 +04:00
}
2017-03-22 16:29:34 +03:00
static void atmel_pwm_disable ( struct pwm_chip * chip , struct pwm_device * pwm ,
bool disable_clk )
2013-12-13 10:41:49 +04:00
{
struct atmel_pwm_chip * atmel_pwm = to_atmel_pwm_chip ( chip ) ;
2021-12-10 03:22:50 +03:00
unsigned long timeout ;
2015-05-25 19:11:49 +03:00
2021-04-21 12:26:08 +03:00
atmel_pwm_wait_nonpending ( atmel_pwm , pwm - > hwpwm ) ;
2013-12-13 10:41:49 +04:00
atmel_pwm_writel ( atmel_pwm , PWM_DIS , 1 < < pwm - > hwpwm ) ;
2016-05-13 14:09:37 +03:00
/*
* Wait for the PWM channel disable operation to be effective before
* stopping the clock .
*/
timeout = jiffies + 2 * HZ ;
while ( ( atmel_pwm_readl ( atmel_pwm , PWM_SR ) & ( 1 < < pwm - > hwpwm ) ) & &
time_before ( jiffies , timeout ) )
usleep_range ( 10 , 100 ) ;
2017-03-22 16:29:34 +03:00
if ( disable_clk )
clk_disable ( atmel_pwm - > clk ) ;
}
static int atmel_pwm_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
2019-08-24 18:37:07 +03:00
const struct pwm_state * state )
2017-03-22 16:29:34 +03:00
{
struct atmel_pwm_chip * atmel_pwm = to_atmel_pwm_chip ( chip ) ;
struct pwm_state cstate ;
unsigned long cprd , cdty ;
u32 pres , val ;
int ret ;
pwm_get_state ( pwm , & cstate ) ;
if ( state - > enabled ) {
2021-04-20 12:51:18 +03:00
unsigned long clkrate = clk_get_rate ( atmel_pwm - > clk ) ;
2017-03-22 16:29:34 +03:00
if ( cstate . enabled & &
cstate . polarity = = state - > polarity & &
cstate . period = = state - > period ) {
2021-04-20 12:51:18 +03:00
u32 cmr = atmel_pwm_ch_readl ( atmel_pwm , pwm - > hwpwm , PWM_CMR ) ;
2017-03-22 16:29:34 +03:00
cprd = atmel_pwm_ch_readl ( atmel_pwm , pwm - > hwpwm ,
2019-02-25 19:44:33 +03:00
atmel_pwm - > data - > regs . period ) ;
2021-04-20 12:51:18 +03:00
pres = cmr & PWM_CMR_CPRE_MSK ;
atmel_pwm_calculate_cdty ( state , clkrate , cprd , pres , & cdty ) ;
2017-03-22 16:29:34 +03:00
atmel_pwm_update_cdty ( chip , pwm , cdty ) ;
return 0 ;
}
2021-04-20 12:51:18 +03:00
ret = atmel_pwm_calculate_cprd_and_pres ( chip , clkrate , state , & cprd ,
2017-03-22 16:29:34 +03:00
& pres ) ;
if ( ret ) {
dev_err ( chip - > dev ,
" failed to calculate cprd and prescaler \n " ) ;
return ret ;
}
2021-04-20 12:51:18 +03:00
atmel_pwm_calculate_cdty ( state , clkrate , cprd , pres , & cdty ) ;
2017-03-22 16:29:34 +03:00
if ( cstate . enabled ) {
atmel_pwm_disable ( chip , pwm , false ) ;
} else {
ret = clk_enable ( atmel_pwm - > clk ) ;
if ( ret ) {
dev_err ( chip - > dev , " failed to enable clock \n " ) ;
return ret ;
}
}
/* It is necessary to preserve CPOL, inside CMR */
val = atmel_pwm_ch_readl ( atmel_pwm , pwm - > hwpwm , PWM_CMR ) ;
val = ( val & ~ PWM_CMR_CPRE_MSK ) | ( pres & PWM_CMR_CPRE_MSK ) ;
if ( state - > polarity = = PWM_POLARITY_NORMAL )
val & = ~ PWM_CMR_CPOL ;
else
val | = PWM_CMR_CPOL ;
atmel_pwm_ch_writel ( atmel_pwm , pwm - > hwpwm , PWM_CMR , val ) ;
atmel_pwm_set_cprd_cdty ( chip , pwm , cprd , cdty ) ;
atmel_pwm_writel ( atmel_pwm , PWM_ENA , 1 < < pwm - > hwpwm ) ;
} else if ( cstate . enabled ) {
atmel_pwm_disable ( chip , pwm , true ) ;
}
return 0 ;
2013-12-13 10:41:49 +04:00
}
2022-12-02 21:35:26 +03:00
static int atmel_pwm_get_state ( struct pwm_chip * chip , struct pwm_device * pwm ,
struct pwm_state * state )
2019-08-24 03:10:41 +03:00
{
struct atmel_pwm_chip * atmel_pwm = to_atmel_pwm_chip ( chip ) ;
u32 sr , cmr ;
sr = atmel_pwm_readl ( atmel_pwm , PWM_SR ) ;
cmr = atmel_pwm_ch_readl ( atmel_pwm , pwm - > hwpwm , PWM_CMR ) ;
if ( sr & ( 1 < < pwm - > hwpwm ) ) {
unsigned long rate = clk_get_rate ( atmel_pwm - > clk ) ;
u32 cdty , cprd , pres ;
u64 tmp ;
pres = cmr & PWM_CMR_CPRE_MSK ;
cprd = atmel_pwm_ch_readl ( atmel_pwm , pwm - > hwpwm ,
atmel_pwm - > data - > regs . period ) ;
tmp = ( u64 ) cprd * NSEC_PER_SEC ;
tmp < < = pres ;
state - > period = DIV64_U64_ROUND_UP ( tmp , rate ) ;
2021-04-21 12:26:08 +03:00
/* Wait for an updated duty_cycle queued in hardware */
atmel_pwm_wait_nonpending ( atmel_pwm , pwm - > hwpwm ) ;
2019-08-24 03:10:41 +03:00
cdty = atmel_pwm_ch_readl ( atmel_pwm , pwm - > hwpwm ,
atmel_pwm - > data - > regs . duty ) ;
2021-04-20 12:51:17 +03:00
tmp = ( u64 ) ( cprd - cdty ) * NSEC_PER_SEC ;
2019-08-24 03:10:41 +03:00
tmp < < = pres ;
state - > duty_cycle = DIV64_U64_ROUND_UP ( tmp , rate ) ;
state - > enabled = true ;
} else {
state - > enabled = false ;
}
if ( cmr & PWM_CMR_CPOL )
state - > polarity = PWM_POLARITY_INVERSED ;
else
state - > polarity = PWM_POLARITY_NORMAL ;
2022-12-02 21:35:26 +03:00
return 0 ;
2019-08-24 03:10:41 +03:00
}
2013-12-13 10:41:49 +04:00
static const struct pwm_ops atmel_pwm_ops = {
2017-03-22 16:29:34 +03:00
. apply = atmel_pwm_apply ,
2019-08-24 03:10:41 +03:00
. get_state = atmel_pwm_get_state ,
2013-12-13 10:41:49 +04:00
. owner = THIS_MODULE ,
} ;
2019-02-25 19:44:41 +03:00
static const struct atmel_pwm_data atmel_sam9rl_pwm_data = {
2019-02-25 19:44:33 +03:00
. regs = {
. period = PWMV1_CPRD ,
. period_upd = PWMV1_CUPD ,
. duty = PWMV1_CDTY ,
. duty_upd = PWMV1_CUPD ,
} ,
2019-02-25 19:44:37 +03:00
. cfg = {
/* 16 bits to keep period and duty. */
2019-08-24 03:10:38 +03:00
. period_bits = 16 ,
2019-02-25 19:44:37 +03:00
} ,
2013-12-13 10:41:49 +04:00
} ;
2019-02-25 19:44:41 +03:00
static const struct atmel_pwm_data atmel_sama5_pwm_data = {
2019-02-25 19:44:33 +03:00
. regs = {
. period = PWMV2_CPRD ,
. period_upd = PWMV2_CPRDUPD ,
. duty = PWMV2_CDTY ,
. duty_upd = PWMV2_CDTYUPD ,
} ,
2019-02-25 19:44:37 +03:00
. cfg = {
/* 16 bits to keep period and duty. */
2019-08-24 03:10:38 +03:00
. period_bits = 16 ,
2019-02-25 19:44:37 +03:00
} ,
2013-12-13 10:41:49 +04:00
} ;
2019-02-25 19:44:45 +03:00
static const struct atmel_pwm_data mchp_sam9x60_pwm_data = {
. regs = {
. period = PWMV1_CPRD ,
. period_upd = PWMV1_CUPD ,
. duty = PWMV1_CDTY ,
. duty_upd = PWMV1_CUPD ,
} ,
. cfg = {
/* 32 bits to keep period and duty. */
2019-08-24 03:10:38 +03:00
. period_bits = 32 ,
2019-02-25 19:44:45 +03:00
} ,
} ;
2013-12-13 10:41:49 +04:00
static const struct of_device_id atmel_pwm_dt_ids [ ] = {
{
. compatible = " atmel,at91sam9rl-pwm " ,
2019-02-25 19:44:41 +03:00
. data = & atmel_sam9rl_pwm_data ,
2013-12-13 10:41:49 +04:00
} , {
. compatible = " atmel,sama5d3-pwm " ,
2019-02-25 19:44:41 +03:00
. data = & atmel_sama5_pwm_data ,
2017-03-22 16:29:35 +03:00
} , {
. compatible = " atmel,sama5d2-pwm " ,
2019-02-25 19:44:41 +03:00
. data = & atmel_sama5_pwm_data ,
2019-02-25 19:44:45 +03:00
} , {
. compatible = " microchip,sam9x60-pwm " ,
. data = & mchp_sam9x60_pwm_data ,
2013-12-13 10:41:49 +04:00
} , {
/* sentinel */
} ,
} ;
MODULE_DEVICE_TABLE ( of , atmel_pwm_dt_ids ) ;
static int atmel_pwm_probe ( struct platform_device * pdev )
{
struct atmel_pwm_chip * atmel_pwm ;
int ret ;
atmel_pwm = devm_kzalloc ( & pdev - > dev , sizeof ( * atmel_pwm ) , GFP_KERNEL ) ;
if ( ! atmel_pwm )
return - ENOMEM ;
2019-09-21 02:55:48 +03:00
atmel_pwm - > data = of_device_get_match_data ( & pdev - > dev ) ;
2021-04-21 12:26:08 +03:00
atmel_pwm - > update_pending = 0 ;
spin_lock_init ( & atmel_pwm - > lock ) ;
2019-09-21 02:55:48 +03:00
2019-12-29 11:06:10 +03:00
atmel_pwm - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2013-12-13 10:41:49 +04:00
if ( IS_ERR ( atmel_pwm - > base ) )
return PTR_ERR ( atmel_pwm - > base ) ;
atmel_pwm - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( atmel_pwm - > clk ) )
return PTR_ERR ( atmel_pwm - > clk ) ;
ret = clk_prepare ( atmel_pwm - > clk ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to prepare PWM clock \n " ) ;
return ret ;
}
atmel_pwm - > chip . dev = & pdev - > dev ;
atmel_pwm - > chip . ops = & atmel_pwm_ops ;
atmel_pwm - > chip . npwm = 4 ;
ret = pwmchip_add ( & atmel_pwm - > chip ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to add PWM chip %d \n " , ret ) ;
goto unprepare_clk ;
}
platform_set_drvdata ( pdev , atmel_pwm ) ;
2013-12-19 07:42:22 +04:00
return ret ;
2013-12-13 10:41:49 +04:00
unprepare_clk :
clk_unprepare ( atmel_pwm - > clk ) ;
return ret ;
}
2023-03-03 21:54:18 +03:00
static void atmel_pwm_remove ( struct platform_device * pdev )
2013-12-13 10:41:49 +04:00
{
struct atmel_pwm_chip * atmel_pwm = platform_get_drvdata ( pdev ) ;
2021-03-24 22:56:35 +03:00
pwmchip_remove ( & atmel_pwm - > chip ) ;
2013-12-13 10:41:49 +04:00
clk_unprepare ( atmel_pwm - > clk ) ;
}
static struct platform_driver atmel_pwm_driver = {
. driver = {
. name = " atmel-pwm " ,
. of_match_table = of_match_ptr ( atmel_pwm_dt_ids ) ,
} ,
. probe = atmel_pwm_probe ,
2023-03-03 21:54:18 +03:00
. remove_new = atmel_pwm_remove ,
2013-12-13 10:41:49 +04:00
} ;
module_platform_driver ( atmel_pwm_driver ) ;
MODULE_ALIAS ( " platform:atmel-pwm " ) ;
MODULE_AUTHOR ( " Bo Shen <voice.shen@atmel.com> " ) ;
MODULE_DESCRIPTION ( " Atmel PWM driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;