// 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.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/unaligned.h>
#define IQS62X_PROD_NUM 0 x00
#define IQS62X_SYS_FLAGS 0 x10
#define IQS620_HALL_FLAGS 0 x16
#define IQS621_HALL_FLAGS 0 x19
#define IQS622_HALL_FLAGS IQS621_HALL_FLAGS
#define IQS624_INTERVAL_NUM 0 x18
#define IQS625_INTERVAL_NUM 0 x12
#define IQS622_PROX_SETTINGS_4 0 x48
#define IQS620_PROX_SETTINGS_4 0 x50
#define IQS620_PROX_SETTINGS_4_SAR_EN BIT(7 )
#define IQS621_ALS_CAL_DIV_LUX 0 x82
#define IQS621_ALS_CAL_DIV_IR 0 x83
#define IQS620_TEMP_CAL_MULT 0 xC2
#define IQS620_TEMP_CAL_DIV 0 xC3
#define IQS620_TEMP_CAL_OFFS 0 xC4
#define IQS62X_SYS_SETTINGS 0 xD0
#define IQS62X_SYS_SETTINGS_ACK_RESET BIT(6 )
#define IQS62X_SYS_SETTINGS_EVENT_MODE BIT(5 )
#define IQS62X_SYS_SETTINGS_CLK_DIV BIT(4 )
#define IQS62X_SYS_SETTINGS_COMM_ATI BIT(3 )
#define IQS62X_SYS_SETTINGS_REDO_ATI BIT(1 )
#define IQS62X_PWR_SETTINGS 0 xD2
#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 0 xF0
#define IQS62X_OTP_CMD_FG3 0 x13
#define IQS62X_OTP_DATA 0 xF1
#define IQS62X_MAX_REG 0 xFF
#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
#define IQS62X_ATI_STARTUP_MS 350
#define IQS62X_FILT_SETTLE_MS 250
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[] __counted_by(len);
};
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) {
/*
* 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);
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;
fallthrough;
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;
}
/*
* 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);
if (ret)
return ret;
/*
* 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 );
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 ),
},
[IQS62X_EVENT_SYS_ATI] = {
.reg = IQS62X_EVENT_SYS,
.mask = BIT(2 ),
.val = BIT(2 ),
},
};
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]);
fallthrough;
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;
fallthrough;
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;
}
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, 0 xFF);
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);
}
/*
* 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 ;
}
/*
* 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 .
*/
usleep_range(150 , 200 );
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;
}
if (!wait_for_completion_timeout(&iqs62x->ati_done,
msecs_to_jiffies(2000 ))) {
dev_err(&client->dev, "Failed to complete ATI\n" );
goto err_out;
}
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 = 0 x08,
.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 = 0 x08,
.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 = 0 x09,
.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 = 0 x06,
.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 = 0 x0B,
.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 = 0 x0B,
.interval = IQS625_INTERVAL_NUM,
.interval_div = 10 ,
.fw_name = "iqs625.bin" ,
.event_regs = &iqs625_event_regs[IQS62X_UI_PROX],
},
};
static const struct regmap_config iqs62x_regmap_config = {
.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);
init_completion(&iqs62x->ati_done);
init_completion(&iqs62x->fw_done);
iqs62x->regmap = devm_regmap_init_i2c(client, &iqs62x_regmap_config);
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 ;
iqs62x->sw_num = info.sw_num;
iqs62x->hw_num = info.hw_num;
/*
* 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;
}
if (!iqs62x->sw_num) {
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);
ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
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;
}
static void iqs62x_remove(struct i2c_client *client)
{
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, 0 xFF);
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,
},
.probe = iqs62x_probe,
.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" );
Messung V0.5 in Prozent C=96 H=94 G=94
¤ Dauer der Verarbeitung: 0.6 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland