2019-05-27 09:55:00 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2012-08-22 12:01:24 +04:00
/*
* Copyright ( C ) 2010 , Lars - Peter Clausen < lars @ metafoo . de >
* JZ4740 platform PWM support
2019-07-30 15:32:29 +03:00
*
* Limitations :
* - The . apply callback doesn ' t complete the currently running period before
* reconfiguring the hardware .
2012-08-22 12:01:24 +04:00
*/
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/gpio.h>
# include <linux/kernel.h>
2020-03-23 17:24:20 +03:00
# include <linux/mfd/ingenic-tcu.h>
# include <linux/mfd/syscon.h>
2012-08-22 12:01:24 +04:00
# include <linux/module.h>
2018-01-06 19:58:42 +03:00
# include <linux/of_device.h>
2012-08-22 12:01:24 +04:00
# include <linux/platform_device.h>
# include <linux/pwm.h>
2020-03-23 17:24:20 +03:00
# include <linux/regmap.h>
2012-08-22 12:01:24 +04:00
2020-05-27 14:52:25 +03:00
struct soc_info {
unsigned int num_pwms ;
} ;
2012-08-22 12:01:24 +04:00
struct jz4740_pwm_chip {
struct pwm_chip chip ;
2020-03-23 17:24:20 +03:00
struct regmap * map ;
2012-08-22 12:01:24 +04:00
} ;
static inline struct jz4740_pwm_chip * to_jz4740 ( struct pwm_chip * chip )
{
return container_of ( chip , struct jz4740_pwm_chip , chip ) ;
}
2020-03-23 17:24:21 +03:00
static bool jz4740_pwm_can_use_chn ( struct jz4740_pwm_chip * jz ,
unsigned int channel )
{
/* Enable all TCU channels for PWM use by default except channels 0/1 */
2020-05-27 14:52:25 +03:00
u32 pwm_channels_mask = GENMASK ( jz - > chip . npwm - 1 , 2 ) ;
2020-03-23 17:24:21 +03:00
device_property_read_u32 ( jz - > chip . dev - > parent ,
" ingenic,pwm-channels-mask " ,
& pwm_channels_mask ) ;
return ! ! ( pwm_channels_mask & BIT ( channel ) ) ;
}
2012-08-22 12:01:24 +04:00
static int jz4740_pwm_request ( struct pwm_chip * chip , struct pwm_device * pwm )
{
2020-03-23 17:24:18 +03:00
struct jz4740_pwm_chip * jz = to_jz4740 ( chip ) ;
struct clk * clk ;
char name [ 16 ] ;
int err ;
2020-03-23 17:24:21 +03:00
if ( ! jz4740_pwm_can_use_chn ( jz , pwm - > hwpwm ) )
2012-08-22 12:01:24 +04:00
return - EBUSY ;
2020-03-23 17:24:18 +03:00
snprintf ( name , sizeof ( name ) , " timer%u " , pwm - > hwpwm ) ;
clk = clk_get ( chip - > dev , name ) ;
2020-08-26 17:47:43 +03:00
if ( IS_ERR ( clk ) )
return dev_err_probe ( chip - > dev , PTR_ERR ( clk ) ,
" Failed to get clock \n " ) ;
2020-03-23 17:24:18 +03:00
err = clk_prepare_enable ( clk ) ;
if ( err < 0 ) {
clk_put ( clk ) ;
return err ;
}
pwm_set_chip_data ( pwm , clk ) ;
2012-08-22 12:01:24 +04:00
return 0 ;
}
static void jz4740_pwm_free ( struct pwm_chip * chip , struct pwm_device * pwm )
{
2020-03-23 17:24:18 +03:00
struct clk * clk = pwm_get_chip_data ( pwm ) ;
2012-08-22 12:01:24 +04:00
2020-03-23 17:24:18 +03:00
clk_disable_unprepare ( clk ) ;
clk_put ( clk ) ;
2012-08-22 12:01:24 +04:00
}
static int jz4740_pwm_enable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
2020-03-23 17:24:20 +03:00
struct jz4740_pwm_chip * jz = to_jz4740 ( chip ) ;
/* Enable PWM output */
regmap_update_bits ( jz - > map , TCU_REG_TCSRc ( pwm - > hwpwm ) ,
TCU_TCSR_PWM_EN , TCU_TCSR_PWM_EN ) ;
2012-08-22 12:01:24 +04:00
2020-03-23 17:24:20 +03:00
/* Start counter */
regmap_write ( jz - > map , TCU_REG_TESR , BIT ( pwm - > hwpwm ) ) ;
2012-08-22 12:01:24 +04:00
return 0 ;
}
static void jz4740_pwm_disable ( struct pwm_chip * chip , struct pwm_device * pwm )
{
2020-03-23 17:24:20 +03:00
struct jz4740_pwm_chip * jz = to_jz4740 ( chip ) ;
2012-08-22 12:01:24 +04:00
2019-06-07 18:44:09 +03:00
/*
* Set duty > period . This trick allows the TCU channels in TCU2 mode to
* properly return to their init level .
*/
2020-03-23 17:24:20 +03:00
regmap_write ( jz - > map , TCU_REG_TDHRc ( pwm - > hwpwm ) , 0xffff ) ;
regmap_write ( jz - > map , TCU_REG_TDFRc ( pwm - > hwpwm ) , 0x0 ) ;
2019-06-07 18:44:09 +03:00
/*
* Disable PWM output .
2018-01-06 19:58:40 +03:00
* In TCU2 mode ( channel 1 / 2 on JZ4750 + ) , this must be done before the
* counter is stopped , while in TCU1 mode the order does not matter .
*/
2020-03-23 17:24:20 +03:00
regmap_update_bits ( jz - > map , TCU_REG_TCSRc ( pwm - > hwpwm ) ,
TCU_TCSR_PWM_EN , 0 ) ;
2018-01-06 19:58:40 +03:00
/* Stop counter */
2020-03-23 17:24:20 +03:00
regmap_write ( jz - > map , TCU_REG_TECR , BIT ( pwm - > hwpwm ) ) ;
2012-08-22 12:01:24 +04:00
}
2019-06-07 18:44:07 +03:00
static int jz4740_pwm_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
2019-08-24 18:37:07 +03:00
const struct pwm_state * state )
2012-08-22 12:01:24 +04:00
{
struct jz4740_pwm_chip * jz4740 = to_jz4740 ( pwm - > chip ) ;
2020-03-23 17:24:19 +03:00
unsigned long long tmp = 0xffffull * NSEC_PER_SEC ;
struct clk * clk = pwm_get_chip_data ( pwm ) ;
unsigned long period , duty ;
long rate ;
2020-03-23 17:24:18 +03:00
int err ;
2012-08-22 12:01:24 +04:00
2020-03-23 17:24:19 +03:00
/*
* Limit the clock to a maximum rate that still gives us a period value
* which fits in 16 bits .
*/
do_div ( tmp , state - > period ) ;
2012-08-22 12:01:24 +04:00
2020-03-23 17:24:19 +03:00
/*
* / ! \ IMPORTANT NOTE :
* - - - - - - - - - - - - - - - - - - -
* This code relies on the fact that clk_round_rate ( ) will always round
* down , which is not a valid assumption given by the clk API , but only
* happens to be true with the clk drivers used for Ingenic SoCs .
*
* Right now , there is no alternative as the clk API does not have a
* round - down function ( and won ' t have one for a while ) , but if it ever
* comes to light , a round - down function should be used instead .
*/
rate = clk_round_rate ( clk , tmp ) ;
if ( rate < 0 ) {
dev_err ( chip - > dev , " Unable to round rate: %ld " , rate ) ;
return rate ;
2012-08-22 12:01:24 +04:00
}
2020-03-23 17:24:19 +03:00
/* Calculate period value */
tmp = ( unsigned long long ) rate * state - > period ;
do_div ( tmp , NSEC_PER_SEC ) ;
2020-05-27 14:52:23 +03:00
period = tmp ;
2012-08-22 12:01:24 +04:00
2020-03-23 17:24:19 +03:00
/* Calculate duty value */
2020-05-27 14:52:23 +03:00
tmp = ( unsigned long long ) rate * state - > duty_cycle ;
do_div ( tmp , NSEC_PER_SEC ) ;
2020-05-27 14:52:24 +03:00
duty = tmp ;
2012-08-22 12:01:24 +04:00
if ( duty > = period )
duty = period - 1 ;
2019-06-07 18:44:07 +03:00
jz4740_pwm_disable ( chip , pwm ) ;
2012-08-22 12:01:24 +04:00
2020-03-23 17:24:18 +03:00
err = clk_set_rate ( clk , rate ) ;
if ( err ) {
dev_err ( chip - > dev , " Unable to set rate: %d " , err ) ;
return err ;
}
2020-03-23 17:24:20 +03:00
/* Reset counter to 0 */
regmap_write ( jz4740 - > map , TCU_REG_TCNTc ( pwm - > hwpwm ) , 0 ) ;
/* Set duty */
regmap_write ( jz4740 - > map , TCU_REG_TDHRc ( pwm - > hwpwm ) , duty ) ;
2012-08-22 12:01:24 +04:00
2020-03-23 17:24:20 +03:00
/* Set period */
regmap_write ( jz4740 - > map , TCU_REG_TDFRc ( pwm - > hwpwm ) , period ) ;
2012-08-22 12:01:24 +04:00
2020-03-23 17:24:20 +03:00
/* Set abrupt shutdown */
regmap_update_bits ( jz4740 - > map , TCU_REG_TCSRc ( pwm - > hwpwm ) ,
TCU_TCSR_PWM_SD , TCU_TCSR_PWM_SD ) ;
2020-05-27 14:52:24 +03:00
/*
* Set polarity .
*
* The PWM starts in inactive state until the internal timer reaches the
* duty value , then becomes active until the timer reaches the period
* value . In theory , we should then use ( period - duty ) as the real duty
* value , as a high duty value would otherwise result in the PWM pin
* being inactive most of the time .
*
* Here , we don ' t do that , and instead invert the polarity of the PWM
* when it is active . This trick makes the PWM start with its active
* state instead of its inactive state .
*/
if ( ( state - > polarity = = PWM_POLARITY_NORMAL ) ^ state - > enabled )
2020-03-23 17:24:20 +03:00
regmap_update_bits ( jz4740 - > map , TCU_REG_TCSRc ( pwm - > hwpwm ) ,
TCU_TCSR_PWM_INITL_HIGH , 0 ) ;
2020-05-27 14:52:24 +03:00
else
2020-03-23 17:24:20 +03:00
regmap_update_bits ( jz4740 - > map , TCU_REG_TCSRc ( pwm - > hwpwm ) ,
TCU_TCSR_PWM_INITL_HIGH ,
TCU_TCSR_PWM_INITL_HIGH ) ;
2018-01-06 19:58:41 +03:00
2019-06-07 18:44:07 +03:00
if ( state - > enabled )
jz4740_pwm_enable ( chip , pwm ) ;
2018-01-06 19:58:41 +03:00
return 0 ;
}
2012-08-22 12:01:24 +04:00
static const struct pwm_ops jz4740_pwm_ops = {
. request = jz4740_pwm_request ,
. free = jz4740_pwm_free ,
2019-06-07 18:44:07 +03:00
. apply = jz4740_pwm_apply ,
2012-08-22 12:01:24 +04:00
. owner = THIS_MODULE ,
} ;
2012-11-19 22:23:14 +04:00
static int jz4740_pwm_probe ( struct platform_device * pdev )
2012-08-22 12:01:24 +04:00
{
2020-03-23 17:24:20 +03:00
struct device * dev = & pdev - > dev ;
2012-08-22 12:01:24 +04:00
struct jz4740_pwm_chip * jz4740 ;
2020-05-27 14:52:25 +03:00
const struct soc_info * info ;
info = device_get_match_data ( dev ) ;
if ( ! info )
return - EINVAL ;
2012-08-22 12:01:24 +04:00
2020-03-23 17:24:20 +03:00
jz4740 = devm_kzalloc ( dev , sizeof ( * jz4740 ) , GFP_KERNEL ) ;
2012-08-22 12:01:24 +04:00
if ( ! jz4740 )
return - ENOMEM ;
2020-03-23 17:24:20 +03:00
jz4740 - > map = device_node_to_regmap ( dev - > parent - > of_node ) ;
if ( IS_ERR ( jz4740 - > map ) ) {
dev_err ( dev , " regmap not found: %ld \n " , PTR_ERR ( jz4740 - > map ) ) ;
return PTR_ERR ( jz4740 - > map ) ;
}
jz4740 - > chip . dev = dev ;
2012-08-22 12:01:24 +04:00
jz4740 - > chip . ops = & jz4740_pwm_ops ;
2020-05-27 14:52:25 +03:00
jz4740 - > chip . npwm = info - > num_pwms ;
2012-08-22 12:01:24 +04:00
2021-07-07 19:28:07 +03:00
return devm_pwmchip_add ( dev , & jz4740 - > chip ) ;
2012-08-22 12:01:24 +04:00
}
2020-05-27 14:52:25 +03:00
static const struct soc_info __maybe_unused jz4740_soc_info = {
. num_pwms = 8 ,
} ;
static const struct soc_info __maybe_unused jz4725b_soc_info = {
. num_pwms = 6 ,
} ;
2018-01-06 19:58:42 +03:00
# ifdef CONFIG_OF
static const struct of_device_id jz4740_pwm_dt_ids [ ] = {
2020-05-27 14:52:25 +03:00
{ . compatible = " ingenic,jz4740-pwm " , . data = & jz4740_soc_info } ,
{ . compatible = " ingenic,jz4725b-pwm " , . data = & jz4725b_soc_info } ,
2018-01-06 19:58:42 +03:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , jz4740_pwm_dt_ids ) ;
# endif
2012-08-22 12:01:24 +04:00
static struct platform_driver jz4740_pwm_driver = {
. driver = {
. name = " jz4740-pwm " ,
2018-01-06 19:58:42 +03:00
. of_match_table = of_match_ptr ( jz4740_pwm_dt_ids ) ,
2012-08-22 12:01:24 +04:00
} ,
. probe = jz4740_pwm_probe ,
} ;
module_platform_driver ( jz4740_pwm_driver ) ;
MODULE_AUTHOR ( " Lars-Peter Clausen <lars@metafoo.de> " ) ;
MODULE_DESCRIPTION ( " Ingenic JZ4740 PWM driver " ) ;
MODULE_ALIAS ( " platform:jz4740-pwm " ) ;
MODULE_LICENSE ( " GPL " ) ;