2020-02-17 02:32:06 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Azoteq IQS620A / 621 / 622 / 624 / 625 Multi - Function Sensors
*
* Copyright ( C ) 2019 Jeff LaBundy < jeff @ labundy . com >
*
* These devices rely on application - specific register settings and calibration
* data developed in and exported from a suite of GUIs offered by the vendor . A
* separate tool converts the GUIs ' ASCII - based output into a standard firmware
* file parsed by the driver .
*
* Link to datasheets and GUIs : https : //www.azoteq.com/
*
* Link to conversion tool : https : //github.com/jlabundy/iqs62x-h2bin.git
*/
# include <linux/completion.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/err.h>
# include <linux/firmware.h>
# include <linux/i2c.h>
# include <linux/interrupt.h>
# include <linux/kernel.h>
# include <linux/list.h>
# include <linux/mfd/core.h>
# include <linux/mfd/iqs62x.h>
# include <linux/module.h>
# include <linux/notifier.h>
# include <linux/of_device.h>
# include <linux/property.h>
# include <linux/regmap.h>
# include <linux/slab.h>
# include <asm/unaligned.h>
# define IQS62X_PROD_NUM 0x00
# define IQS62X_SYS_FLAGS 0x10
# define IQS620_HALL_FLAGS 0x16
# define IQS621_HALL_FLAGS 0x19
# define IQS622_HALL_FLAGS IQS621_HALL_FLAGS
# define IQS624_INTERVAL_NUM 0x18
# define IQS625_INTERVAL_NUM 0x12
# define IQS622_PROX_SETTINGS_4 0x48
# define IQS620_PROX_SETTINGS_4 0x50
# define IQS620_PROX_SETTINGS_4_SAR_EN BIT(7)
# define IQS621_ALS_CAL_DIV_LUX 0x82
# define IQS621_ALS_CAL_DIV_IR 0x83
# define IQS620_TEMP_CAL_MULT 0xC2
# define IQS620_TEMP_CAL_DIV 0xC3
# define IQS620_TEMP_CAL_OFFS 0xC4
# define IQS62X_SYS_SETTINGS 0xD0
# define IQS62X_SYS_SETTINGS_ACK_RESET BIT(6)
# define IQS62X_SYS_SETTINGS_EVENT_MODE BIT(5)
# define IQS62X_SYS_SETTINGS_CLK_DIV BIT(4)
2021-01-18 06:57:07 +03:00
# define IQS62X_SYS_SETTINGS_COMM_ATI BIT(3)
2020-02-17 02:32:06 +03:00
# define IQS62X_SYS_SETTINGS_REDO_ATI BIT(1)
# define IQS62X_PWR_SETTINGS 0xD2
# define IQS62X_PWR_SETTINGS_DIS_AUTO BIT(5)
# define IQS62X_PWR_SETTINGS_PWR_MODE_MASK (BIT(4) | BIT(3))
# define IQS62X_PWR_SETTINGS_PWR_MODE_HALT (BIT(4) | BIT(3))
# define IQS62X_PWR_SETTINGS_PWR_MODE_NORM 0
# define IQS62X_OTP_CMD 0xF0
# define IQS62X_OTP_CMD_FG3 0x13
# define IQS62X_OTP_DATA 0xF1
# define IQS62X_MAX_REG 0xFF
# define IQS62X_HALL_CAL_MASK GENMASK(3, 0)
# define IQS62X_FW_REC_TYPE_INFO 0
# define IQS62X_FW_REC_TYPE_PROD 1
# define IQS62X_FW_REC_TYPE_HALL 2
# define IQS62X_FW_REC_TYPE_MASK 3
# define IQS62X_FW_REC_TYPE_DATA 4
2021-01-18 06:57:08 +03:00
# define IQS62X_ATI_STARTUP_MS 350
2021-01-18 06:57:07 +03:00
# define IQS62X_FILT_SETTLE_MS 250
2020-02-17 02:32:06 +03:00
struct iqs62x_fw_rec {
u8 type ;
u8 addr ;
u8 len ;
u8 data ;
} __packed ;
struct iqs62x_fw_blk {
struct list_head list ;
u8 addr ;
u8 mask ;
u8 len ;
u8 data [ ] ;
} ;
struct iqs62x_info {
u8 prod_num ;
u8 sw_num ;
u8 hw_num ;
} __packed ;
static int iqs62x_dev_init ( struct iqs62x_core * iqs62x )
{
struct iqs62x_fw_blk * fw_blk ;
unsigned int val ;
int ret ;
list_for_each_entry ( fw_blk , & iqs62x - > fw_blk_head , list ) {
2021-01-18 06:57:08 +03:00
/*
* In case ATI is in progress , wait for it to complete before
* lowering the core clock frequency .
*/
if ( fw_blk - > addr = = IQS62X_SYS_SETTINGS & &
* fw_blk - > data & IQS62X_SYS_SETTINGS_CLK_DIV )
msleep ( IQS62X_ATI_STARTUP_MS ) ;
2020-02-17 02:32:06 +03:00
if ( fw_blk - > mask )
ret = regmap_update_bits ( iqs62x - > regmap , fw_blk - > addr ,
fw_blk - > mask , * fw_blk - > data ) ;
else
ret = regmap_raw_write ( iqs62x - > regmap , fw_blk - > addr ,
fw_blk - > data , fw_blk - > len ) ;
if ( ret )
return ret ;
}
switch ( iqs62x - > dev_desc - > prod_num ) {
case IQS620_PROD_NUM :
case IQS622_PROD_NUM :
ret = regmap_read ( iqs62x - > regmap ,
iqs62x - > dev_desc - > prox_settings , & val ) ;
if ( ret )
return ret ;
if ( val & IQS620_PROX_SETTINGS_4_SAR_EN )
iqs62x - > ui_sel = IQS62X_UI_SAR1 ;
2020-08-24 01:36:59 +03:00
fallthrough ;
2020-02-17 02:32:06 +03:00
case IQS621_PROD_NUM :
ret = regmap_write ( iqs62x - > regmap , IQS620_GLBL_EVENT_MASK ,
IQS620_GLBL_EVENT_MASK_PMU |
iqs62x - > dev_desc - > prox_mask |
iqs62x - > dev_desc - > sar_mask |
iqs62x - > dev_desc - > hall_mask |
iqs62x - > dev_desc - > hyst_mask |
iqs62x - > dev_desc - > temp_mask |
iqs62x - > dev_desc - > als_mask |
iqs62x - > dev_desc - > ir_mask ) ;
if ( ret )
return ret ;
break ;
default :
ret = regmap_write ( iqs62x - > regmap , IQS624_HALL_UI ,
IQS624_HALL_UI_WHL_EVENT |
IQS624_HALL_UI_INT_EVENT |
IQS624_HALL_UI_AUTO_CAL ) ;
if ( ret )
return ret ;
/*
* The IQS625 default interval divider is below the minimum
* permissible value , and the datasheet mandates that it is
* corrected during initialization ( unless an updated value
* has already been provided by firmware ) .
*
* To protect against an unacceptably low user - entered value
* stored in the firmware , the same check is extended to the
* IQS624 as well .
*/
ret = regmap_read ( iqs62x - > regmap , IQS624_INTERVAL_DIV , & val ) ;
if ( ret )
return ret ;
if ( val > = iqs62x - > dev_desc - > interval_div )
break ;
ret = regmap_write ( iqs62x - > regmap , IQS624_INTERVAL_DIV ,
iqs62x - > dev_desc - > interval_div ) ;
if ( ret )
return ret ;
}
2021-01-18 06:57:07 +03:00
/*
* Place the device in streaming mode at first so as not to miss the
* limited number of interrupts that would otherwise occur after ATI
* completes . The device is subsequently placed in event mode by the
* interrupt handler .
*
* In the meantime , mask interrupts during ATI to prevent the device
* from soliciting I2C traffic until the noise - sensitive ATI process
* is complete .
*/
ret = regmap_update_bits ( iqs62x - > regmap , IQS62X_SYS_SETTINGS ,
IQS62X_SYS_SETTINGS_ACK_RESET |
IQS62X_SYS_SETTINGS_EVENT_MODE |
IQS62X_SYS_SETTINGS_COMM_ATI |
IQS62X_SYS_SETTINGS_REDO_ATI ,
IQS62X_SYS_SETTINGS_ACK_RESET |
IQS62X_SYS_SETTINGS_REDO_ATI ) ;
2020-02-17 02:32:06 +03:00
if ( ret )
return ret ;
2021-01-18 06:57:07 +03:00
/*
* The following delay gives the device time to deassert its RDY output
* in case a communication window was open while the REDO_ATI field was
* written . This prevents an interrupt from being serviced prematurely .
*/
usleep_range ( 5000 , 5100 ) ;
2020-02-17 02:32:06 +03:00
return 0 ;
}
static int iqs62x_firmware_parse ( struct iqs62x_core * iqs62x ,
const struct firmware * fw )
{
struct i2c_client * client = iqs62x - > client ;
struct iqs62x_fw_rec * fw_rec ;
struct iqs62x_fw_blk * fw_blk ;
unsigned int val ;
size_t pos = 0 ;
int ret = 0 ;
u8 mask , len , * data ;
u8 hall_cal_index = 0 ;
while ( pos < fw - > size ) {
if ( pos + sizeof ( * fw_rec ) > fw - > size ) {
ret = - EINVAL ;
break ;
}
fw_rec = ( struct iqs62x_fw_rec * ) ( fw - > data + pos ) ;
pos + = sizeof ( * fw_rec ) ;
if ( pos + fw_rec - > len - 1 > fw - > size ) {
ret = - EINVAL ;
break ;
}
pos + = fw_rec - > len - 1 ;
switch ( fw_rec - > type ) {
case IQS62X_FW_REC_TYPE_INFO :
continue ;
case IQS62X_FW_REC_TYPE_PROD :
if ( fw_rec - > data = = iqs62x - > dev_desc - > prod_num )
continue ;
dev_err ( & client - > dev ,
" Incompatible product number: 0x%02X \n " ,
fw_rec - > data ) ;
ret = - EINVAL ;
break ;
case IQS62X_FW_REC_TYPE_HALL :
if ( ! hall_cal_index ) {
ret = regmap_write ( iqs62x - > regmap ,
IQS62X_OTP_CMD ,
IQS62X_OTP_CMD_FG3 ) ;
if ( ret )
break ;
ret = regmap_read ( iqs62x - > regmap ,
IQS62X_OTP_DATA , & val ) ;
if ( ret )
break ;
hall_cal_index = val & IQS62X_HALL_CAL_MASK ;
if ( ! hall_cal_index ) {
dev_err ( & client - > dev ,
" Uncalibrated device \n " ) ;
ret = - ENODATA ;
break ;
}
}
if ( hall_cal_index > fw_rec - > len ) {
ret = - EINVAL ;
break ;
}
mask = 0 ;
data = & fw_rec - > data + hall_cal_index - 1 ;
len = sizeof ( * data ) ;
break ;
case IQS62X_FW_REC_TYPE_MASK :
if ( fw_rec - > len < ( sizeof ( mask ) + sizeof ( * data ) ) ) {
ret = - EINVAL ;
break ;
}
mask = fw_rec - > data ;
data = & fw_rec - > data + sizeof ( mask ) ;
len = sizeof ( * data ) ;
break ;
case IQS62X_FW_REC_TYPE_DATA :
mask = 0 ;
data = & fw_rec - > data ;
len = fw_rec - > len ;
break ;
default :
dev_err ( & client - > dev ,
" Unrecognized record type: 0x%02X \n " ,
fw_rec - > type ) ;
ret = - EINVAL ;
}
if ( ret )
break ;
fw_blk = devm_kzalloc ( & client - > dev ,
struct_size ( fw_blk , data , len ) ,
GFP_KERNEL ) ;
if ( ! fw_blk ) {
ret = - ENOMEM ;
break ;
}
fw_blk - > addr = fw_rec - > addr ;
fw_blk - > mask = mask ;
fw_blk - > len = len ;
memcpy ( fw_blk - > data , data , len ) ;
list_add ( & fw_blk - > list , & iqs62x - > fw_blk_head ) ;
}
release_firmware ( fw ) ;
return ret ;
}
const struct iqs62x_event_desc iqs62x_events [ IQS62X_NUM_EVENTS ] = {
[ IQS62X_EVENT_PROX_CH0_T ] = {
. reg = IQS62X_EVENT_PROX ,
. mask = BIT ( 4 ) ,
. val = BIT ( 4 ) ,
} ,
[ IQS62X_EVENT_PROX_CH0_P ] = {
. reg = IQS62X_EVENT_PROX ,
. mask = BIT ( 0 ) ,
. val = BIT ( 0 ) ,
} ,
[ IQS62X_EVENT_PROX_CH1_T ] = {
. reg = IQS62X_EVENT_PROX ,
. mask = BIT ( 5 ) ,
. val = BIT ( 5 ) ,
} ,
[ IQS62X_EVENT_PROX_CH1_P ] = {
. reg = IQS62X_EVENT_PROX ,
. mask = BIT ( 1 ) ,
. val = BIT ( 1 ) ,
} ,
[ IQS62X_EVENT_PROX_CH2_T ] = {
. reg = IQS62X_EVENT_PROX ,
. mask = BIT ( 6 ) ,
. val = BIT ( 6 ) ,
} ,
[ IQS62X_EVENT_PROX_CH2_P ] = {
. reg = IQS62X_EVENT_PROX ,
. mask = BIT ( 2 ) ,
. val = BIT ( 2 ) ,
} ,
[ IQS62X_EVENT_HYST_POS_T ] = {
. reg = IQS62X_EVENT_HYST ,
. mask = BIT ( 6 ) | BIT ( 7 ) ,
. val = BIT ( 6 ) ,
} ,
[ IQS62X_EVENT_HYST_POS_P ] = {
. reg = IQS62X_EVENT_HYST ,
. mask = BIT ( 5 ) | BIT ( 7 ) ,
. val = BIT ( 5 ) ,
} ,
[ IQS62X_EVENT_HYST_NEG_T ] = {
. reg = IQS62X_EVENT_HYST ,
. mask = BIT ( 6 ) | BIT ( 7 ) ,
. val = BIT ( 6 ) | BIT ( 7 ) ,
} ,
[ IQS62X_EVENT_HYST_NEG_P ] = {
. reg = IQS62X_EVENT_HYST ,
. mask = BIT ( 5 ) | BIT ( 7 ) ,
. val = BIT ( 5 ) | BIT ( 7 ) ,
} ,
[ IQS62X_EVENT_SAR1_ACT ] = {
. reg = IQS62X_EVENT_HYST ,
. mask = BIT ( 4 ) ,
. val = BIT ( 4 ) ,
} ,
[ IQS62X_EVENT_SAR1_QRD ] = {
. reg = IQS62X_EVENT_HYST ,
. mask = BIT ( 2 ) ,
. val = BIT ( 2 ) ,
} ,
[ IQS62X_EVENT_SAR1_MOVE ] = {
. reg = IQS62X_EVENT_HYST ,
. mask = BIT ( 1 ) ,
. val = BIT ( 1 ) ,
} ,
[ IQS62X_EVENT_SAR1_HALT ] = {
. reg = IQS62X_EVENT_HYST ,
. mask = BIT ( 0 ) ,
. val = BIT ( 0 ) ,
} ,
[ IQS62X_EVENT_WHEEL_UP ] = {
. reg = IQS62X_EVENT_WHEEL ,
. mask = BIT ( 7 ) | BIT ( 6 ) ,
. val = BIT ( 7 ) ,
} ,
[ IQS62X_EVENT_WHEEL_DN ] = {
. reg = IQS62X_EVENT_WHEEL ,
. mask = BIT ( 7 ) | BIT ( 6 ) ,
. val = BIT ( 7 ) | BIT ( 6 ) ,
} ,
[ IQS62X_EVENT_HALL_N_T ] = {
. reg = IQS62X_EVENT_HALL ,
. mask = BIT ( 2 ) | BIT ( 0 ) ,
. val = BIT ( 2 ) ,
} ,
[ IQS62X_EVENT_HALL_N_P ] = {
. reg = IQS62X_EVENT_HALL ,
. mask = BIT ( 1 ) | BIT ( 0 ) ,
. val = BIT ( 1 ) ,
} ,
[ IQS62X_EVENT_HALL_S_T ] = {
. reg = IQS62X_EVENT_HALL ,
. mask = BIT ( 2 ) | BIT ( 0 ) ,
. val = BIT ( 2 ) | BIT ( 0 ) ,
} ,
[ IQS62X_EVENT_HALL_S_P ] = {
. reg = IQS62X_EVENT_HALL ,
. mask = BIT ( 1 ) | BIT ( 0 ) ,
. val = BIT ( 1 ) | BIT ( 0 ) ,
} ,
[ IQS62X_EVENT_SYS_RESET ] = {
. reg = IQS62X_EVENT_SYS ,
. mask = BIT ( 7 ) ,
. val = BIT ( 7 ) ,
} ,
2021-01-18 06:57:07 +03:00
[ IQS62X_EVENT_SYS_ATI ] = {
. reg = IQS62X_EVENT_SYS ,
. mask = BIT ( 2 ) ,
. val = BIT ( 2 ) ,
} ,
2020-02-17 02:32:06 +03:00
} ;
EXPORT_SYMBOL_GPL ( iqs62x_events ) ;
static irqreturn_t iqs62x_irq ( int irq , void * context )
{
struct iqs62x_core * iqs62x = context ;
struct i2c_client * client = iqs62x - > client ;
struct iqs62x_event_data event_data ;
struct iqs62x_event_desc event_desc ;
enum iqs62x_event_reg event_reg ;
unsigned long event_flags = 0 ;
int ret , i , j ;
u8 event_map [ IQS62X_EVENT_SIZE ] ;
/*
* The device asserts the RDY output to signal the beginning of a
* communication window , which is closed by an I2C stop condition .
* As such , all interrupt status is captured in a single read and
* broadcast to any interested sub - device drivers .
*/
ret = regmap_raw_read ( iqs62x - > regmap , IQS62X_SYS_FLAGS , event_map ,
sizeof ( event_map ) ) ;
if ( ret ) {
dev_err ( & client - > dev , " Failed to read device status: %d \n " ,
ret ) ;
return IRQ_NONE ;
}
for ( i = 0 ; i < sizeof ( event_map ) ; i + + ) {
event_reg = iqs62x - > dev_desc - > event_regs [ iqs62x - > ui_sel ] [ i ] ;
switch ( event_reg ) {
case IQS62X_EVENT_UI_LO :
event_data . ui_data = get_unaligned_le16 ( & event_map [ i ] ) ;
2020-08-24 01:36:59 +03:00
fallthrough ;
2020-02-17 02:32:06 +03:00
case IQS62X_EVENT_UI_HI :
case IQS62X_EVENT_NONE :
continue ;
case IQS62X_EVENT_ALS :
event_data . als_flags = event_map [ i ] ;
continue ;
case IQS62X_EVENT_IR :
event_data . ir_flags = event_map [ i ] ;
continue ;
case IQS62X_EVENT_INTER :
event_data . interval = event_map [ i ] ;
continue ;
case IQS62X_EVENT_HYST :
event_map [ i ] < < = iqs62x - > dev_desc - > hyst_shift ;
2020-08-24 01:36:59 +03:00
fallthrough ;
2020-02-17 02:32:06 +03:00
case IQS62X_EVENT_WHEEL :
case IQS62X_EVENT_HALL :
case IQS62X_EVENT_PROX :
case IQS62X_EVENT_SYS :
break ;
}
for ( j = 0 ; j < IQS62X_NUM_EVENTS ; j + + ) {
event_desc = iqs62x_events [ j ] ;
if ( event_desc . reg ! = event_reg )
continue ;
if ( ( event_map [ i ] & event_desc . mask ) = = event_desc . val )
event_flags | = BIT ( j ) ;
}
}
/*
* The device resets itself in response to the I2C master stalling
* communication past a fixed timeout . In this case , all registers
* are restored and any interested sub - device drivers are notified .
*/
if ( event_flags & BIT ( IQS62X_EVENT_SYS_RESET ) ) {
dev_err ( & client - > dev , " Unexpected device reset \n " ) ;
ret = iqs62x_dev_init ( iqs62x ) ;
if ( ret ) {
dev_err ( & client - > dev ,
" Failed to re-initialize device: %d \n " , ret ) ;
return IRQ_NONE ;
}
2021-01-18 06:57:07 +03:00
iqs62x - > event_cache | = BIT ( IQS62X_EVENT_SYS_RESET ) ;
reinit_completion ( & iqs62x - > ati_done ) ;
} else if ( event_flags & BIT ( IQS62X_EVENT_SYS_ATI ) ) {
iqs62x - > event_cache | = BIT ( IQS62X_EVENT_SYS_ATI ) ;
reinit_completion ( & iqs62x - > ati_done ) ;
} else if ( ! completion_done ( & iqs62x - > ati_done ) ) {
ret = regmap_update_bits ( iqs62x - > regmap , IQS62X_SYS_SETTINGS ,
IQS62X_SYS_SETTINGS_EVENT_MODE , 0xFF ) ;
if ( ret ) {
dev_err ( & client - > dev ,
" Failed to enable event mode: %d \n " , ret ) ;
return IRQ_NONE ;
}
msleep ( IQS62X_FILT_SETTLE_MS ) ;
complete_all ( & iqs62x - > ati_done ) ;
2020-02-17 02:32:06 +03:00
}
2021-01-18 06:57:07 +03:00
/*
* Reset and ATI events are not broadcast to the sub - device drivers
* until ATI has completed . Any other events that may have occurred
* during ATI are ignored .
*/
if ( completion_done ( & iqs62x - > ati_done ) ) {
event_flags | = iqs62x - > event_cache ;
ret = blocking_notifier_call_chain ( & iqs62x - > nh , event_flags ,
& event_data ) ;
if ( ret & NOTIFY_STOP_MASK )
return IRQ_NONE ;
iqs62x - > event_cache = 0 ;
}
2020-02-17 02:32:06 +03:00
/*
* Once the communication window is closed , a small delay is added to
* ensure the device ' s RDY output has been deasserted by the time the
* interrupt handler returns .
*/
2021-01-18 06:57:06 +03:00
usleep_range ( 150 , 200 ) ;
2020-02-17 02:32:06 +03:00
return IRQ_HANDLED ;
}
static void iqs62x_firmware_load ( const struct firmware * fw , void * context )
{
struct iqs62x_core * iqs62x = context ;
struct i2c_client * client = iqs62x - > client ;
int ret ;
if ( fw ) {
ret = iqs62x_firmware_parse ( iqs62x , fw ) ;
if ( ret ) {
dev_err ( & client - > dev , " Failed to parse firmware: %d \n " ,
ret ) ;
goto err_out ;
}
}
ret = iqs62x_dev_init ( iqs62x ) ;
if ( ret ) {
dev_err ( & client - > dev , " Failed to initialize device: %d \n " , ret ) ;
goto err_out ;
}
ret = devm_request_threaded_irq ( & client - > dev , client - > irq ,
NULL , iqs62x_irq , IRQF_ONESHOT ,
client - > name , iqs62x ) ;
if ( ret ) {
dev_err ( & client - > dev , " Failed to request IRQ: %d \n " , ret ) ;
goto err_out ;
}
2021-01-18 06:57:07 +03:00
if ( ! wait_for_completion_timeout ( & iqs62x - > ati_done ,
msecs_to_jiffies ( 2000 ) ) ) {
dev_err ( & client - > dev , " Failed to complete ATI \n " ) ;
goto err_out ;
}
2020-02-17 02:32:06 +03:00
ret = devm_mfd_add_devices ( & client - > dev , PLATFORM_DEVID_NONE ,
iqs62x - > dev_desc - > sub_devs ,
iqs62x - > dev_desc - > num_sub_devs ,
NULL , 0 , NULL ) ;
if ( ret )
dev_err ( & client - > dev , " Failed to add sub-devices: %d \n " , ret ) ;
err_out :
complete_all ( & iqs62x - > fw_done ) ;
}
static const struct mfd_cell iqs620at_sub_devs [ ] = {
{
. name = " iqs62x-keys " ,
. of_compatible = " azoteq,iqs620a-keys " ,
} ,
{
. name = " iqs620a-pwm " ,
. of_compatible = " azoteq,iqs620a-pwm " ,
} ,
{ . name = " iqs620at-temp " , } ,
} ;
static const struct mfd_cell iqs620a_sub_devs [ ] = {
{
. name = " iqs62x-keys " ,
. of_compatible = " azoteq,iqs620a-keys " ,
} ,
{
. name = " iqs620a-pwm " ,
. of_compatible = " azoteq,iqs620a-pwm " ,
} ,
} ;
static const struct mfd_cell iqs621_sub_devs [ ] = {
{
. name = " iqs62x-keys " ,
. of_compatible = " azoteq,iqs621-keys " ,
} ,
{ . name = " iqs621-als " , } ,
} ;
static const struct mfd_cell iqs622_sub_devs [ ] = {
{
. name = " iqs62x-keys " ,
. of_compatible = " azoteq,iqs622-keys " ,
} ,
{ . name = " iqs621-als " , } ,
} ;
static const struct mfd_cell iqs624_sub_devs [ ] = {
{
. name = " iqs62x-keys " ,
. of_compatible = " azoteq,iqs624-keys " ,
} ,
{ . name = " iqs624-pos " , } ,
} ;
static const struct mfd_cell iqs625_sub_devs [ ] = {
{
. name = " iqs62x-keys " ,
. of_compatible = " azoteq,iqs625-keys " ,
} ,
{ . name = " iqs624-pos " , } ,
} ;
static const u8 iqs620at_cal_regs [ ] = {
IQS620_TEMP_CAL_MULT ,
IQS620_TEMP_CAL_DIV ,
IQS620_TEMP_CAL_OFFS ,
} ;
static const u8 iqs621_cal_regs [ ] = {
IQS621_ALS_CAL_DIV_LUX ,
IQS621_ALS_CAL_DIV_IR ,
} ;
static const enum iqs62x_event_reg iqs620a_event_regs [ ] [ IQS62X_EVENT_SIZE ] = {
[ IQS62X_UI_PROX ] = {
IQS62X_EVENT_SYS , /* 0x10 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_PROX , /* 0x12 */
IQS62X_EVENT_HYST , /* 0x13 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
IQS62X_EVENT_HALL , /* 0x16 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
} ,
[ IQS62X_UI_SAR1 ] = {
IQS62X_EVENT_SYS , /* 0x10 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
IQS62X_EVENT_HYST , /* 0x13 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
IQS62X_EVENT_HALL , /* 0x16 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
} ,
} ;
static const enum iqs62x_event_reg iqs621_event_regs [ ] [ IQS62X_EVENT_SIZE ] = {
[ IQS62X_UI_PROX ] = {
IQS62X_EVENT_SYS , /* 0x10 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_PROX , /* 0x12 */
IQS62X_EVENT_HYST , /* 0x13 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
IQS62X_EVENT_ALS , /* 0x16 */
IQS62X_EVENT_UI_LO , /* 0x17 */
IQS62X_EVENT_UI_HI , /* 0x18 */
IQS62X_EVENT_HALL , /* 0x19 */
} ,
} ;
static const enum iqs62x_event_reg iqs622_event_regs [ ] [ IQS62X_EVENT_SIZE ] = {
[ IQS62X_UI_PROX ] = {
IQS62X_EVENT_SYS , /* 0x10 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_PROX , /* 0x12 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_ALS , /* 0x14 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_IR , /* 0x16 */
IQS62X_EVENT_UI_LO , /* 0x17 */
IQS62X_EVENT_UI_HI , /* 0x18 */
IQS62X_EVENT_HALL , /* 0x19 */
} ,
[ IQS62X_UI_SAR1 ] = {
IQS62X_EVENT_SYS , /* 0x10 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
IQS62X_EVENT_HYST , /* 0x13 */
IQS62X_EVENT_ALS , /* 0x14 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_IR , /* 0x16 */
IQS62X_EVENT_UI_LO , /* 0x17 */
IQS62X_EVENT_UI_HI , /* 0x18 */
IQS62X_EVENT_HALL , /* 0x19 */
} ,
} ;
static const enum iqs62x_event_reg iqs624_event_regs [ ] [ IQS62X_EVENT_SIZE ] = {
[ IQS62X_UI_PROX ] = {
IQS62X_EVENT_SYS , /* 0x10 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_PROX , /* 0x12 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_WHEEL , /* 0x14 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_UI_LO , /* 0x16 */
IQS62X_EVENT_UI_HI , /* 0x17 */
IQS62X_EVENT_INTER , /* 0x18 */
IQS62X_EVENT_NONE ,
} ,
} ;
static const enum iqs62x_event_reg iqs625_event_regs [ ] [ IQS62X_EVENT_SIZE ] = {
[ IQS62X_UI_PROX ] = {
IQS62X_EVENT_SYS , /* 0x10 */
IQS62X_EVENT_PROX , /* 0x11 */
IQS62X_EVENT_INTER , /* 0x12 */
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
IQS62X_EVENT_NONE ,
} ,
} ;
static const struct iqs62x_dev_desc iqs62x_devs [ ] = {
{
. dev_name = " iqs620at " ,
. sub_devs = iqs620at_sub_devs ,
. num_sub_devs = ARRAY_SIZE ( iqs620at_sub_devs ) ,
. prod_num = IQS620_PROD_NUM ,
. sw_num = 0x08 ,
. cal_regs = iqs620at_cal_regs ,
. num_cal_regs = ARRAY_SIZE ( iqs620at_cal_regs ) ,
. prox_mask = BIT ( 0 ) ,
. sar_mask = BIT ( 1 ) | BIT ( 7 ) ,
. hall_mask = BIT ( 2 ) ,
. hyst_mask = BIT ( 3 ) ,
. temp_mask = BIT ( 4 ) ,
. prox_settings = IQS620_PROX_SETTINGS_4 ,
. hall_flags = IQS620_HALL_FLAGS ,
. fw_name = " iqs620a.bin " ,
. event_regs = & iqs620a_event_regs [ IQS62X_UI_PROX ] ,
} ,
{
. dev_name = " iqs620a " ,
. sub_devs = iqs620a_sub_devs ,
. num_sub_devs = ARRAY_SIZE ( iqs620a_sub_devs ) ,
. prod_num = IQS620_PROD_NUM ,
. sw_num = 0x08 ,
. prox_mask = BIT ( 0 ) ,
. sar_mask = BIT ( 1 ) | BIT ( 7 ) ,
. hall_mask = BIT ( 2 ) ,
. hyst_mask = BIT ( 3 ) ,
. temp_mask = BIT ( 4 ) ,
. prox_settings = IQS620_PROX_SETTINGS_4 ,
. hall_flags = IQS620_HALL_FLAGS ,
. fw_name = " iqs620a.bin " ,
. event_regs = & iqs620a_event_regs [ IQS62X_UI_PROX ] ,
} ,
{
. dev_name = " iqs621 " ,
. sub_devs = iqs621_sub_devs ,
. num_sub_devs = ARRAY_SIZE ( iqs621_sub_devs ) ,
. prod_num = IQS621_PROD_NUM ,
. sw_num = 0x09 ,
. cal_regs = iqs621_cal_regs ,
. num_cal_regs = ARRAY_SIZE ( iqs621_cal_regs ) ,
. prox_mask = BIT ( 0 ) ,
. hall_mask = BIT ( 1 ) ,
. als_mask = BIT ( 2 ) ,
. hyst_mask = BIT ( 3 ) ,
. temp_mask = BIT ( 4 ) ,
. als_flags = IQS621_ALS_FLAGS ,
. hall_flags = IQS621_HALL_FLAGS ,
. hyst_shift = 5 ,
. fw_name = " iqs621.bin " ,
. event_regs = & iqs621_event_regs [ IQS62X_UI_PROX ] ,
} ,
{
. dev_name = " iqs622 " ,
. sub_devs = iqs622_sub_devs ,
. num_sub_devs = ARRAY_SIZE ( iqs622_sub_devs ) ,
. prod_num = IQS622_PROD_NUM ,
. sw_num = 0x06 ,
. prox_mask = BIT ( 0 ) ,
. sar_mask = BIT ( 1 ) ,
. hall_mask = BIT ( 2 ) ,
. als_mask = BIT ( 3 ) ,
. ir_mask = BIT ( 4 ) ,
. prox_settings = IQS622_PROX_SETTINGS_4 ,
. als_flags = IQS622_ALS_FLAGS ,
. hall_flags = IQS622_HALL_FLAGS ,
. fw_name = " iqs622.bin " ,
. event_regs = & iqs622_event_regs [ IQS62X_UI_PROX ] ,
} ,
{
. dev_name = " iqs624 " ,
. sub_devs = iqs624_sub_devs ,
. num_sub_devs = ARRAY_SIZE ( iqs624_sub_devs ) ,
. prod_num = IQS624_PROD_NUM ,
. sw_num = 0x0B ,
. interval = IQS624_INTERVAL_NUM ,
. interval_div = 3 ,
. fw_name = " iqs624.bin " ,
. event_regs = & iqs624_event_regs [ IQS62X_UI_PROX ] ,
} ,
{
. dev_name = " iqs625 " ,
. sub_devs = iqs625_sub_devs ,
. num_sub_devs = ARRAY_SIZE ( iqs625_sub_devs ) ,
. prod_num = IQS625_PROD_NUM ,
. sw_num = 0x0B ,
. interval = IQS625_INTERVAL_NUM ,
. interval_div = 10 ,
. fw_name = " iqs625.bin " ,
. event_regs = & iqs625_event_regs [ IQS62X_UI_PROX ] ,
} ,
} ;
2021-01-18 06:57:05 +03:00
static const struct regmap_config iqs62x_regmap_config = {
2020-02-17 02:32:06 +03:00
. reg_bits = 8 ,
. val_bits = 8 ,
. max_register = IQS62X_MAX_REG ,
} ;
static int iqs62x_probe ( struct i2c_client * client )
{
struct iqs62x_core * iqs62x ;
struct iqs62x_info info ;
unsigned int val ;
int ret , i , j ;
const char * fw_name = NULL ;
iqs62x = devm_kzalloc ( & client - > dev , sizeof ( * iqs62x ) , GFP_KERNEL ) ;
if ( ! iqs62x )
return - ENOMEM ;
i2c_set_clientdata ( client , iqs62x ) ;
iqs62x - > client = client ;
BLOCKING_INIT_NOTIFIER_HEAD ( & iqs62x - > nh ) ;
INIT_LIST_HEAD ( & iqs62x - > fw_blk_head ) ;
2021-01-18 06:57:07 +03:00
init_completion ( & iqs62x - > ati_done ) ;
2020-02-17 02:32:06 +03:00
init_completion ( & iqs62x - > fw_done ) ;
2021-01-18 06:57:05 +03:00
iqs62x - > regmap = devm_regmap_init_i2c ( client , & iqs62x_regmap_config ) ;
2020-02-17 02:32:06 +03:00
if ( IS_ERR ( iqs62x - > regmap ) ) {
ret = PTR_ERR ( iqs62x - > regmap ) ;
dev_err ( & client - > dev , " Failed to initialize register map: %d \n " ,
ret ) ;
return ret ;
}
ret = regmap_raw_read ( iqs62x - > regmap , IQS62X_PROD_NUM , & info ,
sizeof ( info ) ) ;
if ( ret )
return ret ;
/*
* The following sequence validates the device ' s product and software
* numbers . It then determines if the device is factory - calibrated by
* checking for nonzero values in the device ' s designated calibration
* registers ( if applicable ) . Depending on the device , the absence of
* calibration data indicates a reduced feature set or invalid device .
*
* For devices given in both calibrated and uncalibrated versions , the
* calibrated version ( e . g . IQS620AT ) appears first in the iqs62x_devs
* array . The uncalibrated version ( e . g . IQS620A ) appears next and has
* the same product and software numbers , but no calibration registers
* are specified .
*/
for ( i = 0 ; i < ARRAY_SIZE ( iqs62x_devs ) ; i + + ) {
if ( info . prod_num ! = iqs62x_devs [ i ] . prod_num )
continue ;
iqs62x - > dev_desc = & iqs62x_devs [ i ] ;
if ( info . sw_num < iqs62x - > dev_desc - > sw_num )
continue ;
2022-01-23 22:01:05 +03:00
iqs62x - > sw_num = info . sw_num ;
iqs62x - > hw_num = info . hw_num ;
2020-02-17 02:32:06 +03:00
/*
* Read each of the device ' s designated calibration registers ,
* if any , and exit from the inner loop early if any are equal
* to zero ( indicating the device is uncalibrated ) . This could
* be acceptable depending on the device ( e . g . IQS620A instead
* of IQS620AT ) .
*/
for ( j = 0 ; j < iqs62x - > dev_desc - > num_cal_regs ; j + + ) {
ret = regmap_read ( iqs62x - > regmap ,
iqs62x - > dev_desc - > cal_regs [ j ] , & val ) ;
if ( ret )
return ret ;
if ( ! val )
break ;
}
/*
* If the number of nonzero values read from the device equals
* the number of designated calibration registers ( which could
* be zero ) , exit from the outer loop early to signal that the
* device ' s product and software numbers match a known device ,
* and the device is calibrated ( if applicable ) .
*/
if ( j = = iqs62x - > dev_desc - > num_cal_regs )
break ;
}
if ( ! iqs62x - > dev_desc ) {
dev_err ( & client - > dev , " Unrecognized product number: 0x%02X \n " ,
info . prod_num ) ;
return - EINVAL ;
}
2022-01-23 22:01:05 +03:00
if ( ! iqs62x - > sw_num ) {
2020-02-17 02:32:06 +03:00
dev_err ( & client - > dev , " Unrecognized software number: 0x%02X \n " ,
info . sw_num ) ;
return - EINVAL ;
}
if ( i = = ARRAY_SIZE ( iqs62x_devs ) ) {
dev_err ( & client - > dev , " Uncalibrated device \n " ) ;
return - ENODATA ;
}
device_property_read_string ( & client - > dev , " firmware-name " , & fw_name ) ;
2021-04-25 05:00:24 +03:00
ret = request_firmware_nowait ( THIS_MODULE , FW_ACTION_UEVENT ,
2020-02-17 02:32:06 +03:00
fw_name ? : iqs62x - > dev_desc - > fw_name ,
& client - > dev , GFP_KERNEL , iqs62x ,
iqs62x_firmware_load ) ;
if ( ret )
dev_err ( & client - > dev , " Failed to request firmware: %d \n " , ret ) ;
return ret ;
}
2022-08-15 11:02:30 +03:00
static void iqs62x_remove ( struct i2c_client * client )
2020-02-17 02:32:06 +03:00
{
struct iqs62x_core * iqs62x = i2c_get_clientdata ( client ) ;
wait_for_completion ( & iqs62x - > fw_done ) ;
}
static int __maybe_unused iqs62x_suspend ( struct device * dev )
{
struct iqs62x_core * iqs62x = dev_get_drvdata ( dev ) ;
int ret ;
wait_for_completion ( & iqs62x - > fw_done ) ;
/*
* As per the datasheet , automatic mode switching must be disabled
* before the device is placed in or taken out of halt mode .
*/
ret = regmap_update_bits ( iqs62x - > regmap , IQS62X_PWR_SETTINGS ,
IQS62X_PWR_SETTINGS_DIS_AUTO , 0xFF ) ;
if ( ret )
return ret ;
return regmap_update_bits ( iqs62x - > regmap , IQS62X_PWR_SETTINGS ,
IQS62X_PWR_SETTINGS_PWR_MODE_MASK ,
IQS62X_PWR_SETTINGS_PWR_MODE_HALT ) ;
}
static int __maybe_unused iqs62x_resume ( struct device * dev )
{
struct iqs62x_core * iqs62x = dev_get_drvdata ( dev ) ;
int ret ;
ret = regmap_update_bits ( iqs62x - > regmap , IQS62X_PWR_SETTINGS ,
IQS62X_PWR_SETTINGS_PWR_MODE_MASK ,
IQS62X_PWR_SETTINGS_PWR_MODE_NORM ) ;
if ( ret )
return ret ;
return regmap_update_bits ( iqs62x - > regmap , IQS62X_PWR_SETTINGS ,
IQS62X_PWR_SETTINGS_DIS_AUTO , 0 ) ;
}
static SIMPLE_DEV_PM_OPS ( iqs62x_pm , iqs62x_suspend , iqs62x_resume ) ;
static const struct of_device_id iqs62x_of_match [ ] = {
{ . compatible = " azoteq,iqs620a " } ,
{ . compatible = " azoteq,iqs621 " } ,
{ . compatible = " azoteq,iqs622 " } ,
{ . compatible = " azoteq,iqs624 " } ,
{ . compatible = " azoteq,iqs625 " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , iqs62x_of_match ) ;
static struct i2c_driver iqs62x_i2c_driver = {
. driver = {
. name = " iqs62x " ,
. of_match_table = iqs62x_of_match ,
. pm = & iqs62x_pm ,
} ,
2023-05-15 21:27:52 +03:00
. probe = iqs62x_probe ,
2020-02-17 02:32:06 +03:00
. remove = iqs62x_remove ,
} ;
module_i2c_driver ( iqs62x_i2c_driver ) ;
MODULE_AUTHOR ( " Jeff LaBundy <jeff@labundy.com> " ) ;
MODULE_DESCRIPTION ( " Azoteq IQS620A/621/622/624/625 Multi-Function Sensors " ) ;
MODULE_LICENSE ( " GPL " ) ;