2017-01-23 21:34:37 +03:00
/*
* Mediatek Pulse Width Modulator driver
*
* 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
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# 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
2017-01-23 21:34:37 +03:00
2017-06-30 09:05:20 +03:00
# define PWM_CLK_DIV_MAX 7
2017-01-23 21:34:37 +03:00
enum {
MTK_CLK_MAIN = 0 ,
MTK_CLK_TOP ,
MTK_CLK_PWM1 ,
MTK_CLK_PWM2 ,
MTK_CLK_PWM3 ,
MTK_CLK_PWM4 ,
MTK_CLK_PWM5 ,
2017-10-25 13:11:01 +03:00
MTK_CLK_PWM6 ,
MTK_CLK_PWM7 ,
MTK_CLK_PWM8 ,
2017-01-23 21:34:37 +03:00
MTK_CLK_MAX ,
} ;
2017-10-25 13:11:01 +03:00
static const char * const mtk_pwm_clk_name [ MTK_CLK_MAX ] = {
" main " , " top " , " pwm1 " , " pwm2 " , " pwm3 " , " pwm4 " , " pwm5 " , " pwm6 " , " pwm7 " ,
" pwm8 "
} ;
struct mtk_pwm_platform_data {
unsigned int num_pwms ;
2018-03-01 11:19:12 +03:00
bool pwm45_fixup ;
2018-07-25 12:52:09 +03:00
bool has_clks ;
2017-01-23 21:34:37 +03:00
} ;
/**
* struct mtk_pwm_chip - struct representing PWM chip
* @ chip : linux PWM chip representation
* @ regs : base address of PWM chip
* @ clks : list of clocks
*/
struct mtk_pwm_chip {
struct pwm_chip chip ;
void __iomem * regs ;
struct clk * clks [ MTK_CLK_MAX ] ;
2018-03-01 11:19:12 +03:00
const struct mtk_pwm_platform_data * soc ;
2017-01-23 21:34:37 +03:00
} ;
2017-10-25 13:11:01 +03:00
static const unsigned int mtk_pwm_reg_offset [ ] = {
0x0010 , 0x0050 , 0x0090 , 0x00d0 , 0x0110 , 0x0150 , 0x0190 , 0x0220
} ;
2017-01-23 21:34:37 +03:00
static inline struct mtk_pwm_chip * to_mtk_pwm_chip ( struct pwm_chip * chip )
{
return container_of ( chip , struct mtk_pwm_chip , chip ) ;
}
2017-06-30 09:05:18 +03:00
static int mtk_pwm_clk_enable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct mtk_pwm_chip * pc = to_mtk_pwm_chip ( chip ) ;
int ret ;
2018-07-25 12:52:09 +03:00
if ( ! pc - > soc - > has_clks )
return 0 ;
2017-06-30 09:05:18 +03:00
ret = clk_prepare_enable ( pc - > clks [ MTK_CLK_TOP ] ) ;
if ( ret < 0 )
return ret ;
ret = clk_prepare_enable ( pc - > clks [ MTK_CLK_MAIN ] ) ;
if ( ret < 0 )
goto disable_clk_top ;
ret = clk_prepare_enable ( pc - > clks [ MTK_CLK_PWM1 + pwm - > hwpwm ] ) ;
if ( ret < 0 )
goto disable_clk_main ;
return 0 ;
disable_clk_main :
clk_disable_unprepare ( pc - > clks [ MTK_CLK_MAIN ] ) ;
disable_clk_top :
clk_disable_unprepare ( pc - > clks [ MTK_CLK_TOP ] ) ;
return ret ;
}
static void mtk_pwm_clk_disable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct mtk_pwm_chip * pc = to_mtk_pwm_chip ( chip ) ;
2018-07-25 12:52:09 +03:00
if ( ! pc - > soc - > has_clks )
return ;
2017-06-30 09:05:18 +03:00
clk_disable_unprepare ( pc - > clks [ MTK_CLK_PWM1 + pwm - > hwpwm ] ) ;
clk_disable_unprepare ( pc - > clks [ MTK_CLK_MAIN ] ) ;
clk_disable_unprepare ( pc - > clks [ MTK_CLK_TOP ] ) ;
}
2017-01-23 21:34:37 +03:00
static inline u32 mtk_pwm_readl ( struct mtk_pwm_chip * chip , unsigned int num ,
unsigned int offset )
{
2017-10-25 13:11:01 +03:00
return readl ( chip - > regs + mtk_pwm_reg_offset [ num ] + offset ) ;
2017-01-23 21:34:37 +03:00
}
static inline void mtk_pwm_writel ( struct mtk_pwm_chip * chip ,
unsigned int num , unsigned int offset ,
u32 value )
{
2017-10-25 13:11:01 +03:00
writel ( value , chip - > regs + mtk_pwm_reg_offset [ num ] + offset ) ;
2017-01-23 21:34:37 +03:00
}
static int mtk_pwm_config ( struct pwm_chip * chip , struct pwm_device * pwm ,
int duty_ns , int period_ns )
{
struct mtk_pwm_chip * pc = to_mtk_pwm_chip ( chip ) ;
struct clk * clk = pc - > clks [ MTK_CLK_PWM1 + pwm - > hwpwm ] ;
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 ;
ret = mtk_pwm_clk_enable ( chip , pwm ) ;
if ( ret < 0 )
return ret ;
2017-01-23 21:34:37 +03:00
2018-03-02 11:49:14 +03:00
/* Using resolution in picosecond gets accuracy higher */
resolution = ( u64 ) NSEC_PER_SEC * 1000 ;
do_div ( resolution , clk_get_rate ( clk ) ) ;
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 ) {
mtk_pwm_clk_disable ( chip , pwm ) ;
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 ) ;
2017-06-30 09:05:17 +03:00
mtk_pwm_writel ( pc , pwm - > hwpwm , PWMCON , BIT ( 15 ) | clkdiv ) ;
2018-03-02 11:49:14 +03:00
mtk_pwm_writel ( pc , pwm - > hwpwm , reg_width , cnt_period ) ;
mtk_pwm_writel ( pc , pwm - > hwpwm , reg_thres , cnt_duty ) ;
2017-01-23 21:34:37 +03:00
2017-06-30 09:05:18 +03:00
mtk_pwm_clk_disable ( chip , pwm ) ;
2017-01-23 21:34:37 +03:00
return 0 ;
}
static int mtk_pwm_enable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct mtk_pwm_chip * pc = to_mtk_pwm_chip ( chip ) ;
u32 value ;
int ret ;
2017-06-30 09:05:18 +03:00
ret = mtk_pwm_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 ;
}
static void mtk_pwm_disable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
struct mtk_pwm_chip * pc = to_mtk_pwm_chip ( chip ) ;
u32 value ;
value = readl ( pc - > regs ) ;
value & = ~ BIT ( pwm - > hwpwm ) ;
writel ( value , pc - > regs ) ;
2017-06-30 09:05:18 +03:00
mtk_pwm_clk_disable ( chip , pwm ) ;
2017-01-23 21:34:37 +03:00
}
static const struct pwm_ops mtk_pwm_ops = {
. config = mtk_pwm_config ,
. enable = mtk_pwm_enable ,
. disable = mtk_pwm_disable ,
. owner = THIS_MODULE ,
} ;
static int mtk_pwm_probe ( struct platform_device * pdev )
{
2017-10-25 13:11:01 +03:00
const struct mtk_pwm_platform_data * data ;
2017-01-23 21:34:37 +03:00
struct mtk_pwm_chip * pc ;
struct resource * res ;
unsigned int i ;
int ret ;
pc = devm_kzalloc ( & pdev - > dev , sizeof ( * pc ) , GFP_KERNEL ) ;
if ( ! pc )
return - ENOMEM ;
2017-10-25 13:11:01 +03:00
data = of_device_get_match_data ( & pdev - > dev ) ;
if ( data = = NULL )
return - EINVAL ;
2018-03-01 11:19:12 +03:00
pc - > soc = data ;
2017-10-25 13:11:01 +03:00
2017-01-23 21:34:37 +03:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
pc - > regs = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( pc - > regs ) )
return PTR_ERR ( pc - > regs ) ;
2018-07-25 12:52:09 +03:00
for ( i = 0 ; i < data - > num_pwms + 2 & & pc - > soc - > has_clks ; i + + ) {
2017-01-23 21:34:37 +03:00
pc - > clks [ i ] = devm_clk_get ( & pdev - > dev , mtk_pwm_clk_name [ i ] ) ;
2017-10-25 13:11:01 +03:00
if ( IS_ERR ( pc - > clks [ i ] ) ) {
dev_err ( & pdev - > dev , " clock: %s fail: %ld \n " ,
mtk_pwm_clk_name [ i ] , PTR_ERR ( pc - > clks [ i ] ) ) ;
2017-01-23 21:34:37 +03:00
return PTR_ERR ( pc - > clks [ i ] ) ;
2017-10-25 13:11:01 +03:00
}
2017-01-23 21:34:37 +03:00
}
platform_set_drvdata ( pdev , pc ) ;
pc - > chip . dev = & pdev - > dev ;
pc - > chip . ops = & mtk_pwm_ops ;
pc - > chip . base = - 1 ;
2017-10-25 13:11:01 +03:00
pc - > chip . npwm = data - > num_pwms ;
2017-01-23 21:34:37 +03:00
ret = pwmchip_add ( & pc - > chip ) ;
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 ;
}
static int mtk_pwm_remove ( struct platform_device * pdev )
{
struct mtk_pwm_chip * pc = platform_get_drvdata ( pdev ) ;
return pwmchip_remove ( & pc - > chip ) ;
}
2017-10-25 13:11:01 +03:00
static const struct mtk_pwm_platform_data mt2712_pwm_data = {
. num_pwms = 8 ,
2018-03-01 11:19:12 +03:00
. pwm45_fixup = false ,
2018-07-25 12:52:09 +03:00
. has_clks = true ,
2017-10-25 13:11:01 +03:00
} ;
static const struct mtk_pwm_platform_data mt7622_pwm_data = {
. num_pwms = 6 ,
2018-03-01 11:19:12 +03:00
. pwm45_fixup = false ,
2018-07-25 12:52:09 +03:00
. has_clks = true ,
2017-10-25 13:11:01 +03:00
} ;
static const struct mtk_pwm_platform_data mt7623_pwm_data = {
. num_pwms = 5 ,
2018-03-01 11:19:12 +03:00
. pwm45_fixup = true ,
2018-07-25 12:52:09 +03:00
. has_clks = true ,
} ;
static const struct mtk_pwm_platform_data mt7628_pwm_data = {
. num_pwms = 4 ,
. pwm45_fixup = true ,
. has_clks = false ,
2017-10-25 13:11:01 +03:00
} ;
2017-01-23 21:34:37 +03:00
static const struct of_device_id mtk_pwm_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 } ,
2017-10-25 13:11:01 +03:00
{ } ,
2017-01-23 21:34:37 +03:00
} ;
MODULE_DEVICE_TABLE ( of , mtk_pwm_of_match ) ;
static struct platform_driver mtk_pwm_driver = {
. driver = {
. name = " mtk-pwm " ,
. of_match_table = mtk_pwm_of_match ,
} ,
. probe = mtk_pwm_probe ,
. remove = mtk_pwm_remove ,
} ;
module_platform_driver ( mtk_pwm_driver ) ;
MODULE_AUTHOR ( " John Crispin <blogic@openwrt.org> " ) ;
MODULE_LICENSE ( " GPL " ) ;