2020-04-13 05:15:16 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Azoteq IQS620A PWM Generator
*
* Copyright ( C ) 2019 Jeff LaBundy < jeff @ labundy . com >
*
* Limitations :
* - The period is fixed to 1 ms and is generated continuously despite changes
* to the duty cycle or enable / disable state .
* - Changes to the duty cycle or enable / disable state take effect immediately
* and may result in a glitch during the period in which the change is made .
* - The device cannot generate a 0 % duty cycle . For duty cycles below 1 / 256
* ms , the output is disabled and relies upon an external pull - down resistor
* to hold the GPIO3 / LTX pin low .
*/
# include <linux/device.h>
# include <linux/kernel.h>
# include <linux/mfd/iqs62x.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/notifier.h>
# include <linux/platform_device.h>
# include <linux/pwm.h>
# include <linux/regmap.h>
# include <linux/slab.h>
2020-06-15 17:10:16 +03:00
# define IQS620_PWR_SETTINGS 0xd2
2020-04-13 05:15:16 +03:00
# define IQS620_PWR_SETTINGS_PWM_OUT BIT(7)
2020-06-15 17:10:16 +03:00
# define IQS620_PWM_DUTY_CYCLE 0xd8
2020-04-13 05:15:16 +03:00
# define IQS620_PWM_PERIOD_NS 1000000
struct iqs620_pwm_private {
struct iqs62x_core * iqs62x ;
struct pwm_chip chip ;
struct notifier_block notifier ;
struct mutex lock ;
2021-01-19 07:30:29 +03:00
unsigned int duty_scale ;
2020-04-13 05:15:16 +03:00
} ;
2021-01-19 07:30:29 +03:00
static int iqs620_pwm_init ( struct iqs620_pwm_private * iqs620_pwm ,
unsigned int duty_scale )
{
struct iqs62x_core * iqs62x = iqs620_pwm - > iqs62x ;
int ret ;
if ( ! duty_scale )
pwm: iqs620a: Use regmap_clear_bits and regmap_set_bits where applicable
Found using coccinelle and the following semantic patch:
@@
expression map, reg, bits;
@@
- regmap_update_bits(map, reg, bits, bits)
+ regmap_set_bits(map, reg, bits)
@@
expression map, reg, bits;
@@
- regmap_update_bits(map, reg, bits, 0)
+ regmap_clear_bits(map, reg, bits)
Link: https://lore.kernel.org/r/20221115111347.3705732-4-u.kleine-koenig@pengutronix.de
Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
2022-12-02 21:35:16 +03:00
return regmap_clear_bits ( iqs62x - > regmap , IQS620_PWR_SETTINGS ,
IQS620_PWR_SETTINGS_PWM_OUT ) ;
2021-01-19 07:30:29 +03:00
ret = regmap_write ( iqs62x - > regmap , IQS620_PWM_DUTY_CYCLE ,
duty_scale - 1 ) ;
if ( ret )
return ret ;
2023-02-06 05:56:22 +03:00
return regmap_set_bits ( iqs62x - > regmap , IQS620_PWR_SETTINGS ,
IQS620_PWR_SETTINGS_PWM_OUT ) ;
2021-01-19 07:30:29 +03:00
}
2020-04-13 05:15:16 +03:00
static int iqs620_pwm_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
const struct pwm_state * state )
{
struct iqs620_pwm_private * iqs620_pwm ;
2021-01-15 10:32:39 +03:00
unsigned int duty_cycle ;
unsigned int duty_scale ;
2020-06-15 17:08:28 +03:00
int ret ;
2020-04-13 05:15:16 +03:00
if ( state - > polarity ! = PWM_POLARITY_NORMAL )
2020-11-11 23:18:11 +03:00
return - EINVAL ;
2020-04-13 05:15:16 +03:00
if ( state - > period < IQS620_PWM_PERIOD_NS )
return - EINVAL ;
iqs620_pwm = container_of ( chip , struct iqs620_pwm_private , chip ) ;
/*
* The duty cycle generated by the device is calculated as follows :
*
* duty_cycle = ( IQS620_PWM_DUTY_CYCLE + 1 ) / 256 * 1 ms
*
* . . . where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 255
* ( inclusive ) . Therefore the lowest duty cycle the device can generate
* while the output is enabled is 1 / 256 ms .
*
* For lower duty cycles ( e . g . 0 ) , the PWM output is simply disabled to
* allow an external pull - down resistor to hold the GPIO3 / LTX pin low .
*/
2021-01-15 10:32:39 +03:00
duty_cycle = min_t ( u64 , state - > duty_cycle , IQS620_PWM_PERIOD_NS ) ;
duty_scale = duty_cycle * 256 / IQS620_PWM_PERIOD_NS ;
2020-04-13 05:15:16 +03:00
2021-01-19 07:30:29 +03:00
if ( ! state - > enabled )
duty_scale = 0 ;
2020-04-13 05:15:16 +03:00
2021-01-19 07:30:29 +03:00
mutex_lock ( & iqs620_pwm - > lock ) ;
2020-04-13 05:15:16 +03:00
2021-01-19 07:30:29 +03:00
ret = iqs620_pwm_init ( iqs620_pwm , duty_scale ) ;
if ( ! ret )
iqs620_pwm - > duty_scale = duty_scale ;
2020-04-13 05:15:16 +03:00
mutex_unlock ( & iqs620_pwm - > lock ) ;
return ret ;
}
2022-12-02 21:35:26 +03:00
static int iqs620_pwm_get_state ( struct pwm_chip * chip , struct pwm_device * pwm ,
struct pwm_state * state )
2020-04-13 05:15:16 +03:00
{
struct iqs620_pwm_private * iqs620_pwm ;
iqs620_pwm = container_of ( chip , struct iqs620_pwm_private , chip ) ;
mutex_lock ( & iqs620_pwm - > lock ) ;
/*
* Since the device cannot generate a 0 % duty cycle , requests to do so
* cause subsequent calls to iqs620_pwm_get_state to report the output
2021-01-19 07:30:29 +03:00
* as disabled . This is not ideal , but is the best compromise based on
2020-04-13 05:15:16 +03:00
* the capabilities of the device .
*/
2021-01-19 07:30:29 +03:00
state - > enabled = iqs620_pwm - > duty_scale > 0 ;
state - > duty_cycle = DIV_ROUND_UP ( iqs620_pwm - > duty_scale *
2020-04-13 05:15:16 +03:00
IQS620_PWM_PERIOD_NS , 256 ) ;
mutex_unlock ( & iqs620_pwm - > lock ) ;
state - > period = IQS620_PWM_PERIOD_NS ;
2023-03-23 00:45:42 +03:00
state - > polarity = PWM_POLARITY_NORMAL ;
2022-12-02 21:35:26 +03:00
return 0 ;
2020-04-13 05:15:16 +03:00
}
static int iqs620_pwm_notifier ( struct notifier_block * notifier ,
unsigned long event_flags , void * context )
{
struct iqs620_pwm_private * iqs620_pwm ;
int ret ;
if ( ! ( event_flags & BIT ( IQS62X_EVENT_SYS_RESET ) ) )
return NOTIFY_DONE ;
iqs620_pwm = container_of ( notifier , struct iqs620_pwm_private ,
notifier ) ;
mutex_lock ( & iqs620_pwm - > lock ) ;
/*
* The parent MFD driver already prints an error message in the event
* of a device reset , so nothing else is printed here unless there is
* an additional failure .
*/
2021-01-19 07:30:29 +03:00
ret = iqs620_pwm_init ( iqs620_pwm , iqs620_pwm - > duty_scale ) ;
2020-04-13 05:15:16 +03:00
mutex_unlock ( & iqs620_pwm - > lock ) ;
if ( ret ) {
dev_err ( iqs620_pwm - > chip . dev ,
" Failed to re-initialize device: %d \n " , ret ) ;
return NOTIFY_BAD ;
}
return NOTIFY_OK ;
}
static const struct pwm_ops iqs620_pwm_ops = {
. apply = iqs620_pwm_apply ,
. get_state = iqs620_pwm_get_state ,
} ;
static void iqs620_pwm_notifier_unregister ( void * context )
{
struct iqs620_pwm_private * iqs620_pwm = context ;
int ret ;
ret = blocking_notifier_chain_unregister ( & iqs620_pwm - > iqs62x - > nh ,
& iqs620_pwm - > notifier ) ;
if ( ret )
dev_err ( iqs620_pwm - > chip . dev ,
" Failed to unregister notifier: %d \n " , ret ) ;
}
static int iqs620_pwm_probe ( struct platform_device * pdev )
{
struct iqs62x_core * iqs62x = dev_get_drvdata ( pdev - > dev . parent ) ;
struct iqs620_pwm_private * iqs620_pwm ;
unsigned int val ;
int ret ;
iqs620_pwm = devm_kzalloc ( & pdev - > dev , sizeof ( * iqs620_pwm ) , GFP_KERNEL ) ;
if ( ! iqs620_pwm )
return - ENOMEM ;
iqs620_pwm - > iqs62x = iqs62x ;
ret = regmap_read ( iqs62x - > regmap , IQS620_PWR_SETTINGS , & val ) ;
if ( ret )
return ret ;
2021-01-19 07:30:29 +03:00
if ( val & IQS620_PWR_SETTINGS_PWM_OUT ) {
ret = regmap_read ( iqs62x - > regmap , IQS620_PWM_DUTY_CYCLE , & val ) ;
if ( ret )
return ret ;
iqs620_pwm - > duty_scale = val + 1 ;
}
2020-04-13 05:15:16 +03:00
iqs620_pwm - > chip . dev = & pdev - > dev ;
iqs620_pwm - > chip . ops = & iqs620_pwm_ops ;
iqs620_pwm - > chip . npwm = 1 ;
mutex_init ( & iqs620_pwm - > lock ) ;
iqs620_pwm - > notifier . notifier_call = iqs620_pwm_notifier ;
ret = blocking_notifier_chain_register ( & iqs620_pwm - > iqs62x - > nh ,
& iqs620_pwm - > notifier ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to register notifier: %d \n " , ret ) ;
return ret ;
}
ret = devm_add_action_or_reset ( & pdev - > dev ,
iqs620_pwm_notifier_unregister ,
iqs620_pwm ) ;
if ( ret )
return ret ;
2021-07-07 19:28:06 +03:00
ret = devm_pwmchip_add ( & pdev - > dev , & iqs620_pwm - > chip ) ;
2020-04-13 05:15:16 +03:00
if ( ret )
dev_err ( & pdev - > dev , " Failed to add device: %d \n " , ret ) ;
return ret ;
}
static struct platform_driver iqs620_pwm_platform_driver = {
. driver = {
. name = " iqs620a-pwm " ,
} ,
. probe = iqs620_pwm_probe ,
} ;
module_platform_driver ( iqs620_pwm_platform_driver ) ;
MODULE_AUTHOR ( " Jeff LaBundy <jeff@labundy.com> " ) ;
MODULE_DESCRIPTION ( " Azoteq IQS620A PWM Generator " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:iqs620a-pwm " ) ;