2018-11-10 19:22:58 +03:00
// SPDX-License-Identifier: GPL-2.0
2014-10-08 14:14:32 +04:00
/*
* Copyright 2014 Bart Tanghe < bart . tanghe @ thomasmore . be >
*/
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/pwm.h>
# define PWM_CONTROL 0x000
# define PWM_CONTROL_SHIFT(x) ((x) * 8)
# define PWM_CONTROL_MASK 0xff
# define PWM_MODE 0x80 /* set timer in PWM mode */
# define PWM_ENABLE (1 << 0)
# define PWM_POLARITY (1 << 4)
# define PERIOD(x) (((x) * 0x10) + 0x10)
# define DUTY(x) (((x) * 0x10) + 0x14)
2019-08-24 17:09:47 +03:00
# define PERIOD_MIN 0x2
2014-10-08 14:14:32 +04:00
struct bcm2835_pwm {
struct pwm_chip chip ;
struct device * dev ;
void __iomem * base ;
struct clk * clk ;
} ;
static inline struct bcm2835_pwm * to_bcm2835_pwm ( struct pwm_chip * chip )
{
return container_of ( chip , struct bcm2835_pwm , chip ) ;
}
static int bcm2835_pwm_request ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct bcm2835_pwm * pc = to_bcm2835_pwm ( chip ) ;
u32 value ;
value = readl ( pc - > base + PWM_CONTROL ) ;
value & = ~ ( PWM_CONTROL_MASK < < PWM_CONTROL_SHIFT ( pwm - > hwpwm ) ) ;
value | = ( PWM_MODE < < PWM_CONTROL_SHIFT ( pwm - > hwpwm ) ) ;
writel ( value , pc - > base + PWM_CONTROL ) ;
return 0 ;
}
static void bcm2835_pwm_free ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct bcm2835_pwm * pc = to_bcm2835_pwm ( chip ) ;
u32 value ;
value = readl ( pc - > base + PWM_CONTROL ) ;
value & = ~ ( PWM_CONTROL_MASK < < PWM_CONTROL_SHIFT ( pwm - > hwpwm ) ) ;
writel ( value , pc - > base + PWM_CONTROL ) ;
}
2020-12-09 23:48:25 +03:00
static int bcm2835_pwm_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
const struct pwm_state * state )
2014-10-08 14:14:32 +04:00
{
2020-12-09 23:48:25 +03:00
2014-10-08 14:14:32 +04:00
struct bcm2835_pwm * pc = to_bcm2835_pwm ( chip ) ;
2015-12-02 01:55:40 +03:00
unsigned long rate = clk_get_rate ( pc - > clk ) ;
2021-01-14 23:48:04 +03:00
unsigned long long period_cycles ;
u64 max_period ;
2020-12-09 23:48:25 +03:00
u32 val ;
2015-12-02 01:55:40 +03:00
if ( ! rate ) {
dev_err ( pc - > dev , " failed to get clock rate \n " ) ;
return - EINVAL ;
}
2021-01-14 23:48:04 +03:00
/*
* period_cycles must be a 32 bit value , so period * rate / NSEC_PER_SEC
* must be < = U32_MAX . As U32_MAX * NSEC_PER_SEC < U64_MAX the
* multiplication period * rate doesn ' t overflow .
* To calculate the maximal possible period that guarantees the
* above inequality :
*
* round ( period * rate / NSEC_PER_SEC ) < = U32_MAX
* < = > period * rate / NSEC_PER_SEC < U32_MAX + 0.5
* < = > period * rate < ( U32_MAX + 0.5 ) * NSEC_PER_SEC
* < = > period < ( ( U32_MAX + 0.5 ) * NSEC_PER_SEC ) / rate
* < = > period < ( ( U32_MAX * NSEC_PER_SEC + NSEC_PER_SEC / 2 ) / rate
* < = > period < = ceil ( ( U32_MAX * NSEC_PER_SEC + NSEC_PER_SEC / 2 ) / rate ) - 1
*/
max_period = DIV_ROUND_UP_ULL ( ( u64 ) U32_MAX * NSEC_PER_SEC + NSEC_PER_SEC / 2 , rate ) - 1 ;
if ( state - > period > max_period )
return - EINVAL ;
2020-12-09 23:48:25 +03:00
/* set period */
2021-01-14 23:48:04 +03:00
period_cycles = DIV_ROUND_CLOSEST_ULL ( state - > period * rate , NSEC_PER_SEC ) ;
2014-10-08 14:14:32 +04:00
2021-01-14 23:48:04 +03:00
/* don't accept a period that is too small */
if ( period_cycles < PERIOD_MIN )
2014-10-08 14:14:32 +04:00
return - EINVAL ;
2021-01-14 23:48:04 +03:00
writel ( period_cycles , pc - > base + PERIOD ( pwm - > hwpwm ) ) ;
2014-10-08 14:14:32 +04:00
2020-12-09 23:48:25 +03:00
/* set duty cycle */
2021-01-14 23:48:04 +03:00
val = DIV_ROUND_CLOSEST_ULL ( state - > duty_cycle * rate , NSEC_PER_SEC ) ;
2020-12-09 23:48:25 +03:00
writel ( val , pc - > base + DUTY ( pwm - > hwpwm ) ) ;
2014-10-08 14:14:32 +04:00
2020-12-09 23:48:25 +03:00
/* set polarity */
val = readl ( pc - > base + PWM_CONTROL ) ;
2014-10-08 14:14:32 +04:00
2020-12-09 23:48:25 +03:00
if ( state - > polarity = = PWM_POLARITY_NORMAL )
val & = ~ ( PWM_POLARITY < < PWM_CONTROL_SHIFT ( pwm - > hwpwm ) ) ;
else
val | = PWM_POLARITY < < PWM_CONTROL_SHIFT ( pwm - > hwpwm ) ;
2014-10-08 14:14:32 +04:00
2020-12-09 23:48:25 +03:00
/* enable/disable */
if ( state - > enabled )
val | = PWM_ENABLE < < PWM_CONTROL_SHIFT ( pwm - > hwpwm ) ;
2014-10-08 14:14:32 +04:00
else
2020-12-09 23:48:25 +03:00
val & = ~ ( PWM_ENABLE < < PWM_CONTROL_SHIFT ( pwm - > hwpwm ) ) ;
2014-10-08 14:14:32 +04:00
2020-12-09 23:48:25 +03:00
writel ( val , pc - > base + PWM_CONTROL ) ;
2014-10-08 14:14:32 +04:00
return 0 ;
}
static const struct pwm_ops bcm2835_pwm_ops = {
. request = bcm2835_pwm_request ,
. free = bcm2835_pwm_free ,
2020-12-09 23:48:25 +03:00
. apply = bcm2835_pwm_apply ,
2014-10-08 14:14:32 +04:00
. owner = THIS_MODULE ,
} ;
static int bcm2835_pwm_probe ( struct platform_device * pdev )
{
struct bcm2835_pwm * pc ;
int ret ;
pc = devm_kzalloc ( & pdev - > dev , sizeof ( * pc ) , GFP_KERNEL ) ;
if ( ! pc )
return - ENOMEM ;
pc - > dev = & pdev - > dev ;
2019-12-29 11:06:00 +03:00
pc - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2014-10-08 14:14:32 +04:00
if ( IS_ERR ( pc - > base ) )
return PTR_ERR ( pc - > base ) ;
pc - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
2020-08-26 17:47:42 +03:00
if ( IS_ERR ( pc - > clk ) )
return dev_err_probe ( & pdev - > dev , PTR_ERR ( pc - > clk ) ,
" clock not found \n " ) ;
2014-10-08 14:14:32 +04:00
ret = clk_prepare_enable ( pc - > clk ) ;
if ( ret )
return ret ;
pc - > chip . dev = & pdev - > dev ;
pc - > chip . ops = & bcm2835_pwm_ops ;
pc - > chip . npwm = 2 ;
platform_set_drvdata ( pdev , pc ) ;
ret = pwmchip_add ( & pc - > chip ) ;
if ( ret < 0 )
goto add_fail ;
return 0 ;
add_fail :
clk_disable_unprepare ( pc - > clk ) ;
return ret ;
}
2023-03-03 21:54:20 +03:00
static void bcm2835_pwm_remove ( struct platform_device * pdev )
2014-10-08 14:14:32 +04:00
{
struct bcm2835_pwm * pc = platform_get_drvdata ( pdev ) ;
2021-03-25 11:29:31 +03:00
pwmchip_remove ( & pc - > chip ) ;
2014-10-08 14:14:32 +04:00
clk_disable_unprepare ( pc - > clk ) ;
}
static const struct of_device_id bcm2835_pwm_of_match [ ] = {
{ . compatible = " brcm,bcm2835-pwm " , } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , bcm2835_pwm_of_match ) ;
static struct platform_driver bcm2835_pwm_driver = {
. driver = {
. name = " bcm2835-pwm " ,
. of_match_table = bcm2835_pwm_of_match ,
} ,
. probe = bcm2835_pwm_probe ,
2023-03-03 21:54:20 +03:00
. remove_new = bcm2835_pwm_remove ,
2014-10-08 14:14:32 +04:00
} ;
module_platform_driver ( bcm2835_pwm_driver ) ;
2015-12-02 01:55:41 +03:00
MODULE_AUTHOR ( " Bart Tanghe <bart.tanghe@thomasmore.be> " ) ;
2014-10-08 14:14:32 +04:00
MODULE_DESCRIPTION ( " Broadcom BCM2835 PWM driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;