2020-02-16 17:32:11 -06:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Azoteq IQS624 / 625 Angular Position Sensors
*
* Copyright ( C ) 2019 Jeff LaBundy < jeff @ labundy . com >
*/
# include <linux/device.h>
# include <linux/iio/events.h>
# include <linux/iio/iio.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/regmap.h>
# define IQS624_POS_DEG_OUT 0x16
# define IQS624_POS_SCALE1 (314159 / 180)
# define IQS624_POS_SCALE2 100000
struct iqs624_pos_private {
struct iqs62x_core * iqs62x ;
2020-05-22 09:53:22 +03:00
struct iio_dev * indio_dev ;
2020-02-16 17:32:11 -06:00
struct notifier_block notifier ;
struct mutex lock ;
bool angle_en ;
u16 angle ;
} ;
static int iqs624_pos_angle_en ( struct iqs62x_core * iqs62x , bool angle_en )
{
unsigned int event_mask = IQS624_HALL_UI_WHL_EVENT ;
/*
* The IQS625 reports angular position in the form of coarse intervals ,
* so only interval change events are unmasked . Conversely , the IQS624
* reports angular position down to one degree of resolution , so wheel
* movement events are unmasked instead .
*/
if ( iqs62x - > dev_desc - > prod_num = = IQS625_PROD_NUM )
event_mask = IQS624_HALL_UI_INT_EVENT ;
return regmap_update_bits ( iqs62x - > regmap , IQS624_HALL_UI , event_mask ,
angle_en ? 0 : 0xFF ) ;
}
static int iqs624_pos_notifier ( struct notifier_block * notifier ,
unsigned long event_flags , void * context )
{
struct iqs62x_event_data * event_data = context ;
struct iqs624_pos_private * iqs624_pos ;
struct iqs62x_core * iqs62x ;
struct iio_dev * indio_dev ;
u16 angle = event_data - > ui_data ;
s64 timestamp ;
int ret ;
iqs624_pos = container_of ( notifier , struct iqs624_pos_private ,
notifier ) ;
2020-05-22 09:53:22 +03:00
indio_dev = iqs624_pos - > indio_dev ;
2020-02-16 17:32:11 -06:00
timestamp = iio_get_time_ns ( indio_dev ) ;
iqs62x = iqs624_pos - > iqs62x ;
if ( iqs62x - > dev_desc - > prod_num = = IQS625_PROD_NUM )
angle = event_data - > interval ;
mutex_lock ( & iqs624_pos - > lock ) ;
if ( event_flags & BIT ( IQS62X_EVENT_SYS_RESET ) ) {
ret = iqs624_pos_angle_en ( iqs62x , iqs624_pos - > angle_en ) ;
if ( ret ) {
dev_err ( indio_dev - > dev . parent ,
" Failed to re-initialize device: %d \n " , ret ) ;
ret = NOTIFY_BAD ;
} else {
ret = NOTIFY_OK ;
}
} else if ( iqs624_pos - > angle_en & & ( angle ! = iqs624_pos - > angle ) ) {
iio_push_event ( indio_dev ,
IIO_UNMOD_EVENT_CODE ( IIO_ANGL , 0 ,
IIO_EV_TYPE_CHANGE ,
IIO_EV_DIR_NONE ) ,
timestamp ) ;
iqs624_pos - > angle = angle ;
ret = NOTIFY_OK ;
} else {
ret = NOTIFY_DONE ;
}
mutex_unlock ( & iqs624_pos - > lock ) ;
return ret ;
}
static void iqs624_pos_notifier_unregister ( void * context )
{
struct iqs624_pos_private * iqs624_pos = context ;
2020-05-22 09:53:22 +03:00
struct iio_dev * indio_dev = iqs624_pos - > indio_dev ;
2020-02-16 17:32:11 -06:00
int ret ;
ret = blocking_notifier_chain_unregister ( & iqs624_pos - > iqs62x - > nh ,
& iqs624_pos - > notifier ) ;
if ( ret )
dev_err ( indio_dev - > dev . parent ,
" Failed to unregister notifier: %d \n " , ret ) ;
}
static int iqs624_pos_angle_get ( struct iqs62x_core * iqs62x , unsigned int * val )
{
int ret ;
__le16 val_buf ;
if ( iqs62x - > dev_desc - > prod_num = = IQS625_PROD_NUM )
return regmap_read ( iqs62x - > regmap , iqs62x - > dev_desc - > interval ,
val ) ;
ret = regmap_raw_read ( iqs62x - > regmap , IQS624_POS_DEG_OUT , & val_buf ,
sizeof ( val_buf ) ) ;
if ( ret )
return ret ;
* val = le16_to_cpu ( val_buf ) ;
return 0 ;
}
static int iqs624_pos_read_raw ( struct iio_dev * indio_dev ,
struct iio_chan_spec const * chan ,
int * val , int * val2 , long mask )
{
struct iqs624_pos_private * iqs624_pos = iio_priv ( indio_dev ) ;
struct iqs62x_core * iqs62x = iqs624_pos - > iqs62x ;
unsigned int scale = 1 ;
int ret ;
switch ( mask ) {
case IIO_CHAN_INFO_RAW :
ret = iqs624_pos_angle_get ( iqs62x , val ) ;
if ( ret )
return ret ;
return IIO_VAL_INT ;
case IIO_CHAN_INFO_SCALE :
if ( iqs62x - > dev_desc - > prod_num = = IQS625_PROD_NUM ) {
ret = regmap_read ( iqs62x - > regmap , IQS624_INTERVAL_DIV ,
& scale ) ;
if ( ret )
return ret ;
}
* val = scale * IQS624_POS_SCALE1 ;
* val2 = IQS624_POS_SCALE2 ;
return IIO_VAL_FRACTIONAL ;
default :
return - EINVAL ;
}
}
static int iqs624_pos_read_event_config ( struct iio_dev * indio_dev ,
const struct iio_chan_spec * chan ,
enum iio_event_type type ,
enum iio_event_direction dir )
{
struct iqs624_pos_private * iqs624_pos = iio_priv ( indio_dev ) ;
int ret ;
mutex_lock ( & iqs624_pos - > lock ) ;
ret = iqs624_pos - > angle_en ;
mutex_unlock ( & iqs624_pos - > lock ) ;
return ret ;
}
static int iqs624_pos_write_event_config ( struct iio_dev * indio_dev ,
const struct iio_chan_spec * chan ,
enum iio_event_type type ,
enum iio_event_direction dir ,
int state )
{
struct iqs624_pos_private * iqs624_pos = iio_priv ( indio_dev ) ;
struct iqs62x_core * iqs62x = iqs624_pos - > iqs62x ;
unsigned int val ;
int ret ;
mutex_lock ( & iqs624_pos - > lock ) ;
ret = iqs624_pos_angle_get ( iqs62x , & val ) ;
if ( ret )
goto err_mutex ;
ret = iqs624_pos_angle_en ( iqs62x , state ) ;
if ( ret )
goto err_mutex ;
iqs624_pos - > angle = val ;
iqs624_pos - > angle_en = state ;
err_mutex :
mutex_unlock ( & iqs624_pos - > lock ) ;
return ret ;
}
static const struct iio_info iqs624_pos_info = {
. read_raw = & iqs624_pos_read_raw ,
. read_event_config = iqs624_pos_read_event_config ,
. write_event_config = iqs624_pos_write_event_config ,
} ;
static const struct iio_event_spec iqs624_pos_events [ ] = {
{
. type = IIO_EV_TYPE_CHANGE ,
. dir = IIO_EV_DIR_NONE ,
. mask_separate = BIT ( IIO_EV_INFO_ENABLE ) ,
} ,
} ;
static const struct iio_chan_spec iqs624_pos_channels [ ] = {
{
. type = IIO_ANGL ,
. info_mask_separate = BIT ( IIO_CHAN_INFO_RAW ) |
BIT ( IIO_CHAN_INFO_SCALE ) ,
. event_spec = iqs624_pos_events ,
. num_event_specs = ARRAY_SIZE ( iqs624_pos_events ) ,
} ,
} ;
static int iqs624_pos_probe ( struct platform_device * pdev )
{
struct iqs62x_core * iqs62x = dev_get_drvdata ( pdev - > dev . parent ) ;
struct iqs624_pos_private * iqs624_pos ;
struct iio_dev * indio_dev ;
int ret ;
indio_dev = devm_iio_device_alloc ( & pdev - > dev , sizeof ( * iqs624_pos ) ) ;
if ( ! indio_dev )
return - ENOMEM ;
iqs624_pos = iio_priv ( indio_dev ) ;
iqs624_pos - > iqs62x = iqs62x ;
2020-05-22 09:53:22 +03:00
iqs624_pos - > indio_dev = indio_dev ;
2020-02-16 17:32:11 -06:00
indio_dev - > modes = INDIO_DIRECT_MODE ;
indio_dev - > channels = iqs624_pos_channels ;
indio_dev - > num_channels = ARRAY_SIZE ( iqs624_pos_channels ) ;
indio_dev - > name = iqs62x - > dev_desc - > dev_name ;
indio_dev - > info = & iqs624_pos_info ;
mutex_init ( & iqs624_pos - > lock ) ;
iqs624_pos - > notifier . notifier_call = iqs624_pos_notifier ;
ret = blocking_notifier_chain_register ( & iqs624_pos - > iqs62x - > nh ,
& iqs624_pos - > notifier ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to register notifier: %d \n " , ret ) ;
return ret ;
}
ret = devm_add_action_or_reset ( & pdev - > dev ,
iqs624_pos_notifier_unregister ,
iqs624_pos ) ;
if ( ret )
return ret ;
return devm_iio_device_register ( & pdev - > dev , indio_dev ) ;
}
static struct platform_driver iqs624_pos_platform_driver = {
. driver = {
. name = " iqs624-pos " ,
} ,
. probe = iqs624_pos_probe ,
} ;
module_platform_driver ( iqs624_pos_platform_driver ) ;
MODULE_AUTHOR ( " Jeff LaBundy <jeff@labundy.com> " ) ;
MODULE_DESCRIPTION ( " Azoteq IQS624/625 Angular Position Sensors " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:iqs624-pos " ) ;