2019-09-20 01:49:06 +03:00
// SPDX-License-Identifier: GPL-2.0
2017-01-23 21:34:37 +03:00
/*
2019-09-20 01:49:06 +03:00
* MediaTek Pulse Width Modulator driver
2017-01-23 21:34:37 +03:00
*
* Copyright ( C ) 2015 John Crispin < blogic @ openwrt . org >
2017-06-30 09:05:18 +03:00
* Copyright ( C ) 2017 Zhi Mao < zhi . mao @ mediatek . com >
2017-01-23 21:34:37 +03:00
*
*/
# include <linux/err.h>
# include <linux/io.h>
# include <linux/ioport.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/clk.h>
# include <linux/of.h>
2017-10-25 13:11:01 +03:00
# include <linux/of_device.h>
2017-01-23 21:34:37 +03:00
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/slab.h>
# include <linux/types.h>
/* PWM registers and bits definitions */
# define PWMCON 0x00
# define PWMHDUR 0x04
# define PWMLDUR 0x08
# define PWMGDUR 0x0c
# define PWMWAVENUM 0x28
# define PWMDWIDTH 0x2c
2018-03-01 11:19:12 +03:00
# define PWM45DWIDTH_FIXUP 0x30
2017-01-23 21:34:37 +03:00
# define PWMTHRES 0x30
2018-03-01 11:19:12 +03:00
# define PWM45THRES_FIXUP 0x34
2020-10-19 17:07:02 +03:00
# define PWM_CK_26M_SEL 0x210
2017-01-23 21:34:37 +03:00
2017-06-30 09:05:20 +03:00
# define PWM_CLK_DIV_MAX 7
2019-09-20 01:49:05 +03:00
struct pwm_mediatek_of_data {
2017-10-25 13:11:01 +03:00
unsigned int num_pwms ;
2018-03-01 11:19:12 +03:00
bool pwm45_fixup ;
2020-10-19 17:07:02 +03:00
bool has_ck_26m_sel ;
2017-01-23 21:34:37 +03:00
} ;
/**
2019-09-20 01:49:05 +03:00
* struct pwm_mediatek_chip - struct representing PWM chip
2017-01-23 21:34:37 +03:00
* @ chip : linux PWM chip representation
* @ regs : base address of PWM chip
2019-09-20 01:49:04 +03:00
* @ clk_top : the top clock generator
* @ clk_main : the clock used by PWM core
* @ clk_pwms : the clock used by each PWM channel
* @ clk_freq : the fix clock frequency of legacy MIPS SoC
2020-06-29 15:47:51 +03:00
* @ soc : pointer to chip ' s platform data
2017-01-23 21:34:37 +03:00
*/
2019-09-20 01:49:05 +03:00
struct pwm_mediatek_chip {
2017-01-23 21:34:37 +03:00
struct pwm_chip chip ;
void __iomem * regs ;
2019-09-20 01:49:04 +03:00
struct clk * clk_top ;
struct clk * clk_main ;
struct clk * * clk_pwms ;
2019-09-20 01:49:05 +03:00
const struct pwm_mediatek_of_data * soc ;
2017-01-23 21:34:37 +03:00
} ;
2019-09-20 01:49:05 +03:00
static const unsigned int pwm_mediatek_reg_offset [ ] = {
2017-10-25 13:11:01 +03:00
0x0010 , 0x0050 , 0x0090 , 0x00d0 , 0x0110 , 0x0150 , 0x0190 , 0x0220
} ;
2019-09-20 01:49:05 +03:00
static inline struct pwm_mediatek_chip *
to_pwm_mediatek_chip ( struct pwm_chip * chip )
2017-01-23 21:34:37 +03:00
{
2019-09-20 01:49:05 +03:00
return container_of ( chip , struct pwm_mediatek_chip , chip ) ;
2017-01-23 21:34:37 +03:00
}
2019-09-20 01:49:05 +03:00
static int pwm_mediatek_clk_enable ( struct pwm_chip * chip ,
struct pwm_device * pwm )
2017-06-30 09:05:18 +03:00
{
2019-09-20 01:49:05 +03:00
struct pwm_mediatek_chip * pc = to_pwm_mediatek_chip ( chip ) ;
2017-06-30 09:05:18 +03:00
int ret ;
2019-09-20 01:49:04 +03:00
ret = clk_prepare_enable ( pc - > clk_top ) ;
2017-06-30 09:05:18 +03:00
if ( ret < 0 )
return ret ;
2019-09-20 01:49:04 +03:00
ret = clk_prepare_enable ( pc - > clk_main ) ;
2017-06-30 09:05:18 +03:00
if ( ret < 0 )
goto disable_clk_top ;
2019-09-20 01:49:04 +03:00
ret = clk_prepare_enable ( pc - > clk_pwms [ pwm - > hwpwm ] ) ;
2017-06-30 09:05:18 +03:00
if ( ret < 0 )
goto disable_clk_main ;
return 0 ;
disable_clk_main :
2019-09-20 01:49:04 +03:00
clk_disable_unprepare ( pc - > clk_main ) ;
2017-06-30 09:05:18 +03:00
disable_clk_top :
2019-09-20 01:49:04 +03:00
clk_disable_unprepare ( pc - > clk_top ) ;
2017-06-30 09:05:18 +03:00
return ret ;
}
2019-09-20 01:49:05 +03:00
static void pwm_mediatek_clk_disable ( struct pwm_chip * chip ,
struct pwm_device * pwm )
2017-06-30 09:05:18 +03:00
{
2019-09-20 01:49:05 +03:00
struct pwm_mediatek_chip * pc = to_pwm_mediatek_chip ( chip ) ;
2017-06-30 09:05:18 +03:00
2019-09-20 01:49:04 +03:00
clk_disable_unprepare ( pc - > clk_pwms [ pwm - > hwpwm ] ) ;
clk_disable_unprepare ( pc - > clk_main ) ;
clk_disable_unprepare ( pc - > clk_top ) ;
2017-06-30 09:05:18 +03:00
}
2019-09-20 01:49:05 +03:00
static inline void pwm_mediatek_writel ( struct pwm_mediatek_chip * chip ,
unsigned int num , unsigned int offset ,
u32 value )
2017-01-23 21:34:37 +03:00
{
2019-09-20 01:49:05 +03:00
writel ( value , chip - > regs + pwm_mediatek_reg_offset [ num ] + offset ) ;
2017-01-23 21:34:37 +03:00
}
2019-09-20 01:49:05 +03:00
static int pwm_mediatek_config ( struct pwm_chip * chip , struct pwm_device * pwm ,
int duty_ns , int period_ns )
2017-01-23 21:34:37 +03:00
{
2019-09-20 01:49:05 +03:00
struct pwm_mediatek_chip * pc = to_pwm_mediatek_chip ( chip ) ;
2018-03-02 11:49:14 +03:00
u32 clkdiv = 0 , cnt_period , cnt_duty , reg_width = PWMDWIDTH ,
2018-03-01 11:19:12 +03:00
reg_thres = PWMTHRES ;
2018-03-02 11:49:14 +03:00
u64 resolution ;
2017-06-30 09:05:18 +03:00
int ret ;
2019-09-20 01:49:05 +03:00
ret = pwm_mediatek_clk_enable ( chip , pwm ) ;
2017-06-30 09:05:18 +03:00
if ( ret < 0 )
return ret ;
2017-01-23 21:34:37 +03:00
2020-10-19 17:07:02 +03:00
/* Make sure we use the bus clock and not the 26MHz clock */
if ( pc - > soc - > has_ck_26m_sel )
writel ( 0 , pc - > regs + PWM_CK_26M_SEL ) ;
2018-03-02 11:49:14 +03:00
/* Using resolution in picosecond gets accuracy higher */
resolution = ( u64 ) NSEC_PER_SEC * 1000 ;
2019-09-20 01:49:05 +03:00
do_div ( resolution , clk_get_rate ( pc - > clk_pwms [ pwm - > hwpwm ] ) ) ;
2017-01-23 21:34:37 +03:00
2018-03-02 11:49:14 +03:00
cnt_period = DIV_ROUND_CLOSEST_ULL ( ( u64 ) period_ns * 1000 , resolution ) ;
while ( cnt_period > 8191 ) {
2017-01-23 21:34:37 +03:00
resolution * = 2 ;
clkdiv + + ;
2018-03-02 11:49:14 +03:00
cnt_period = DIV_ROUND_CLOSEST_ULL ( ( u64 ) period_ns * 1000 ,
resolution ) ;
2017-01-23 21:34:37 +03:00
}
2017-06-30 09:05:20 +03:00
if ( clkdiv > PWM_CLK_DIV_MAX ) {
2019-09-20 01:49:05 +03:00
pwm_mediatek_clk_disable ( chip , pwm ) ;
2017-06-30 09:05:20 +03:00
dev_err ( chip - > dev , " period %d not supported \n " , period_ns ) ;
2017-01-23 21:34:37 +03:00
return - EINVAL ;
2017-06-30 09:05:20 +03:00
}
2017-01-23 21:34:37 +03:00
2018-03-01 11:19:12 +03:00
if ( pc - > soc - > pwm45_fixup & & pwm - > hwpwm > 2 ) {
/*
* PWM [ 4 , 5 ] has distinct offset for PWMDWIDTH and PWMTHRES
* from the other PWMs on MT7623 .
*/
reg_width = PWM45DWIDTH_FIXUP ;
reg_thres = PWM45THRES_FIXUP ;
}
2018-03-02 11:49:14 +03:00
cnt_duty = DIV_ROUND_CLOSEST_ULL ( ( u64 ) duty_ns * 1000 , resolution ) ;
2019-09-20 01:49:05 +03:00
pwm_mediatek_writel ( pc , pwm - > hwpwm , PWMCON , BIT ( 15 ) | clkdiv ) ;
pwm_mediatek_writel ( pc , pwm - > hwpwm , reg_width , cnt_period ) ;
pwm_mediatek_writel ( pc , pwm - > hwpwm , reg_thres , cnt_duty ) ;
2017-01-23 21:34:37 +03:00
2019-09-20 01:49:05 +03:00
pwm_mediatek_clk_disable ( chip , pwm ) ;
2017-06-30 09:05:18 +03:00
2017-01-23 21:34:37 +03:00
return 0 ;
}
2019-09-20 01:49:05 +03:00
static int pwm_mediatek_enable ( struct pwm_chip * chip , struct pwm_device * pwm )
2017-01-23 21:34:37 +03:00
{
2019-09-20 01:49:05 +03:00
struct pwm_mediatek_chip * pc = to_pwm_mediatek_chip ( chip ) ;
2017-01-23 21:34:37 +03:00
u32 value ;
int ret ;
2019-09-20 01:49:05 +03:00
ret = pwm_mediatek_clk_enable ( chip , pwm ) ;
2017-01-23 21:34:37 +03:00
if ( ret < 0 )
return ret ;
value = readl ( pc - > regs ) ;
value | = BIT ( pwm - > hwpwm ) ;
writel ( value , pc - > regs ) ;
return 0 ;
}
2019-09-20 01:49:05 +03:00
static void pwm_mediatek_disable ( struct pwm_chip * chip , struct pwm_device * pwm )
2017-01-23 21:34:37 +03:00
{
2019-09-20 01:49:05 +03:00
struct pwm_mediatek_chip * pc = to_pwm_mediatek_chip ( chip ) ;
2017-01-23 21:34:37 +03:00
u32 value ;
value = readl ( pc - > regs ) ;
value & = ~ BIT ( pwm - > hwpwm ) ;
writel ( value , pc - > regs ) ;
2019-09-20 01:49:05 +03:00
pwm_mediatek_clk_disable ( chip , pwm ) ;
2017-01-23 21:34:37 +03:00
}
2019-09-20 01:49:05 +03:00
static const struct pwm_ops pwm_mediatek_ops = {
. config = pwm_mediatek_config ,
. enable = pwm_mediatek_enable ,
. disable = pwm_mediatek_disable ,
2017-01-23 21:34:37 +03:00
. owner = THIS_MODULE ,
} ;
2019-09-20 01:49:05 +03:00
static int pwm_mediatek_probe ( struct platform_device * pdev )
2017-01-23 21:34:37 +03:00
{
2019-09-20 01:49:05 +03:00
struct pwm_mediatek_chip * pc ;
2017-01-23 21:34:37 +03:00
unsigned int i ;
int ret ;
pc = devm_kzalloc ( & pdev - > dev , sizeof ( * pc ) , GFP_KERNEL ) ;
if ( ! pc )
return - ENOMEM ;
2019-09-20 01:49:02 +03:00
pc - > soc = of_device_get_match_data ( & pdev - > dev ) ;
2017-10-25 13:11:01 +03:00
2019-12-29 11:05:45 +03:00
pc - > regs = devm_platform_ioremap_resource ( pdev , 0 ) ;
2017-01-23 21:34:37 +03:00
if ( IS_ERR ( pc - > regs ) )
return PTR_ERR ( pc - > regs ) ;
2019-09-20 01:49:04 +03:00
pc - > clk_pwms = devm_kcalloc ( & pdev - > dev , pc - > soc - > num_pwms ,
sizeof ( * pc - > clk_pwms ) , GFP_KERNEL ) ;
if ( ! pc - > clk_pwms )
return - ENOMEM ;
pc - > clk_top = devm_clk_get ( & pdev - > dev , " top " ) ;
if ( IS_ERR ( pc - > clk_top ) ) {
dev_err ( & pdev - > dev , " clock: top fail: %ld \n " ,
PTR_ERR ( pc - > clk_top ) ) ;
return PTR_ERR ( pc - > clk_top ) ;
}
pc - > clk_main = devm_clk_get ( & pdev - > dev , " main " ) ;
if ( IS_ERR ( pc - > clk_main ) ) {
dev_err ( & pdev - > dev , " clock: main fail: %ld \n " ,
PTR_ERR ( pc - > clk_main ) ) ;
return PTR_ERR ( pc - > clk_main ) ;
}
for ( i = 0 ; i < pc - > soc - > num_pwms ; i + + ) {
char name [ 8 ] ;
snprintf ( name , sizeof ( name ) , " pwm%d " , i + 1 ) ;
pc - > clk_pwms [ i ] = devm_clk_get ( & pdev - > dev , name ) ;
if ( IS_ERR ( pc - > clk_pwms [ i ] ) ) {
2017-10-25 13:11:01 +03:00
dev_err ( & pdev - > dev , " clock: %s fail: %ld \n " ,
2019-09-20 01:49:04 +03:00
name , PTR_ERR ( pc - > clk_pwms [ i ] ) ) ;
return PTR_ERR ( pc - > clk_pwms [ i ] ) ;
2017-10-25 13:11:01 +03:00
}
2017-01-23 21:34:37 +03:00
}
pc - > chip . dev = & pdev - > dev ;
2019-09-20 01:49:05 +03:00
pc - > chip . ops = & pwm_mediatek_ops ;
2019-09-20 01:49:02 +03:00
pc - > chip . npwm = pc - > soc - > num_pwms ;
2017-01-23 21:34:37 +03:00
2021-07-07 19:28:11 +03:00
ret = devm_pwmchip_add ( & pdev - > dev , & pc - > chip ) ;
2017-01-23 21:34:37 +03:00
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " pwmchip_add() failed: %d \n " , ret ) ;
2017-06-30 09:05:18 +03:00
return ret ;
2017-01-23 21:34:37 +03:00
}
return 0 ;
}
2019-09-20 01:49:05 +03:00
static const struct pwm_mediatek_of_data mt2712_pwm_data = {
2017-10-25 13:11:01 +03:00
. num_pwms = 8 ,
2018-03-01 11:19:12 +03:00
. pwm45_fixup = false ,
2020-10-19 17:07:02 +03:00
. has_ck_26m_sel = false ,
2017-10-25 13:11:01 +03:00
} ;
2019-09-20 01:49:05 +03:00
static const struct pwm_mediatek_of_data mt7622_pwm_data = {
2017-10-25 13:11:01 +03:00
. num_pwms = 6 ,
2018-03-01 11:19:12 +03:00
. pwm45_fixup = false ,
2020-10-19 17:07:02 +03:00
. has_ck_26m_sel = false ,
2017-10-25 13:11:01 +03:00
} ;
2019-09-20 01:49:05 +03:00
static const struct pwm_mediatek_of_data mt7623_pwm_data = {
2017-10-25 13:11:01 +03:00
. num_pwms = 5 ,
2018-03-01 11:19:12 +03:00
. pwm45_fixup = true ,
2020-10-19 17:07:02 +03:00
. has_ck_26m_sel = false ,
2018-07-25 12:52:09 +03:00
} ;
2019-09-20 01:49:05 +03:00
static const struct pwm_mediatek_of_data mt7628_pwm_data = {
2018-07-25 12:52:09 +03:00
. num_pwms = 4 ,
. pwm45_fixup = true ,
2020-10-19 17:07:02 +03:00
. has_ck_26m_sel = false ,
2017-10-25 13:11:01 +03:00
} ;
2019-09-25 17:32:33 +03:00
static const struct pwm_mediatek_of_data mt7629_pwm_data = {
. num_pwms = 1 ,
. pwm45_fixup = false ,
2020-10-19 17:07:02 +03:00
. has_ck_26m_sel = false ,
2019-09-25 17:32:33 +03:00
} ;
2020-10-19 17:07:03 +03:00
static const struct pwm_mediatek_of_data mt8183_pwm_data = {
. num_pwms = 4 ,
. pwm45_fixup = false ,
. has_ck_26m_sel = true ,
} ;
2019-09-20 01:49:05 +03:00
static const struct pwm_mediatek_of_data mt8516_pwm_data = {
2019-08-05 15:58:48 +03:00
. num_pwms = 5 ,
. pwm45_fixup = false ,
2020-10-19 17:07:02 +03:00
. has_ck_26m_sel = true ,
2019-08-05 15:58:48 +03:00
} ;
2019-09-20 01:49:05 +03:00
static const struct of_device_id pwm_mediatek_of_match [ ] = {
2017-10-25 13:11:01 +03:00
{ . compatible = " mediatek,mt2712-pwm " , . data = & mt2712_pwm_data } ,
{ . compatible = " mediatek,mt7622-pwm " , . data = & mt7622_pwm_data } ,
{ . compatible = " mediatek,mt7623-pwm " , . data = & mt7623_pwm_data } ,
2018-07-25 12:52:09 +03:00
{ . compatible = " mediatek,mt7628-pwm " , . data = & mt7628_pwm_data } ,
2019-09-25 17:32:33 +03:00
{ . compatible = " mediatek,mt7629-pwm " , . data = & mt7629_pwm_data } ,
2020-10-19 17:07:03 +03:00
{ . compatible = " mediatek,mt8183-pwm " , . data = & mt8183_pwm_data } ,
2019-08-05 15:58:48 +03:00
{ . compatible = " mediatek,mt8516-pwm " , . data = & mt8516_pwm_data } ,
2017-10-25 13:11:01 +03:00
{ } ,
2017-01-23 21:34:37 +03:00
} ;
2019-09-20 01:49:05 +03:00
MODULE_DEVICE_TABLE ( of , pwm_mediatek_of_match ) ;
2017-01-23 21:34:37 +03:00
2019-09-20 01:49:05 +03:00
static struct platform_driver pwm_mediatek_driver = {
2017-01-23 21:34:37 +03:00
. driver = {
2019-09-20 01:49:05 +03:00
. name = " pwm-mediatek " ,
. of_match_table = pwm_mediatek_of_match ,
2017-01-23 21:34:37 +03:00
} ,
2019-09-20 01:49:05 +03:00
. probe = pwm_mediatek_probe ,
2017-01-23 21:34:37 +03:00
} ;
2019-09-20 01:49:05 +03:00
module_platform_driver ( pwm_mediatek_driver ) ;
2017-01-23 21:34:37 +03:00
MODULE_AUTHOR ( " John Crispin <blogic@openwrt.org> " ) ;
2019-09-20 01:49:06 +03:00
MODULE_LICENSE ( " GPL v2 " ) ;