With commit 312c004d36 ("[PATCH] driver core: replace "hotplug" by
"uevent"") already in the tree over a decade, update the name of
FW_ACTION defines to follow semantics, and reflect what the defines are
really meant for, i.e. whether or not generate user space event.
Acked-by: Lee Jones <lee.jones@linaro.org>
Signed-off-by: Shawn Guo <shawn.guo@linaro.org>
Link: https://lore.kernel.org/r/20210425020024.28057-1-shawn.guo@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
		
	
			
		
			
				
	
	
		
			1082 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1082 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// 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)
 | 
						|
#define IQS62X_SYS_SETTINGS_COMM_ATI		BIT(3)
 | 
						|
#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_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[];
 | 
						|
};
 | 
						|
 | 
						|
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, 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);
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * 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		= 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],
 | 
						|
	},
 | 
						|
};
 | 
						|
 | 
						|
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;
 | 
						|
	u8 sw_num = 0;
 | 
						|
	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;
 | 
						|
 | 
						|
		sw_num = info.sw_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 (!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 int iqs62x_remove(struct i2c_client *client)
 | 
						|
{
 | 
						|
	struct iqs62x_core *iqs62x = i2c_get_clientdata(client);
 | 
						|
 | 
						|
	wait_for_completion(&iqs62x->fw_done);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
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,
 | 
						|
	},
 | 
						|
	.probe_new = 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");
 |