// 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 IQS62X_SYS_FLAGS_IN_ATI BIT(2)
#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_SOFT_RESET BIT(7)
#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_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
#define IQS62X_ATI_POLL_SLEEP_US 10000
#define IQS62X_ATI_POLL_TIMEOUT_US 500000
#define IQS62X_ATI_STABLE_DELAY_MS 150
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;
u8 clk_div = 1;
list_for_each_entry(fw_blk, &iqs62x->fw_blk_head, list) {
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;
/* fall through */
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;
}
ret = regmap_read(iqs62x->regmap, IQS62X_SYS_SETTINGS, &val);
if (ret)
return ret;
if (val & IQS62X_SYS_SETTINGS_CLK_DIV)
<