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 ;
bool out_en ;
u8 duty_val ;
} ;
static int iqs620_pwm_apply ( struct pwm_chip * chip , struct pwm_device * pwm ,
const struct pwm_state * state )
{
struct iqs620_pwm_private * iqs620_pwm ;
struct iqs62x_core * iqs62x ;
2020-06-15 17:08:28 +03:00
u64 duty_scale ;
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 ) ;
iqs62x = iqs620_pwm - > iqs62x ;
/*
* 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 .
*/
2020-06-15 17:08:28 +03:00
duty_scale = div_u64 ( state - > duty_cycle * 256 , IQS620_PWM_PERIOD_NS ) ;
2020-04-13 05:15:16 +03:00
mutex_lock ( & iqs620_pwm - > lock ) ;
if ( ! state - > enabled | | ! duty_scale ) {
ret = regmap_update_bits ( iqs62x - > regmap , IQS620_PWR_SETTINGS ,
IQS620_PWR_SETTINGS_PWM_OUT , 0 ) ;
if ( ret )
goto err_mutex ;
}
if ( duty_scale ) {
2020-06-15 17:08:28 +03:00
u8 duty_val = min_t ( u64 , duty_scale - 1 , 0xff ) ;
2020-04-13 05:15:16 +03:00
ret = regmap_write ( iqs62x - > regmap , IQS620_PWM_DUTY_CYCLE ,
duty_val ) ;
if ( ret )
goto err_mutex ;
iqs620_pwm - > duty_val = duty_val ;
}
if ( state - > enabled & & duty_scale ) {
ret = regmap_update_bits ( iqs62x - > regmap , IQS620_PWR_SETTINGS ,
2020-06-15 17:10:16 +03:00
IQS620_PWR_SETTINGS_PWM_OUT , 0xff ) ;
2020-04-13 05:15:16 +03:00
if ( ret )
goto err_mutex ;
}
iqs620_pwm - > out_en = state - > enabled ;
err_mutex :
mutex_unlock ( & iqs620_pwm - > lock ) ;
return ret ;
}
static void iqs620_pwm_get_state ( struct pwm_chip * chip , struct pwm_device * pwm ,
struct pwm_state * state )
{
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
* as disabled with duty cycle equal to that which was in use prior to
* the request . This is not ideal , but is the best compromise based on
* the capabilities of the device .
*/
state - > enabled = iqs620_pwm - > out_en ;
state - > duty_cycle = DIV_ROUND_UP ( ( iqs620_pwm - > duty_val + 1 ) *
IQS620_PWM_PERIOD_NS , 256 ) ;
mutex_unlock ( & iqs620_pwm - > lock ) ;
state - > period = IQS620_PWM_PERIOD_NS ;
}
static int iqs620_pwm_notifier ( struct notifier_block * notifier ,
unsigned long event_flags , void * context )
{
struct iqs620_pwm_private * iqs620_pwm ;
struct iqs62x_core * iqs62x ;
int ret ;
if ( ! ( event_flags & BIT ( IQS62X_EVENT_SYS_RESET ) ) )
return NOTIFY_DONE ;
iqs620_pwm = container_of ( notifier , struct iqs620_pwm_private ,
notifier ) ;
iqs62x = iqs620_pwm - > iqs62x ;
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 .
*/
ret = regmap_write ( iqs62x - > regmap , IQS620_PWM_DUTY_CYCLE ,
iqs620_pwm - > duty_val ) ;
if ( ret )
goto err_mutex ;
ret = regmap_update_bits ( iqs62x - > regmap , IQS620_PWR_SETTINGS ,
IQS620_PWR_SETTINGS_PWM_OUT ,
2020-06-15 17:10:16 +03:00
iqs620_pwm - > out_en ? 0xff : 0 ) ;
2020-04-13 05:15:16 +03:00
err_mutex :
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 ,
. owner = THIS_MODULE ,
} ;
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 ;
platform_set_drvdata ( pdev , iqs620_pwm ) ;
iqs620_pwm - > iqs62x = iqs62x ;
ret = regmap_read ( iqs62x - > regmap , IQS620_PWR_SETTINGS , & val ) ;
if ( ret )
return ret ;
iqs620_pwm - > out_en = val & IQS620_PWR_SETTINGS_PWM_OUT ;
ret = regmap_read ( iqs62x - > regmap , IQS620_PWM_DUTY_CYCLE , & val ) ;
if ( ret )
return ret ;
iqs620_pwm - > duty_val = val ;
iqs620_pwm - > chip . dev = & pdev - > dev ;
iqs620_pwm - > chip . ops = & iqs620_pwm_ops ;
iqs620_pwm - > chip . base = - 1 ;
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 ;
ret = pwmchip_add ( & iqs620_pwm - > chip ) ;
if ( ret )
dev_err ( & pdev - > dev , " Failed to add device: %d \n " , ret ) ;
return ret ;
}
static int iqs620_pwm_remove ( struct platform_device * pdev )
{
struct iqs620_pwm_private * iqs620_pwm = platform_get_drvdata ( pdev ) ;
int ret ;
ret = pwmchip_remove ( & iqs620_pwm - > chip ) ;
if ( ret )
dev_err ( & pdev - > dev , " Failed to remove device: %d \n " , ret ) ;
return ret ;
}
static struct platform_driver iqs620_pwm_platform_driver = {
. driver = {
. name = " iqs620a-pwm " ,
} ,
. probe = iqs620_pwm_probe ,
. remove = iqs620_pwm_remove ,
} ;
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 " ) ;