2019-05-27 09:55:21 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2015-08-18 10:27:54 +03:00
/*
* MediaTek display pulse - width - modulation controller driver .
* Copyright ( c ) 2015 MediaTek Inc .
* Author : YH Huang < yh . huang @ mediatek . com >
*/
2021-08-08 16:24:33 +03:00
# include <linux/bitfield.h>
2015-08-18 10:27:54 +03:00
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of.h>
2016-07-11 11:18:08 +03:00
# include <linux/of_device.h>
2015-08-18 10:27:54 +03:00
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/slab.h>
# define DISP_PWM_EN 0x00
# define PWM_CLKDIV_SHIFT 16
# define PWM_CLKDIV_MAX 0x3ff
# define PWM_CLKDIV_MASK (PWM_CLKDIV_MAX << PWM_CLKDIV_SHIFT)
# define PWM_PERIOD_BIT_WIDTH 12
# define PWM_PERIOD_MASK ((1 << PWM_PERIOD_BIT_WIDTH) - 1)
# define PWM_HIGH_WIDTH_SHIFT 16
# define PWM_HIGH_WIDTH_MASK (0x1fff << PWM_HIGH_WIDTH_SHIFT)
2016-07-11 11:18:08 +03:00
struct mtk_pwm_data {
u32 enable_mask ;
unsigned int con0 ;
u32 con0_sel ;
unsigned int con1 ;
bool has_commit ;
unsigned int commit ;
unsigned int commit_mask ;
unsigned int bls_debug ;
u32 bls_debug_mask ;
} ;
2015-08-18 10:27:54 +03:00
struct mtk_disp_pwm {
struct pwm_chip chip ;
2016-07-11 11:18:08 +03:00
const struct mtk_pwm_data * data ;
2015-08-18 10:27:54 +03:00
struct clk * clk_main ;
struct clk * clk_mm ;
void __iomem * base ;
2021-08-08 16:24:31 +03:00
bool enabled ;
2015-08-18 10:27:54 +03:00
} ;
static inline struct mtk_disp_pwm * to_mtk_disp_pwm ( struct pwm_chip * chip )
{
return container_of ( chip , struct mtk_disp_pwm , chip ) ;
}
static void mtk_disp_pwm_update_bits ( struct mtk_disp_pwm * mdp , u32 offset ,
u32 mask , u32 data )
{
void __iomem * address = mdp - > base + offset ;
u32 value ;
value = readl ( address ) ;
value & = ~ mask ;
value | = data ;
writel ( value , address ) ;
}
2021-08-08 16:24:31 +03:00
static int mtk_disp_pwm_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
const struct pwm_state * state )
2015-08-18 10:27:54 +03:00
{
struct mtk_disp_pwm * mdp = to_mtk_disp_pwm ( chip ) ;
u32 clk_div , period , high_width , value ;
u64 div , rate ;
int err ;
2021-08-08 16:24:31 +03:00
if ( state - > polarity ! = PWM_POLARITY_NORMAL )
return - EINVAL ;
if ( ! state - > enabled ) {
mtk_disp_pwm_update_bits ( mdp , DISP_PWM_EN , mdp - > data - > enable_mask ,
0x0 ) ;
if ( mdp - > enabled ) {
clk_disable_unprepare ( mdp - > clk_mm ) ;
clk_disable_unprepare ( mdp - > clk_main ) ;
}
mdp - > enabled = false ;
return 0 ;
2021-08-08 16:24:29 +03:00
}
2021-08-08 16:24:31 +03:00
if ( ! mdp - > enabled ) {
err = clk_prepare_enable ( mdp - > clk_main ) ;
if ( err < 0 ) {
dev_err ( chip - > dev , " Can't enable mdp->clk_main: %pe \n " ,
ERR_PTR ( err ) ) ;
return err ;
}
err = clk_prepare_enable ( mdp - > clk_mm ) ;
if ( err < 0 ) {
dev_err ( chip - > dev , " Can't enable mdp->clk_mm: %pe \n " ,
ERR_PTR ( err ) ) ;
clk_disable_unprepare ( mdp - > clk_main ) ;
return err ;
}
2021-08-08 16:24:29 +03:00
}
2015-08-18 10:27:54 +03:00
/*
* Find period , high_width and clk_div to suit duty_ns and period_ns .
* Calculate proper div value to keep period value in the bound .
*
* period_ns = 10 ^ 9 * ( clk_div + 1 ) * ( period + 1 ) / PWM_CLK_RATE
* duty_ns = 10 ^ 9 * ( clk_div + 1 ) * high_width / PWM_CLK_RATE
*
* period = ( PWM_CLK_RATE * period_ns ) / ( 10 ^ 9 * ( clk_div + 1 ) ) - 1
* high_width = ( PWM_CLK_RATE * duty_ns ) / ( 10 ^ 9 * ( clk_div + 1 ) )
*/
rate = clk_get_rate ( mdp - > clk_main ) ;
2021-08-08 16:24:32 +03:00
clk_div = mul_u64_u64_div_u64 ( state - > period , rate , NSEC_PER_SEC ) > >
2015-08-18 10:27:54 +03:00
PWM_PERIOD_BIT_WIDTH ;
2021-08-08 16:24:29 +03:00
if ( clk_div > PWM_CLKDIV_MAX ) {
2021-08-08 16:24:31 +03:00
if ( ! mdp - > enabled ) {
clk_disable_unprepare ( mdp - > clk_mm ) ;
clk_disable_unprepare ( mdp - > clk_main ) ;
}
2015-08-18 10:27:54 +03:00
return - EINVAL ;
2021-08-08 16:24:29 +03:00
}
2015-08-18 10:27:54 +03:00
div = NSEC_PER_SEC * ( clk_div + 1 ) ;
2021-08-08 16:24:32 +03:00
period = mul_u64_u64_div_u64 ( state - > period , rate , div ) ;
2015-08-18 10:27:54 +03:00
if ( period > 0 )
period - - ;
2021-08-08 16:24:32 +03:00
high_width = mul_u64_u64_div_u64 ( state - > duty_cycle , rate , div ) ;
2015-08-18 10:27:54 +03:00
value = period | ( high_width < < PWM_HIGH_WIDTH_SHIFT ) ;
2016-07-11 11:18:08 +03:00
mtk_disp_pwm_update_bits ( mdp , mdp - > data - > con0 ,
PWM_CLKDIV_MASK ,
2015-08-18 10:27:54 +03:00
clk_div < < PWM_CLKDIV_SHIFT ) ;
2016-07-11 11:18:08 +03:00
mtk_disp_pwm_update_bits ( mdp , mdp - > data - > con1 ,
PWM_PERIOD_MASK | PWM_HIGH_WIDTH_MASK ,
value ) ;
if ( mdp - > data - > has_commit ) {
mtk_disp_pwm_update_bits ( mdp , mdp - > data - > commit ,
mdp - > data - > commit_mask ,
mdp - > data - > commit_mask ) ;
mtk_disp_pwm_update_bits ( mdp , mdp - > data - > commit ,
mdp - > data - > commit_mask ,
0x0 ) ;
2021-08-08 16:24:29 +03:00
} else {
/*
* For MT2701 , disable double buffer before writing register
* and select manual mode and use PWM_PERIOD / PWM_HIGH_WIDTH .
*/
mtk_disp_pwm_update_bits ( mdp , mdp - > data - > bls_debug ,
mdp - > data - > bls_debug_mask ,
mdp - > data - > bls_debug_mask ) ;
mtk_disp_pwm_update_bits ( mdp , mdp - > data - > con0 ,
mdp - > data - > con0_sel ,
mdp - > data - > con0_sel ) ;
2016-07-11 11:18:08 +03:00
}
2015-08-18 10:27:54 +03:00
2016-07-11 11:18:08 +03:00
mtk_disp_pwm_update_bits ( mdp , DISP_PWM_EN , mdp - > data - > enable_mask ,
mdp - > data - > enable_mask ) ;
2021-08-08 16:24:31 +03:00
mdp - > enabled = true ;
2015-08-18 10:27:54 +03:00
return 0 ;
}
2021-08-08 16:24:33 +03:00
static void mtk_disp_pwm_get_state ( struct pwm_chip * chip ,
struct pwm_device * pwm ,
struct pwm_state * state )
{
struct mtk_disp_pwm * mdp = to_mtk_disp_pwm ( chip ) ;
u64 rate , period , high_width ;
u32 clk_div , con0 , con1 ;
int err ;
err = clk_prepare_enable ( mdp - > clk_main ) ;
if ( err < 0 ) {
dev_err ( chip - > dev , " Can't enable mdp->clk_main: %pe \n " , ERR_PTR ( err ) ) ;
return ;
}
err = clk_prepare_enable ( mdp - > clk_mm ) ;
if ( err < 0 ) {
dev_err ( chip - > dev , " Can't enable mdp->clk_mm: %pe \n " , ERR_PTR ( err ) ) ;
clk_disable_unprepare ( mdp - > clk_main ) ;
return ;
}
rate = clk_get_rate ( mdp - > clk_main ) ;
con0 = readl ( mdp - > base + mdp - > data - > con0 ) ;
con1 = readl ( mdp - > base + mdp - > data - > con1 ) ;
state - > enabled = ! ! ( con0 & BIT ( 0 ) ) ;
clk_div = FIELD_GET ( PWM_CLKDIV_MASK , con0 ) ;
period = FIELD_GET ( PWM_PERIOD_MASK , con1 ) ;
/*
* period has 12 bits , clk_div 11 and NSEC_PER_SEC has 30 ,
* so period * ( clk_div + 1 ) * NSEC_PER_SEC doesn ' t overflow .
*/
state - > period = DIV64_U64_ROUND_UP ( period * ( clk_div + 1 ) * NSEC_PER_SEC , rate ) ;
high_width = FIELD_GET ( PWM_HIGH_WIDTH_MASK , con1 ) ;
state - > duty_cycle = DIV64_U64_ROUND_UP ( high_width * ( clk_div + 1 ) * NSEC_PER_SEC ,
rate ) ;
state - > polarity = PWM_POLARITY_NORMAL ;
clk_disable_unprepare ( mdp - > clk_mm ) ;
clk_disable_unprepare ( mdp - > clk_main ) ;
}
2015-08-18 10:27:54 +03:00
static const struct pwm_ops mtk_disp_pwm_ops = {
2021-08-08 16:24:31 +03:00
. apply = mtk_disp_pwm_apply ,
2021-08-08 16:24:33 +03:00
. get_state = mtk_disp_pwm_get_state ,
2015-08-18 10:27:54 +03:00
. owner = THIS_MODULE ,
} ;
static int mtk_disp_pwm_probe ( struct platform_device * pdev )
{
struct mtk_disp_pwm * mdp ;
int ret ;
mdp = devm_kzalloc ( & pdev - > dev , sizeof ( * mdp ) , GFP_KERNEL ) ;
if ( ! mdp )
return - ENOMEM ;
2016-07-11 11:18:08 +03:00
mdp - > data = of_device_get_match_data ( & pdev - > dev ) ;
2019-12-29 11:06:04 +03:00
mdp - > base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2015-08-18 10:27:54 +03:00
if ( IS_ERR ( mdp - > base ) )
return PTR_ERR ( mdp - > base ) ;
mdp - > clk_main = devm_clk_get ( & pdev - > dev , " main " ) ;
if ( IS_ERR ( mdp - > clk_main ) )
return PTR_ERR ( mdp - > clk_main ) ;
mdp - > clk_mm = devm_clk_get ( & pdev - > dev , " mm " ) ;
if ( IS_ERR ( mdp - > clk_mm ) )
return PTR_ERR ( mdp - > clk_mm ) ;
mdp - > chip . dev = & pdev - > dev ;
mdp - > chip . ops = & mtk_disp_pwm_ops ;
mdp - > chip . npwm = 1 ;
ret = pwmchip_add ( & mdp - > chip ) ;
if ( ret < 0 ) {
2021-08-08 16:24:29 +03:00
dev_err ( & pdev - > dev , " pwmchip_add() failed: %pe \n " , ERR_PTR ( ret ) ) ;
return ret ;
2015-08-18 10:27:54 +03:00
}
platform_set_drvdata ( pdev , mdp ) ;
return 0 ;
}
static int mtk_disp_pwm_remove ( struct platform_device * pdev )
{
struct mtk_disp_pwm * mdp = platform_get_drvdata ( pdev ) ;
2021-07-07 19:28:27 +03:00
pwmchip_remove ( & mdp - > chip ) ;
2015-08-18 10:27:54 +03:00
2021-07-07 19:28:27 +03:00
return 0 ;
2015-08-18 10:27:54 +03:00
}
2016-07-11 11:18:08 +03:00
static const struct mtk_pwm_data mt2701_pwm_data = {
. enable_mask = BIT ( 16 ) ,
. con0 = 0xa8 ,
. con0_sel = 0x2 ,
. con1 = 0xac ,
. has_commit = false ,
. bls_debug = 0xb0 ,
. bls_debug_mask = 0x3 ,
} ;
static const struct mtk_pwm_data mt8173_pwm_data = {
. enable_mask = BIT ( 0 ) ,
. con0 = 0x10 ,
. con0_sel = 0x0 ,
. con1 = 0x14 ,
. has_commit = true ,
. commit = 0x8 ,
. commit_mask = 0x1 ,
} ;
2019-01-22 12:02:43 +03:00
static const struct mtk_pwm_data mt8183_pwm_data = {
. enable_mask = BIT ( 0 ) ,
. con0 = 0x18 ,
. con0_sel = 0x0 ,
. con1 = 0x1c ,
. has_commit = false ,
. bls_debug = 0x80 ,
. bls_debug_mask = 0x3 ,
} ;
2015-08-18 10:27:54 +03:00
static const struct of_device_id mtk_disp_pwm_of_match [ ] = {
2016-07-11 11:18:08 +03:00
{ . compatible = " mediatek,mt2701-disp-pwm " , . data = & mt2701_pwm_data } ,
{ . compatible = " mediatek,mt6595-disp-pwm " , . data = & mt8173_pwm_data } ,
{ . compatible = " mediatek,mt8173-disp-pwm " , . data = & mt8173_pwm_data } ,
2019-01-22 12:02:43 +03:00
{ . compatible = " mediatek,mt8183-disp-pwm " , . data = & mt8183_pwm_data } ,
2015-08-18 10:27:54 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , mtk_disp_pwm_of_match ) ;
static struct platform_driver mtk_disp_pwm_driver = {
. driver = {
. name = " mediatek-disp-pwm " ,
. of_match_table = mtk_disp_pwm_of_match ,
} ,
. probe = mtk_disp_pwm_probe ,
. remove = mtk_disp_pwm_remove ,
} ;
module_platform_driver ( mtk_disp_pwm_driver ) ;
MODULE_AUTHOR ( " YH Huang <yh.huang@mediatek.com> " ) ;
MODULE_DESCRIPTION ( " MediaTek SoC display PWM driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;