xref: /openbmc/linux/drivers/mfd/iqs62x.c (revision dc0c386e)
14d9cf7dfSJeff LaBundy // SPDX-License-Identifier: GPL-2.0+
24d9cf7dfSJeff LaBundy /*
34d9cf7dfSJeff LaBundy  * Azoteq IQS620A/621/622/624/625 Multi-Function Sensors
44d9cf7dfSJeff LaBundy  *
54d9cf7dfSJeff LaBundy  * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
64d9cf7dfSJeff LaBundy  *
74d9cf7dfSJeff LaBundy  * These devices rely on application-specific register settings and calibration
84d9cf7dfSJeff LaBundy  * data developed in and exported from a suite of GUIs offered by the vendor. A
94d9cf7dfSJeff LaBundy  * separate tool converts the GUIs' ASCII-based output into a standard firmware
104d9cf7dfSJeff LaBundy  * file parsed by the driver.
114d9cf7dfSJeff LaBundy  *
124d9cf7dfSJeff LaBundy  * Link to datasheets and GUIs: https://www.azoteq.com/
134d9cf7dfSJeff LaBundy  *
144d9cf7dfSJeff LaBundy  * Link to conversion tool: https://github.com/jlabundy/iqs62x-h2bin.git
154d9cf7dfSJeff LaBundy  */
164d9cf7dfSJeff LaBundy 
174d9cf7dfSJeff LaBundy #include <linux/completion.h>
184d9cf7dfSJeff LaBundy #include <linux/delay.h>
194d9cf7dfSJeff LaBundy #include <linux/device.h>
204d9cf7dfSJeff LaBundy #include <linux/err.h>
214d9cf7dfSJeff LaBundy #include <linux/firmware.h>
224d9cf7dfSJeff LaBundy #include <linux/i2c.h>
234d9cf7dfSJeff LaBundy #include <linux/interrupt.h>
244d9cf7dfSJeff LaBundy #include <linux/kernel.h>
254d9cf7dfSJeff LaBundy #include <linux/list.h>
264d9cf7dfSJeff LaBundy #include <linux/mfd/core.h>
274d9cf7dfSJeff LaBundy #include <linux/mfd/iqs62x.h>
284d9cf7dfSJeff LaBundy #include <linux/module.h>
294d9cf7dfSJeff LaBundy #include <linux/notifier.h>
30*dc0c386eSRob Herring #include <linux/of.h>
314d9cf7dfSJeff LaBundy #include <linux/property.h>
324d9cf7dfSJeff LaBundy #include <linux/regmap.h>
334d9cf7dfSJeff LaBundy #include <linux/slab.h>
344d9cf7dfSJeff LaBundy #include <asm/unaligned.h>
354d9cf7dfSJeff LaBundy 
364d9cf7dfSJeff LaBundy #define IQS62X_PROD_NUM				0x00
374d9cf7dfSJeff LaBundy 
384d9cf7dfSJeff LaBundy #define IQS62X_SYS_FLAGS			0x10
394d9cf7dfSJeff LaBundy 
404d9cf7dfSJeff LaBundy #define IQS620_HALL_FLAGS			0x16
414d9cf7dfSJeff LaBundy #define IQS621_HALL_FLAGS			0x19
424d9cf7dfSJeff LaBundy #define IQS622_HALL_FLAGS			IQS621_HALL_FLAGS
434d9cf7dfSJeff LaBundy 
444d9cf7dfSJeff LaBundy #define IQS624_INTERVAL_NUM			0x18
454d9cf7dfSJeff LaBundy #define IQS625_INTERVAL_NUM			0x12
464d9cf7dfSJeff LaBundy 
474d9cf7dfSJeff LaBundy #define IQS622_PROX_SETTINGS_4			0x48
484d9cf7dfSJeff LaBundy #define IQS620_PROX_SETTINGS_4			0x50
494d9cf7dfSJeff LaBundy #define IQS620_PROX_SETTINGS_4_SAR_EN		BIT(7)
504d9cf7dfSJeff LaBundy 
514d9cf7dfSJeff LaBundy #define IQS621_ALS_CAL_DIV_LUX			0x82
524d9cf7dfSJeff LaBundy #define IQS621_ALS_CAL_DIV_IR			0x83
534d9cf7dfSJeff LaBundy 
544d9cf7dfSJeff LaBundy #define IQS620_TEMP_CAL_MULT			0xC2
554d9cf7dfSJeff LaBundy #define IQS620_TEMP_CAL_DIV			0xC3
564d9cf7dfSJeff LaBundy #define IQS620_TEMP_CAL_OFFS			0xC4
574d9cf7dfSJeff LaBundy 
584d9cf7dfSJeff LaBundy #define IQS62X_SYS_SETTINGS			0xD0
594d9cf7dfSJeff LaBundy #define IQS62X_SYS_SETTINGS_ACK_RESET		BIT(6)
604d9cf7dfSJeff LaBundy #define IQS62X_SYS_SETTINGS_EVENT_MODE		BIT(5)
614d9cf7dfSJeff LaBundy #define IQS62X_SYS_SETTINGS_CLK_DIV		BIT(4)
6202e550d5SJeff LaBundy #define IQS62X_SYS_SETTINGS_COMM_ATI		BIT(3)
634d9cf7dfSJeff LaBundy #define IQS62X_SYS_SETTINGS_REDO_ATI		BIT(1)
644d9cf7dfSJeff LaBundy 
654d9cf7dfSJeff LaBundy #define IQS62X_PWR_SETTINGS			0xD2
664d9cf7dfSJeff LaBundy #define IQS62X_PWR_SETTINGS_DIS_AUTO		BIT(5)
674d9cf7dfSJeff LaBundy #define IQS62X_PWR_SETTINGS_PWR_MODE_MASK	(BIT(4) | BIT(3))
684d9cf7dfSJeff LaBundy #define IQS62X_PWR_SETTINGS_PWR_MODE_HALT	(BIT(4) | BIT(3))
694d9cf7dfSJeff LaBundy #define IQS62X_PWR_SETTINGS_PWR_MODE_NORM	0
704d9cf7dfSJeff LaBundy 
714d9cf7dfSJeff LaBundy #define IQS62X_OTP_CMD				0xF0
724d9cf7dfSJeff LaBundy #define IQS62X_OTP_CMD_FG3			0x13
734d9cf7dfSJeff LaBundy #define IQS62X_OTP_DATA				0xF1
744d9cf7dfSJeff LaBundy #define IQS62X_MAX_REG				0xFF
754d9cf7dfSJeff LaBundy 
764d9cf7dfSJeff LaBundy #define IQS62X_HALL_CAL_MASK			GENMASK(3, 0)
774d9cf7dfSJeff LaBundy 
784d9cf7dfSJeff LaBundy #define IQS62X_FW_REC_TYPE_INFO			0
794d9cf7dfSJeff LaBundy #define IQS62X_FW_REC_TYPE_PROD			1
804d9cf7dfSJeff LaBundy #define IQS62X_FW_REC_TYPE_HALL			2
814d9cf7dfSJeff LaBundy #define IQS62X_FW_REC_TYPE_MASK			3
824d9cf7dfSJeff LaBundy #define IQS62X_FW_REC_TYPE_DATA			4
834d9cf7dfSJeff LaBundy 
846a8fac01SJeff LaBundy #define IQS62X_ATI_STARTUP_MS			350
8502e550d5SJeff LaBundy #define IQS62X_FILT_SETTLE_MS			250
864d9cf7dfSJeff LaBundy 
874d9cf7dfSJeff LaBundy struct iqs62x_fw_rec {
884d9cf7dfSJeff LaBundy 	u8 type;
894d9cf7dfSJeff LaBundy 	u8 addr;
904d9cf7dfSJeff LaBundy 	u8 len;
914d9cf7dfSJeff LaBundy 	u8 data;
924d9cf7dfSJeff LaBundy } __packed;
934d9cf7dfSJeff LaBundy 
944d9cf7dfSJeff LaBundy struct iqs62x_fw_blk {
954d9cf7dfSJeff LaBundy 	struct list_head list;
964d9cf7dfSJeff LaBundy 	u8 addr;
974d9cf7dfSJeff LaBundy 	u8 mask;
984d9cf7dfSJeff LaBundy 	u8 len;
994d9cf7dfSJeff LaBundy 	u8 data[];
1004d9cf7dfSJeff LaBundy };
1014d9cf7dfSJeff LaBundy 
1024d9cf7dfSJeff LaBundy struct iqs62x_info {
1034d9cf7dfSJeff LaBundy 	u8 prod_num;
1044d9cf7dfSJeff LaBundy 	u8 sw_num;
1054d9cf7dfSJeff LaBundy 	u8 hw_num;
1064d9cf7dfSJeff LaBundy } __packed;
1074d9cf7dfSJeff LaBundy 
iqs62x_dev_init(struct iqs62x_core * iqs62x)1084d9cf7dfSJeff LaBundy static int iqs62x_dev_init(struct iqs62x_core *iqs62x)
1094d9cf7dfSJeff LaBundy {
1104d9cf7dfSJeff LaBundy 	struct iqs62x_fw_blk *fw_blk;
1114d9cf7dfSJeff LaBundy 	unsigned int val;
1124d9cf7dfSJeff LaBundy 	int ret;
1134d9cf7dfSJeff LaBundy 
1144d9cf7dfSJeff LaBundy 	list_for_each_entry(fw_blk, &iqs62x->fw_blk_head, list) {
1156a8fac01SJeff LaBundy 		/*
1166a8fac01SJeff LaBundy 		 * In case ATI is in progress, wait for it to complete before
1176a8fac01SJeff LaBundy 		 * lowering the core clock frequency.
1186a8fac01SJeff LaBundy 		 */
1196a8fac01SJeff LaBundy 		if (fw_blk->addr == IQS62X_SYS_SETTINGS &&
1206a8fac01SJeff LaBundy 		    *fw_blk->data & IQS62X_SYS_SETTINGS_CLK_DIV)
1216a8fac01SJeff LaBundy 			msleep(IQS62X_ATI_STARTUP_MS);
1226a8fac01SJeff LaBundy 
1234d9cf7dfSJeff LaBundy 		if (fw_blk->mask)
1244d9cf7dfSJeff LaBundy 			ret = regmap_update_bits(iqs62x->regmap, fw_blk->addr,
1254d9cf7dfSJeff LaBundy 						 fw_blk->mask, *fw_blk->data);
1264d9cf7dfSJeff LaBundy 		else
1274d9cf7dfSJeff LaBundy 			ret = regmap_raw_write(iqs62x->regmap, fw_blk->addr,
1284d9cf7dfSJeff LaBundy 					       fw_blk->data, fw_blk->len);
1294d9cf7dfSJeff LaBundy 		if (ret)
1304d9cf7dfSJeff LaBundy 			return ret;
1314d9cf7dfSJeff LaBundy 	}
1324d9cf7dfSJeff LaBundy 
1334d9cf7dfSJeff LaBundy 	switch (iqs62x->dev_desc->prod_num) {
1344d9cf7dfSJeff LaBundy 	case IQS620_PROD_NUM:
1354d9cf7dfSJeff LaBundy 	case IQS622_PROD_NUM:
1364d9cf7dfSJeff LaBundy 		ret = regmap_read(iqs62x->regmap,
1374d9cf7dfSJeff LaBundy 				  iqs62x->dev_desc->prox_settings, &val);
1384d9cf7dfSJeff LaBundy 		if (ret)
1394d9cf7dfSJeff LaBundy 			return ret;
1404d9cf7dfSJeff LaBundy 
1414d9cf7dfSJeff LaBundy 		if (val & IQS620_PROX_SETTINGS_4_SAR_EN)
1424d9cf7dfSJeff LaBundy 			iqs62x->ui_sel = IQS62X_UI_SAR1;
143df561f66SGustavo A. R. Silva 		fallthrough;
1444d9cf7dfSJeff LaBundy 
1454d9cf7dfSJeff LaBundy 	case IQS621_PROD_NUM:
1464d9cf7dfSJeff LaBundy 		ret = regmap_write(iqs62x->regmap, IQS620_GLBL_EVENT_MASK,
1474d9cf7dfSJeff LaBundy 				   IQS620_GLBL_EVENT_MASK_PMU |
1484d9cf7dfSJeff LaBundy 				   iqs62x->dev_desc->prox_mask |
1494d9cf7dfSJeff LaBundy 				   iqs62x->dev_desc->sar_mask |
1504d9cf7dfSJeff LaBundy 				   iqs62x->dev_desc->hall_mask |
1514d9cf7dfSJeff LaBundy 				   iqs62x->dev_desc->hyst_mask |
1524d9cf7dfSJeff LaBundy 				   iqs62x->dev_desc->temp_mask |
1534d9cf7dfSJeff LaBundy 				   iqs62x->dev_desc->als_mask |
1544d9cf7dfSJeff LaBundy 				   iqs62x->dev_desc->ir_mask);
1554d9cf7dfSJeff LaBundy 		if (ret)
1564d9cf7dfSJeff LaBundy 			return ret;
1574d9cf7dfSJeff LaBundy 		break;
1584d9cf7dfSJeff LaBundy 
1594d9cf7dfSJeff LaBundy 	default:
1604d9cf7dfSJeff LaBundy 		ret = regmap_write(iqs62x->regmap, IQS624_HALL_UI,
1614d9cf7dfSJeff LaBundy 				   IQS624_HALL_UI_WHL_EVENT |
1624d9cf7dfSJeff LaBundy 				   IQS624_HALL_UI_INT_EVENT |
1634d9cf7dfSJeff LaBundy 				   IQS624_HALL_UI_AUTO_CAL);
1644d9cf7dfSJeff LaBundy 		if (ret)
1654d9cf7dfSJeff LaBundy 			return ret;
1664d9cf7dfSJeff LaBundy 
1674d9cf7dfSJeff LaBundy 		/*
1684d9cf7dfSJeff LaBundy 		 * The IQS625 default interval divider is below the minimum
1694d9cf7dfSJeff LaBundy 		 * permissible value, and the datasheet mandates that it is
1704d9cf7dfSJeff LaBundy 		 * corrected during initialization (unless an updated value
1714d9cf7dfSJeff LaBundy 		 * has already been provided by firmware).
1724d9cf7dfSJeff LaBundy 		 *
1734d9cf7dfSJeff LaBundy 		 * To protect against an unacceptably low user-entered value
1744d9cf7dfSJeff LaBundy 		 * stored in the firmware, the same check is extended to the
1754d9cf7dfSJeff LaBundy 		 * IQS624 as well.
1764d9cf7dfSJeff LaBundy 		 */
1774d9cf7dfSJeff LaBundy 		ret = regmap_read(iqs62x->regmap, IQS624_INTERVAL_DIV, &val);
1784d9cf7dfSJeff LaBundy 		if (ret)
1794d9cf7dfSJeff LaBundy 			return ret;
1804d9cf7dfSJeff LaBundy 
1814d9cf7dfSJeff LaBundy 		if (val >= iqs62x->dev_desc->interval_div)
1824d9cf7dfSJeff LaBundy 			break;
1834d9cf7dfSJeff LaBundy 
1844d9cf7dfSJeff LaBundy 		ret = regmap_write(iqs62x->regmap, IQS624_INTERVAL_DIV,
1854d9cf7dfSJeff LaBundy 				   iqs62x->dev_desc->interval_div);
1864d9cf7dfSJeff LaBundy 		if (ret)
1874d9cf7dfSJeff LaBundy 			return ret;
1884d9cf7dfSJeff LaBundy 	}
1894d9cf7dfSJeff LaBundy 
19002e550d5SJeff LaBundy 	/*
19102e550d5SJeff LaBundy 	 * Place the device in streaming mode at first so as not to miss the
19202e550d5SJeff LaBundy 	 * limited number of interrupts that would otherwise occur after ATI
19302e550d5SJeff LaBundy 	 * completes. The device is subsequently placed in event mode by the
19402e550d5SJeff LaBundy 	 * interrupt handler.
19502e550d5SJeff LaBundy 	 *
19602e550d5SJeff LaBundy 	 * In the meantime, mask interrupts during ATI to prevent the device
19702e550d5SJeff LaBundy 	 * from soliciting I2C traffic until the noise-sensitive ATI process
19802e550d5SJeff LaBundy 	 * is complete.
19902e550d5SJeff LaBundy 	 */
20002e550d5SJeff LaBundy 	ret = regmap_update_bits(iqs62x->regmap, IQS62X_SYS_SETTINGS,
2014d9cf7dfSJeff LaBundy 				 IQS62X_SYS_SETTINGS_ACK_RESET |
2024d9cf7dfSJeff LaBundy 				 IQS62X_SYS_SETTINGS_EVENT_MODE |
20302e550d5SJeff LaBundy 				 IQS62X_SYS_SETTINGS_COMM_ATI |
20402e550d5SJeff LaBundy 				 IQS62X_SYS_SETTINGS_REDO_ATI,
20502e550d5SJeff LaBundy 				 IQS62X_SYS_SETTINGS_ACK_RESET |
2064d9cf7dfSJeff LaBundy 				 IQS62X_SYS_SETTINGS_REDO_ATI);
2074d9cf7dfSJeff LaBundy 	if (ret)
2084d9cf7dfSJeff LaBundy 		return ret;
2094d9cf7dfSJeff LaBundy 
21002e550d5SJeff LaBundy 	/*
21102e550d5SJeff LaBundy 	 * The following delay gives the device time to deassert its RDY output
21202e550d5SJeff LaBundy 	 * in case a communication window was open while the REDO_ATI field was
21302e550d5SJeff LaBundy 	 * written. This prevents an interrupt from being serviced prematurely.
21402e550d5SJeff LaBundy 	 */
21502e550d5SJeff LaBundy 	usleep_range(5000, 5100);
2164d9cf7dfSJeff LaBundy 
2174d9cf7dfSJeff LaBundy 	return 0;
2184d9cf7dfSJeff LaBundy }
2194d9cf7dfSJeff LaBundy 
iqs62x_firmware_parse(struct iqs62x_core * iqs62x,const struct firmware * fw)2204d9cf7dfSJeff LaBundy static int iqs62x_firmware_parse(struct iqs62x_core *iqs62x,
2214d9cf7dfSJeff LaBundy 				 const struct firmware *fw)
2224d9cf7dfSJeff LaBundy {
2234d9cf7dfSJeff LaBundy 	struct i2c_client *client = iqs62x->client;
2244d9cf7dfSJeff LaBundy 	struct iqs62x_fw_rec *fw_rec;
2254d9cf7dfSJeff LaBundy 	struct iqs62x_fw_blk *fw_blk;
2264d9cf7dfSJeff LaBundy 	unsigned int val;
2274d9cf7dfSJeff LaBundy 	size_t pos = 0;
2284d9cf7dfSJeff LaBundy 	int ret = 0;
2294d9cf7dfSJeff LaBundy 	u8 mask, len, *data;
2304d9cf7dfSJeff LaBundy 	u8 hall_cal_index = 0;
2314d9cf7dfSJeff LaBundy 
2324d9cf7dfSJeff LaBundy 	while (pos < fw->size) {
2334d9cf7dfSJeff LaBundy 		if (pos + sizeof(*fw_rec) > fw->size) {
2344d9cf7dfSJeff LaBundy 			ret = -EINVAL;
2354d9cf7dfSJeff LaBundy 			break;
2364d9cf7dfSJeff LaBundy 		}
2374d9cf7dfSJeff LaBundy 		fw_rec = (struct iqs62x_fw_rec *)(fw->data + pos);
2384d9cf7dfSJeff LaBundy 		pos += sizeof(*fw_rec);
2394d9cf7dfSJeff LaBundy 
2404d9cf7dfSJeff LaBundy 		if (pos + fw_rec->len - 1 > fw->size) {
2414d9cf7dfSJeff LaBundy 			ret = -EINVAL;
2424d9cf7dfSJeff LaBundy 			break;
2434d9cf7dfSJeff LaBundy 		}
2444d9cf7dfSJeff LaBundy 		pos += fw_rec->len - 1;
2454d9cf7dfSJeff LaBundy 
2464d9cf7dfSJeff LaBundy 		switch (fw_rec->type) {
2474d9cf7dfSJeff LaBundy 		case IQS62X_FW_REC_TYPE_INFO:
2484d9cf7dfSJeff LaBundy 			continue;
2494d9cf7dfSJeff LaBundy 
2504d9cf7dfSJeff LaBundy 		case IQS62X_FW_REC_TYPE_PROD:
2514d9cf7dfSJeff LaBundy 			if (fw_rec->data == iqs62x->dev_desc->prod_num)
2524d9cf7dfSJeff LaBundy 				continue;
2534d9cf7dfSJeff LaBundy 
2544d9cf7dfSJeff LaBundy 			dev_err(&client->dev,
2554d9cf7dfSJeff LaBundy 				"Incompatible product number: 0x%02X\n",
2564d9cf7dfSJeff LaBundy 				fw_rec->data);
2574d9cf7dfSJeff LaBundy 			ret = -EINVAL;
2584d9cf7dfSJeff LaBundy 			break;
2594d9cf7dfSJeff LaBundy 
2604d9cf7dfSJeff LaBundy 		case IQS62X_FW_REC_TYPE_HALL:
2614d9cf7dfSJeff LaBundy 			if (!hall_cal_index) {
2624d9cf7dfSJeff LaBundy 				ret = regmap_write(iqs62x->regmap,
2634d9cf7dfSJeff LaBundy 						   IQS62X_OTP_CMD,
2644d9cf7dfSJeff LaBundy 						   IQS62X_OTP_CMD_FG3);
2654d9cf7dfSJeff LaBundy 				if (ret)
2664d9cf7dfSJeff LaBundy 					break;
2674d9cf7dfSJeff LaBundy 
2684d9cf7dfSJeff LaBundy 				ret = regmap_read(iqs62x->regmap,
2694d9cf7dfSJeff LaBundy 						  IQS62X_OTP_DATA, &val);
2704d9cf7dfSJeff LaBundy 				if (ret)
2714d9cf7dfSJeff LaBundy 					break;
2724d9cf7dfSJeff LaBundy 
2734d9cf7dfSJeff LaBundy 				hall_cal_index = val & IQS62X_HALL_CAL_MASK;
2744d9cf7dfSJeff LaBundy 				if (!hall_cal_index) {
2754d9cf7dfSJeff LaBundy 					dev_err(&client->dev,
2764d9cf7dfSJeff LaBundy 						"Uncalibrated device\n");
2774d9cf7dfSJeff LaBundy 					ret = -ENODATA;
2784d9cf7dfSJeff LaBundy 					break;
2794d9cf7dfSJeff LaBundy 				}
2804d9cf7dfSJeff LaBundy 			}
2814d9cf7dfSJeff LaBundy 
2824d9cf7dfSJeff LaBundy 			if (hall_cal_index > fw_rec->len) {
2834d9cf7dfSJeff LaBundy 				ret = -EINVAL;
2844d9cf7dfSJeff LaBundy 				break;
2854d9cf7dfSJeff LaBundy 			}
2864d9cf7dfSJeff LaBundy 
2874d9cf7dfSJeff LaBundy 			mask = 0;
2884d9cf7dfSJeff LaBundy 			data = &fw_rec->data + hall_cal_index - 1;
2894d9cf7dfSJeff LaBundy 			len = sizeof(*data);
2904d9cf7dfSJeff LaBundy 			break;
2914d9cf7dfSJeff LaBundy 
2924d9cf7dfSJeff LaBundy 		case IQS62X_FW_REC_TYPE_MASK:
2934d9cf7dfSJeff LaBundy 			if (fw_rec->len < (sizeof(mask) + sizeof(*data))) {
2944d9cf7dfSJeff LaBundy 				ret = -EINVAL;
2954d9cf7dfSJeff LaBundy 				break;
2964d9cf7dfSJeff LaBundy 			}
2974d9cf7dfSJeff LaBundy 
2984d9cf7dfSJeff LaBundy 			mask = fw_rec->data;
2994d9cf7dfSJeff LaBundy 			data = &fw_rec->data + sizeof(mask);
3004d9cf7dfSJeff LaBundy 			len = sizeof(*data);
3014d9cf7dfSJeff LaBundy 			break;
3024d9cf7dfSJeff LaBundy 
3034d9cf7dfSJeff LaBundy 		case IQS62X_FW_REC_TYPE_DATA:
3044d9cf7dfSJeff LaBundy 			mask = 0;
3054d9cf7dfSJeff LaBundy 			data = &fw_rec->data;
3064d9cf7dfSJeff LaBundy 			len = fw_rec->len;
3074d9cf7dfSJeff LaBundy 			break;
3084d9cf7dfSJeff LaBundy 
3094d9cf7dfSJeff LaBundy 		default:
3104d9cf7dfSJeff LaBundy 			dev_err(&client->dev,
3114d9cf7dfSJeff LaBundy 				"Unrecognized record type: 0x%02X\n",
3124d9cf7dfSJeff LaBundy 				fw_rec->type);
3134d9cf7dfSJeff LaBundy 			ret = -EINVAL;
3144d9cf7dfSJeff LaBundy 		}
3154d9cf7dfSJeff LaBundy 
3164d9cf7dfSJeff LaBundy 		if (ret)
3174d9cf7dfSJeff LaBundy 			break;
3184d9cf7dfSJeff LaBundy 
3194d9cf7dfSJeff LaBundy 		fw_blk = devm_kzalloc(&client->dev,
3204d9cf7dfSJeff LaBundy 				      struct_size(fw_blk, data, len),
3214d9cf7dfSJeff LaBundy 				      GFP_KERNEL);
3224d9cf7dfSJeff LaBundy 		if (!fw_blk) {
3234d9cf7dfSJeff LaBundy 			ret = -ENOMEM;
3244d9cf7dfSJeff LaBundy 			break;
3254d9cf7dfSJeff LaBundy 		}
3264d9cf7dfSJeff LaBundy 
3274d9cf7dfSJeff LaBundy 		fw_blk->addr = fw_rec->addr;
3284d9cf7dfSJeff LaBundy 		fw_blk->mask = mask;
3294d9cf7dfSJeff LaBundy 		fw_blk->len = len;
3304d9cf7dfSJeff LaBundy 		memcpy(fw_blk->data, data, len);
3314d9cf7dfSJeff LaBundy 
3324d9cf7dfSJeff LaBundy 		list_add(&fw_blk->list, &iqs62x->fw_blk_head);
3334d9cf7dfSJeff LaBundy 	}
3344d9cf7dfSJeff LaBundy 
3354d9cf7dfSJeff LaBundy 	release_firmware(fw);
3364d9cf7dfSJeff LaBundy 
3374d9cf7dfSJeff LaBundy 	return ret;
3384d9cf7dfSJeff LaBundy }
3394d9cf7dfSJeff LaBundy 
3404d9cf7dfSJeff LaBundy const struct iqs62x_event_desc iqs62x_events[IQS62X_NUM_EVENTS] = {
3414d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_PROX_CH0_T] = {
3424d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_PROX,
3434d9cf7dfSJeff LaBundy 		.mask	= BIT(4),
3444d9cf7dfSJeff LaBundy 		.val	= BIT(4),
3454d9cf7dfSJeff LaBundy 	},
3464d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_PROX_CH0_P] = {
3474d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_PROX,
3484d9cf7dfSJeff LaBundy 		.mask	= BIT(0),
3494d9cf7dfSJeff LaBundy 		.val	= BIT(0),
3504d9cf7dfSJeff LaBundy 	},
3514d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_PROX_CH1_T] = {
3524d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_PROX,
3534d9cf7dfSJeff LaBundy 		.mask	= BIT(5),
3544d9cf7dfSJeff LaBundy 		.val	= BIT(5),
3554d9cf7dfSJeff LaBundy 	},
3564d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_PROX_CH1_P] = {
3574d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_PROX,
3584d9cf7dfSJeff LaBundy 		.mask	= BIT(1),
3594d9cf7dfSJeff LaBundy 		.val	= BIT(1),
3604d9cf7dfSJeff LaBundy 	},
3614d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_PROX_CH2_T] = {
3624d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_PROX,
3634d9cf7dfSJeff LaBundy 		.mask	= BIT(6),
3644d9cf7dfSJeff LaBundy 		.val	= BIT(6),
3654d9cf7dfSJeff LaBundy 	},
3664d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_PROX_CH2_P] = {
3674d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_PROX,
3684d9cf7dfSJeff LaBundy 		.mask	= BIT(2),
3694d9cf7dfSJeff LaBundy 		.val	= BIT(2),
3704d9cf7dfSJeff LaBundy 	},
3714d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_HYST_POS_T] = {
3724d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_HYST,
3734d9cf7dfSJeff LaBundy 		.mask	= BIT(6) | BIT(7),
3744d9cf7dfSJeff LaBundy 		.val	= BIT(6),
3754d9cf7dfSJeff LaBundy 	},
3764d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_HYST_POS_P] = {
3774d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_HYST,
3784d9cf7dfSJeff LaBundy 		.mask	= BIT(5) | BIT(7),
3794d9cf7dfSJeff LaBundy 		.val	= BIT(5),
3804d9cf7dfSJeff LaBundy 	},
3814d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_HYST_NEG_T] = {
3824d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_HYST,
3834d9cf7dfSJeff LaBundy 		.mask	= BIT(6) | BIT(7),
3844d9cf7dfSJeff LaBundy 		.val	= BIT(6) | BIT(7),
3854d9cf7dfSJeff LaBundy 	},
3864d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_HYST_NEG_P] = {
3874d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_HYST,
3884d9cf7dfSJeff LaBundy 		.mask	= BIT(5) | BIT(7),
3894d9cf7dfSJeff LaBundy 		.val	= BIT(5) | BIT(7),
3904d9cf7dfSJeff LaBundy 	},
3914d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_SAR1_ACT] = {
3924d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_HYST,
3934d9cf7dfSJeff LaBundy 		.mask	= BIT(4),
3944d9cf7dfSJeff LaBundy 		.val	= BIT(4),
3954d9cf7dfSJeff LaBundy 	},
3964d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_SAR1_QRD] = {
3974d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_HYST,
3984d9cf7dfSJeff LaBundy 		.mask	= BIT(2),
3994d9cf7dfSJeff LaBundy 		.val	= BIT(2),
4004d9cf7dfSJeff LaBundy 	},
4014d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_SAR1_MOVE] = {
4024d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_HYST,
4034d9cf7dfSJeff LaBundy 		.mask	= BIT(1),
4044d9cf7dfSJeff LaBundy 		.val	= BIT(1),
4054d9cf7dfSJeff LaBundy 	},
4064d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_SAR1_HALT] = {
4074d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_HYST,
4084d9cf7dfSJeff LaBundy 		.mask	= BIT(0),
4094d9cf7dfSJeff LaBundy 		.val	= BIT(0),
4104d9cf7dfSJeff LaBundy 	},
4114d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_WHEEL_UP] = {
4124d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_WHEEL,
4134d9cf7dfSJeff LaBundy 		.mask	= BIT(7) | BIT(6),
4144d9cf7dfSJeff LaBundy 		.val	= BIT(7),
4154d9cf7dfSJeff LaBundy 	},
4164d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_WHEEL_DN] = {
4174d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_WHEEL,
4184d9cf7dfSJeff LaBundy 		.mask	= BIT(7) | BIT(6),
4194d9cf7dfSJeff LaBundy 		.val	= BIT(7) | BIT(6),
4204d9cf7dfSJeff LaBundy 	},
4214d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_HALL_N_T] = {
4224d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_HALL,
4234d9cf7dfSJeff LaBundy 		.mask	= BIT(2) | BIT(0),
4244d9cf7dfSJeff LaBundy 		.val	= BIT(2),
4254d9cf7dfSJeff LaBundy 	},
4264d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_HALL_N_P] = {
4274d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_HALL,
4284d9cf7dfSJeff LaBundy 		.mask	= BIT(1) | BIT(0),
4294d9cf7dfSJeff LaBundy 		.val	= BIT(1),
4304d9cf7dfSJeff LaBundy 	},
4314d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_HALL_S_T] = {
4324d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_HALL,
4334d9cf7dfSJeff LaBundy 		.mask	= BIT(2) | BIT(0),
4344d9cf7dfSJeff LaBundy 		.val	= BIT(2) | BIT(0),
4354d9cf7dfSJeff LaBundy 	},
4364d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_HALL_S_P] = {
4374d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_HALL,
4384d9cf7dfSJeff LaBundy 		.mask	= BIT(1) | BIT(0),
4394d9cf7dfSJeff LaBundy 		.val	= BIT(1) | BIT(0),
4404d9cf7dfSJeff LaBundy 	},
4414d9cf7dfSJeff LaBundy 	[IQS62X_EVENT_SYS_RESET] = {
4424d9cf7dfSJeff LaBundy 		.reg	= IQS62X_EVENT_SYS,
4434d9cf7dfSJeff LaBundy 		.mask	= BIT(7),
4444d9cf7dfSJeff LaBundy 		.val	= BIT(7),
4454d9cf7dfSJeff LaBundy 	},
44602e550d5SJeff LaBundy 	[IQS62X_EVENT_SYS_ATI] = {
44702e550d5SJeff LaBundy 		.reg	= IQS62X_EVENT_SYS,
44802e550d5SJeff LaBundy 		.mask	= BIT(2),
44902e550d5SJeff LaBundy 		.val	= BIT(2),
45002e550d5SJeff LaBundy 	},
4514d9cf7dfSJeff LaBundy };
4524d9cf7dfSJeff LaBundy EXPORT_SYMBOL_GPL(iqs62x_events);
4534d9cf7dfSJeff LaBundy 
iqs62x_irq(int irq,void * context)4544d9cf7dfSJeff LaBundy static irqreturn_t iqs62x_irq(int irq, void *context)
4554d9cf7dfSJeff LaBundy {
4564d9cf7dfSJeff LaBundy 	struct iqs62x_core *iqs62x = context;
4574d9cf7dfSJeff LaBundy 	struct i2c_client *client = iqs62x->client;
4584d9cf7dfSJeff LaBundy 	struct iqs62x_event_data event_data;
4594d9cf7dfSJeff LaBundy 	struct iqs62x_event_desc event_desc;
4604d9cf7dfSJeff LaBundy 	enum iqs62x_event_reg event_reg;
4614d9cf7dfSJeff LaBundy 	unsigned long event_flags = 0;
4624d9cf7dfSJeff LaBundy 	int ret, i, j;
4634d9cf7dfSJeff LaBundy 	u8 event_map[IQS62X_EVENT_SIZE];
4644d9cf7dfSJeff LaBundy 
4654d9cf7dfSJeff LaBundy 	/*
4664d9cf7dfSJeff LaBundy 	 * The device asserts the RDY output to signal the beginning of a
4674d9cf7dfSJeff LaBundy 	 * communication window, which is closed by an I2C stop condition.
4684d9cf7dfSJeff LaBundy 	 * As such, all interrupt status is captured in a single read and
4694d9cf7dfSJeff LaBundy 	 * broadcast to any interested sub-device drivers.
4704d9cf7dfSJeff LaBundy 	 */
4714d9cf7dfSJeff LaBundy 	ret = regmap_raw_read(iqs62x->regmap, IQS62X_SYS_FLAGS, event_map,
4724d9cf7dfSJeff LaBundy 			      sizeof(event_map));
4734d9cf7dfSJeff LaBundy 	if (ret) {
4744d9cf7dfSJeff LaBundy 		dev_err(&client->dev, "Failed to read device status: %d\n",
4754d9cf7dfSJeff LaBundy 			ret);
4764d9cf7dfSJeff LaBundy 		return IRQ_NONE;
4774d9cf7dfSJeff LaBundy 	}
4784d9cf7dfSJeff LaBundy 
4794d9cf7dfSJeff LaBundy 	for (i = 0; i < sizeof(event_map); i++) {
4804d9cf7dfSJeff LaBundy 		event_reg = iqs62x->dev_desc->event_regs[iqs62x->ui_sel][i];
4814d9cf7dfSJeff LaBundy 
4824d9cf7dfSJeff LaBundy 		switch (event_reg) {
4834d9cf7dfSJeff LaBundy 		case IQS62X_EVENT_UI_LO:
4844d9cf7dfSJeff LaBundy 			event_data.ui_data = get_unaligned_le16(&event_map[i]);
485df561f66SGustavo A. R. Silva 			fallthrough;
4864d9cf7dfSJeff LaBundy 
4874d9cf7dfSJeff LaBundy 		case IQS62X_EVENT_UI_HI:
4884d9cf7dfSJeff LaBundy 		case IQS62X_EVENT_NONE:
4894d9cf7dfSJeff LaBundy 			continue;
4904d9cf7dfSJeff LaBundy 
4914d9cf7dfSJeff LaBundy 		case IQS62X_EVENT_ALS:
4924d9cf7dfSJeff LaBundy 			event_data.als_flags = event_map[i];
4934d9cf7dfSJeff LaBundy 			continue;
4944d9cf7dfSJeff LaBundy 
4954d9cf7dfSJeff LaBundy 		case IQS62X_EVENT_IR:
4964d9cf7dfSJeff LaBundy 			event_data.ir_flags = event_map[i];
4974d9cf7dfSJeff LaBundy 			continue;
4984d9cf7dfSJeff LaBundy 
4994d9cf7dfSJeff LaBundy 		case IQS62X_EVENT_INTER:
5004d9cf7dfSJeff LaBundy 			event_data.interval = event_map[i];
5014d9cf7dfSJeff LaBundy 			continue;
5024d9cf7dfSJeff LaBundy 
5034d9cf7dfSJeff LaBundy 		case IQS62X_EVENT_HYST:
5044d9cf7dfSJeff LaBundy 			event_map[i] <<= iqs62x->dev_desc->hyst_shift;
505df561f66SGustavo A. R. Silva 			fallthrough;
5064d9cf7dfSJeff LaBundy 
5074d9cf7dfSJeff LaBundy 		case IQS62X_EVENT_WHEEL:
5084d9cf7dfSJeff LaBundy 		case IQS62X_EVENT_HALL:
5094d9cf7dfSJeff LaBundy 		case IQS62X_EVENT_PROX:
5104d9cf7dfSJeff LaBundy 		case IQS62X_EVENT_SYS:
5114d9cf7dfSJeff LaBundy 			break;
5124d9cf7dfSJeff LaBundy 		}
5134d9cf7dfSJeff LaBundy 
5144d9cf7dfSJeff LaBundy 		for (j = 0; j < IQS62X_NUM_EVENTS; j++) {
5154d9cf7dfSJeff LaBundy 			event_desc = iqs62x_events[j];
5164d9cf7dfSJeff LaBundy 
5174d9cf7dfSJeff LaBundy 			if (event_desc.reg != event_reg)
5184d9cf7dfSJeff LaBundy 				continue;
5194d9cf7dfSJeff LaBundy 
5204d9cf7dfSJeff LaBundy 			if ((event_map[i] & event_desc.mask) == event_desc.val)
5214d9cf7dfSJeff LaBundy 				event_flags |= BIT(j);
5224d9cf7dfSJeff LaBundy 		}
5234d9cf7dfSJeff LaBundy 	}
5244d9cf7dfSJeff LaBundy 
5254d9cf7dfSJeff LaBundy 	/*
5264d9cf7dfSJeff LaBundy 	 * The device resets itself in response to the I2C master stalling
5274d9cf7dfSJeff LaBundy 	 * communication past a fixed timeout. In this case, all registers
5284d9cf7dfSJeff LaBundy 	 * are restored and any interested sub-device drivers are notified.
5294d9cf7dfSJeff LaBundy 	 */
5304d9cf7dfSJeff LaBundy 	if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
5314d9cf7dfSJeff LaBundy 		dev_err(&client->dev, "Unexpected device reset\n");
5324d9cf7dfSJeff LaBundy 
5334d9cf7dfSJeff LaBundy 		ret = iqs62x_dev_init(iqs62x);
5344d9cf7dfSJeff LaBundy 		if (ret) {
5354d9cf7dfSJeff LaBundy 			dev_err(&client->dev,
5364d9cf7dfSJeff LaBundy 				"Failed to re-initialize device: %d\n", ret);
5374d9cf7dfSJeff LaBundy 			return IRQ_NONE;
5384d9cf7dfSJeff LaBundy 		}
53902e550d5SJeff LaBundy 
54002e550d5SJeff LaBundy 		iqs62x->event_cache |= BIT(IQS62X_EVENT_SYS_RESET);
54102e550d5SJeff LaBundy 		reinit_completion(&iqs62x->ati_done);
54202e550d5SJeff LaBundy 	} else if (event_flags & BIT(IQS62X_EVENT_SYS_ATI)) {
54302e550d5SJeff LaBundy 		iqs62x->event_cache |= BIT(IQS62X_EVENT_SYS_ATI);
54402e550d5SJeff LaBundy 		reinit_completion(&iqs62x->ati_done);
54502e550d5SJeff LaBundy 	} else if (!completion_done(&iqs62x->ati_done)) {
54602e550d5SJeff LaBundy 		ret = regmap_update_bits(iqs62x->regmap, IQS62X_SYS_SETTINGS,
54702e550d5SJeff LaBundy 					 IQS62X_SYS_SETTINGS_EVENT_MODE, 0xFF);
54802e550d5SJeff LaBundy 		if (ret) {
54902e550d5SJeff LaBundy 			dev_err(&client->dev,
55002e550d5SJeff LaBundy 				"Failed to enable event mode: %d\n", ret);
55102e550d5SJeff LaBundy 			return IRQ_NONE;
5524d9cf7dfSJeff LaBundy 		}
5534d9cf7dfSJeff LaBundy 
55402e550d5SJeff LaBundy 		msleep(IQS62X_FILT_SETTLE_MS);
55502e550d5SJeff LaBundy 		complete_all(&iqs62x->ati_done);
55602e550d5SJeff LaBundy 	}
55702e550d5SJeff LaBundy 
55802e550d5SJeff LaBundy 	/*
55902e550d5SJeff LaBundy 	 * Reset and ATI events are not broadcast to the sub-device drivers
56002e550d5SJeff LaBundy 	 * until ATI has completed. Any other events that may have occurred
56102e550d5SJeff LaBundy 	 * during ATI are ignored.
56202e550d5SJeff LaBundy 	 */
56302e550d5SJeff LaBundy 	if (completion_done(&iqs62x->ati_done)) {
56402e550d5SJeff LaBundy 		event_flags |= iqs62x->event_cache;
5654d9cf7dfSJeff LaBundy 		ret = blocking_notifier_call_chain(&iqs62x->nh, event_flags,
5664d9cf7dfSJeff LaBundy 						   &event_data);
5674d9cf7dfSJeff LaBundy 		if (ret & NOTIFY_STOP_MASK)
5684d9cf7dfSJeff LaBundy 			return IRQ_NONE;
5694d9cf7dfSJeff LaBundy 
57002e550d5SJeff LaBundy 		iqs62x->event_cache = 0;
57102e550d5SJeff LaBundy 	}
57202e550d5SJeff LaBundy 
5734d9cf7dfSJeff LaBundy 	/*
5744d9cf7dfSJeff LaBundy 	 * Once the communication window is closed, a small delay is added to
5754d9cf7dfSJeff LaBundy 	 * ensure the device's RDY output has been deasserted by the time the
5764d9cf7dfSJeff LaBundy 	 * interrupt handler returns.
5774d9cf7dfSJeff LaBundy 	 */
578a3a06ea1SJeff LaBundy 	usleep_range(150, 200);
5794d9cf7dfSJeff LaBundy 
5804d9cf7dfSJeff LaBundy 	return IRQ_HANDLED;
5814d9cf7dfSJeff LaBundy }
5824d9cf7dfSJeff LaBundy 
iqs62x_firmware_load(const struct firmware * fw,void * context)5834d9cf7dfSJeff LaBundy static void iqs62x_firmware_load(const struct firmware *fw, void *context)
5844d9cf7dfSJeff LaBundy {
5854d9cf7dfSJeff LaBundy 	struct iqs62x_core *iqs62x = context;
5864d9cf7dfSJeff LaBundy 	struct i2c_client *client = iqs62x->client;
5874d9cf7dfSJeff LaBundy 	int ret;
5884d9cf7dfSJeff LaBundy 
5894d9cf7dfSJeff LaBundy 	if (fw) {
5904d9cf7dfSJeff LaBundy 		ret = iqs62x_firmware_parse(iqs62x, fw);
5914d9cf7dfSJeff LaBundy 		if (ret) {
5924d9cf7dfSJeff LaBundy 			dev_err(&client->dev, "Failed to parse firmware: %d\n",
5934d9cf7dfSJeff LaBundy 				ret);
5944d9cf7dfSJeff LaBundy 			goto err_out;
5954d9cf7dfSJeff LaBundy 		}
5964d9cf7dfSJeff LaBundy 	}
5974d9cf7dfSJeff LaBundy 
5984d9cf7dfSJeff LaBundy 	ret = iqs62x_dev_init(iqs62x);
5994d9cf7dfSJeff LaBundy 	if (ret) {
6004d9cf7dfSJeff LaBundy 		dev_err(&client->dev, "Failed to initialize device: %d\n", ret);
6014d9cf7dfSJeff LaBundy 		goto err_out;
6024d9cf7dfSJeff LaBundy 	}
6034d9cf7dfSJeff LaBundy 
6044d9cf7dfSJeff LaBundy 	ret = devm_request_threaded_irq(&client->dev, client->irq,
6054d9cf7dfSJeff LaBundy 					NULL, iqs62x_irq, IRQF_ONESHOT,
6064d9cf7dfSJeff LaBundy 					client->name, iqs62x);
6074d9cf7dfSJeff LaBundy 	if (ret) {
6084d9cf7dfSJeff LaBundy 		dev_err(&client->dev, "Failed to request IRQ: %d\n", ret);
6094d9cf7dfSJeff LaBundy 		goto err_out;
6104d9cf7dfSJeff LaBundy 	}
6114d9cf7dfSJeff LaBundy 
61202e550d5SJeff LaBundy 	if (!wait_for_completion_timeout(&iqs62x->ati_done,
61302e550d5SJeff LaBundy 					 msecs_to_jiffies(2000))) {
61402e550d5SJeff LaBundy 		dev_err(&client->dev, "Failed to complete ATI\n");
61502e550d5SJeff LaBundy 		goto err_out;
61602e550d5SJeff LaBundy 	}
61702e550d5SJeff LaBundy 
6184d9cf7dfSJeff LaBundy 	ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE,
6194d9cf7dfSJeff LaBundy 				   iqs62x->dev_desc->sub_devs,
6204d9cf7dfSJeff LaBundy 				   iqs62x->dev_desc->num_sub_devs,
6214d9cf7dfSJeff LaBundy 				   NULL, 0, NULL);
6224d9cf7dfSJeff LaBundy 	if (ret)
6234d9cf7dfSJeff LaBundy 		dev_err(&client->dev, "Failed to add sub-devices: %d\n", ret);
6244d9cf7dfSJeff LaBundy 
6254d9cf7dfSJeff LaBundy err_out:
6264d9cf7dfSJeff LaBundy 	complete_all(&iqs62x->fw_done);
6274d9cf7dfSJeff LaBundy }
6284d9cf7dfSJeff LaBundy 
6294d9cf7dfSJeff LaBundy static const struct mfd_cell iqs620at_sub_devs[] = {
6304d9cf7dfSJeff LaBundy 	{
6314d9cf7dfSJeff LaBundy 		.name = "iqs62x-keys",
6324d9cf7dfSJeff LaBundy 		.of_compatible = "azoteq,iqs620a-keys",
6334d9cf7dfSJeff LaBundy 	},
6344d9cf7dfSJeff LaBundy 	{
6354d9cf7dfSJeff LaBundy 		.name = "iqs620a-pwm",
6364d9cf7dfSJeff LaBundy 		.of_compatible = "azoteq,iqs620a-pwm",
6374d9cf7dfSJeff LaBundy 	},
6384d9cf7dfSJeff LaBundy 	{ .name = "iqs620at-temp", },
6394d9cf7dfSJeff LaBundy };
6404d9cf7dfSJeff LaBundy 
6414d9cf7dfSJeff LaBundy static const struct mfd_cell iqs620a_sub_devs[] = {
6424d9cf7dfSJeff LaBundy 	{
6434d9cf7dfSJeff LaBundy 		.name = "iqs62x-keys",
6444d9cf7dfSJeff LaBundy 		.of_compatible = "azoteq,iqs620a-keys",
6454d9cf7dfSJeff LaBundy 	},
6464d9cf7dfSJeff LaBundy 	{
6474d9cf7dfSJeff LaBundy 		.name = "iqs620a-pwm",
6484d9cf7dfSJeff LaBundy 		.of_compatible = "azoteq,iqs620a-pwm",
6494d9cf7dfSJeff LaBundy 	},
6504d9cf7dfSJeff LaBundy };
6514d9cf7dfSJeff LaBundy 
6524d9cf7dfSJeff LaBundy static const struct mfd_cell iqs621_sub_devs[] = {
6534d9cf7dfSJeff LaBundy 	{
6544d9cf7dfSJeff LaBundy 		.name = "iqs62x-keys",
6554d9cf7dfSJeff LaBundy 		.of_compatible = "azoteq,iqs621-keys",
6564d9cf7dfSJeff LaBundy 	},
6574d9cf7dfSJeff LaBundy 	{ .name = "iqs621-als", },
6584d9cf7dfSJeff LaBundy };
6594d9cf7dfSJeff LaBundy 
6604d9cf7dfSJeff LaBundy static const struct mfd_cell iqs622_sub_devs[] = {
6614d9cf7dfSJeff LaBundy 	{
6624d9cf7dfSJeff LaBundy 		.name = "iqs62x-keys",
6634d9cf7dfSJeff LaBundy 		.of_compatible = "azoteq,iqs622-keys",
6644d9cf7dfSJeff LaBundy 	},
6654d9cf7dfSJeff LaBundy 	{ .name = "iqs621-als", },
6664d9cf7dfSJeff LaBundy };
6674d9cf7dfSJeff LaBundy 
6684d9cf7dfSJeff LaBundy static const struct mfd_cell iqs624_sub_devs[] = {
6694d9cf7dfSJeff LaBundy 	{
6704d9cf7dfSJeff LaBundy 		.name = "iqs62x-keys",
6714d9cf7dfSJeff LaBundy 		.of_compatible = "azoteq,iqs624-keys",
6724d9cf7dfSJeff LaBundy 	},
6734d9cf7dfSJeff LaBundy 	{ .name = "iqs624-pos", },
6744d9cf7dfSJeff LaBundy };
6754d9cf7dfSJeff LaBundy 
6764d9cf7dfSJeff LaBundy static const struct mfd_cell iqs625_sub_devs[] = {
6774d9cf7dfSJeff LaBundy 	{
6784d9cf7dfSJeff LaBundy 		.name = "iqs62x-keys",
6794d9cf7dfSJeff LaBundy 		.of_compatible = "azoteq,iqs625-keys",
6804d9cf7dfSJeff LaBundy 	},
6814d9cf7dfSJeff LaBundy 	{ .name = "iqs624-pos", },
6824d9cf7dfSJeff LaBundy };
6834d9cf7dfSJeff LaBundy 
6844d9cf7dfSJeff LaBundy static const u8 iqs620at_cal_regs[] = {
6854d9cf7dfSJeff LaBundy 	IQS620_TEMP_CAL_MULT,
6864d9cf7dfSJeff LaBundy 	IQS620_TEMP_CAL_DIV,
6874d9cf7dfSJeff LaBundy 	IQS620_TEMP_CAL_OFFS,
6884d9cf7dfSJeff LaBundy };
6894d9cf7dfSJeff LaBundy 
6904d9cf7dfSJeff LaBundy static const u8 iqs621_cal_regs[] = {
6914d9cf7dfSJeff LaBundy 	IQS621_ALS_CAL_DIV_LUX,
6924d9cf7dfSJeff LaBundy 	IQS621_ALS_CAL_DIV_IR,
6934d9cf7dfSJeff LaBundy };
6944d9cf7dfSJeff LaBundy 
6954d9cf7dfSJeff LaBundy static const enum iqs62x_event_reg iqs620a_event_regs[][IQS62X_EVENT_SIZE] = {
6964d9cf7dfSJeff LaBundy 	[IQS62X_UI_PROX] = {
6974d9cf7dfSJeff LaBundy 		IQS62X_EVENT_SYS,	/* 0x10 */
6984d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
6994d9cf7dfSJeff LaBundy 		IQS62X_EVENT_PROX,	/* 0x12 */
7004d9cf7dfSJeff LaBundy 		IQS62X_EVENT_HYST,	/* 0x13 */
7014d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7024d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7034d9cf7dfSJeff LaBundy 		IQS62X_EVENT_HALL,	/* 0x16 */
7044d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7054d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7064d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7074d9cf7dfSJeff LaBundy 	},
7084d9cf7dfSJeff LaBundy 	[IQS62X_UI_SAR1] = {
7094d9cf7dfSJeff LaBundy 		IQS62X_EVENT_SYS,	/* 0x10 */
7104d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7114d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7124d9cf7dfSJeff LaBundy 		IQS62X_EVENT_HYST,	/* 0x13 */
7134d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7144d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7154d9cf7dfSJeff LaBundy 		IQS62X_EVENT_HALL,	/* 0x16 */
7164d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7174d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7184d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7194d9cf7dfSJeff LaBundy 	},
7204d9cf7dfSJeff LaBundy };
7214d9cf7dfSJeff LaBundy 
7224d9cf7dfSJeff LaBundy static const enum iqs62x_event_reg iqs621_event_regs[][IQS62X_EVENT_SIZE] = {
7234d9cf7dfSJeff LaBundy 	[IQS62X_UI_PROX] = {
7244d9cf7dfSJeff LaBundy 		IQS62X_EVENT_SYS,	/* 0x10 */
7254d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7264d9cf7dfSJeff LaBundy 		IQS62X_EVENT_PROX,	/* 0x12 */
7274d9cf7dfSJeff LaBundy 		IQS62X_EVENT_HYST,	/* 0x13 */
7284d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7294d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7304d9cf7dfSJeff LaBundy 		IQS62X_EVENT_ALS,	/* 0x16 */
7314d9cf7dfSJeff LaBundy 		IQS62X_EVENT_UI_LO,	/* 0x17 */
7324d9cf7dfSJeff LaBundy 		IQS62X_EVENT_UI_HI,	/* 0x18 */
7334d9cf7dfSJeff LaBundy 		IQS62X_EVENT_HALL,	/* 0x19 */
7344d9cf7dfSJeff LaBundy 	},
7354d9cf7dfSJeff LaBundy };
7364d9cf7dfSJeff LaBundy 
7374d9cf7dfSJeff LaBundy static const enum iqs62x_event_reg iqs622_event_regs[][IQS62X_EVENT_SIZE] = {
7384d9cf7dfSJeff LaBundy 	[IQS62X_UI_PROX] = {
7394d9cf7dfSJeff LaBundy 		IQS62X_EVENT_SYS,	/* 0x10 */
7404d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7414d9cf7dfSJeff LaBundy 		IQS62X_EVENT_PROX,	/* 0x12 */
7424d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7434d9cf7dfSJeff LaBundy 		IQS62X_EVENT_ALS,	/* 0x14 */
7444d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7454d9cf7dfSJeff LaBundy 		IQS62X_EVENT_IR,	/* 0x16 */
7464d9cf7dfSJeff LaBundy 		IQS62X_EVENT_UI_LO,	/* 0x17 */
7474d9cf7dfSJeff LaBundy 		IQS62X_EVENT_UI_HI,	/* 0x18 */
7484d9cf7dfSJeff LaBundy 		IQS62X_EVENT_HALL,	/* 0x19 */
7494d9cf7dfSJeff LaBundy 	},
7504d9cf7dfSJeff LaBundy 	[IQS62X_UI_SAR1] = {
7514d9cf7dfSJeff LaBundy 		IQS62X_EVENT_SYS,	/* 0x10 */
7524d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7534d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7544d9cf7dfSJeff LaBundy 		IQS62X_EVENT_HYST,	/* 0x13 */
7554d9cf7dfSJeff LaBundy 		IQS62X_EVENT_ALS,	/* 0x14 */
7564d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7574d9cf7dfSJeff LaBundy 		IQS62X_EVENT_IR,	/* 0x16 */
7584d9cf7dfSJeff LaBundy 		IQS62X_EVENT_UI_LO,	/* 0x17 */
7594d9cf7dfSJeff LaBundy 		IQS62X_EVENT_UI_HI,	/* 0x18 */
7604d9cf7dfSJeff LaBundy 		IQS62X_EVENT_HALL,	/* 0x19 */
7614d9cf7dfSJeff LaBundy 	},
7624d9cf7dfSJeff LaBundy };
7634d9cf7dfSJeff LaBundy 
7644d9cf7dfSJeff LaBundy static const enum iqs62x_event_reg iqs624_event_regs[][IQS62X_EVENT_SIZE] = {
7654d9cf7dfSJeff LaBundy 	[IQS62X_UI_PROX] = {
7664d9cf7dfSJeff LaBundy 		IQS62X_EVENT_SYS,	/* 0x10 */
7674d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7684d9cf7dfSJeff LaBundy 		IQS62X_EVENT_PROX,	/* 0x12 */
7694d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7704d9cf7dfSJeff LaBundy 		IQS62X_EVENT_WHEEL,	/* 0x14 */
7714d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7724d9cf7dfSJeff LaBundy 		IQS62X_EVENT_UI_LO,	/* 0x16 */
7734d9cf7dfSJeff LaBundy 		IQS62X_EVENT_UI_HI,	/* 0x17 */
7744d9cf7dfSJeff LaBundy 		IQS62X_EVENT_INTER,	/* 0x18 */
7754d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7764d9cf7dfSJeff LaBundy 	},
7774d9cf7dfSJeff LaBundy };
7784d9cf7dfSJeff LaBundy 
7794d9cf7dfSJeff LaBundy static const enum iqs62x_event_reg iqs625_event_regs[][IQS62X_EVENT_SIZE] = {
7804d9cf7dfSJeff LaBundy 	[IQS62X_UI_PROX] = {
7814d9cf7dfSJeff LaBundy 		IQS62X_EVENT_SYS,	/* 0x10 */
7824d9cf7dfSJeff LaBundy 		IQS62X_EVENT_PROX,	/* 0x11 */
7834d9cf7dfSJeff LaBundy 		IQS62X_EVENT_INTER,	/* 0x12 */
7844d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7854d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7864d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7874d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7884d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7894d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7904d9cf7dfSJeff LaBundy 		IQS62X_EVENT_NONE,
7914d9cf7dfSJeff LaBundy 	},
7924d9cf7dfSJeff LaBundy };
7934d9cf7dfSJeff LaBundy 
7944d9cf7dfSJeff LaBundy static const struct iqs62x_dev_desc iqs62x_devs[] = {
7954d9cf7dfSJeff LaBundy 	{
7964d9cf7dfSJeff LaBundy 		.dev_name	= "iqs620at",
7974d9cf7dfSJeff LaBundy 		.sub_devs	= iqs620at_sub_devs,
7984d9cf7dfSJeff LaBundy 		.num_sub_devs	= ARRAY_SIZE(iqs620at_sub_devs),
7994d9cf7dfSJeff LaBundy 		.prod_num	= IQS620_PROD_NUM,
8004d9cf7dfSJeff LaBundy 		.sw_num		= 0x08,
8014d9cf7dfSJeff LaBundy 		.cal_regs	= iqs620at_cal_regs,
8024d9cf7dfSJeff LaBundy 		.num_cal_regs	= ARRAY_SIZE(iqs620at_cal_regs),
8034d9cf7dfSJeff LaBundy 		.prox_mask	= BIT(0),
8044d9cf7dfSJeff LaBundy 		.sar_mask	= BIT(1) | BIT(7),
8054d9cf7dfSJeff LaBundy 		.hall_mask	= BIT(2),
8064d9cf7dfSJeff LaBundy 		.hyst_mask	= BIT(3),
8074d9cf7dfSJeff LaBundy 		.temp_mask	= BIT(4),
8084d9cf7dfSJeff LaBundy 		.prox_settings	= IQS620_PROX_SETTINGS_4,
8094d9cf7dfSJeff LaBundy 		.hall_flags	= IQS620_HALL_FLAGS,
8104d9cf7dfSJeff LaBundy 		.fw_name	= "iqs620a.bin",
8114d9cf7dfSJeff LaBundy 		.event_regs	= &iqs620a_event_regs[IQS62X_UI_PROX],
8124d9cf7dfSJeff LaBundy 	},
8134d9cf7dfSJeff LaBundy 	{
8144d9cf7dfSJeff LaBundy 		.dev_name	= "iqs620a",
8154d9cf7dfSJeff LaBundy 		.sub_devs	= iqs620a_sub_devs,
8164d9cf7dfSJeff LaBundy 		.num_sub_devs	= ARRAY_SIZE(iqs620a_sub_devs),
8174d9cf7dfSJeff LaBundy 		.prod_num	= IQS620_PROD_NUM,
8184d9cf7dfSJeff LaBundy 		.sw_num		= 0x08,
8194d9cf7dfSJeff LaBundy 		.prox_mask	= BIT(0),
8204d9cf7dfSJeff LaBundy 		.sar_mask	= BIT(1) | BIT(7),
8214d9cf7dfSJeff LaBundy 		.hall_mask	= BIT(2),
8224d9cf7dfSJeff LaBundy 		.hyst_mask	= BIT(3),
8234d9cf7dfSJeff LaBundy 		.temp_mask	= BIT(4),
8244d9cf7dfSJeff LaBundy 		.prox_settings	= IQS620_PROX_SETTINGS_4,
8254d9cf7dfSJeff LaBundy 		.hall_flags	= IQS620_HALL_FLAGS,
8264d9cf7dfSJeff LaBundy 		.fw_name	= "iqs620a.bin",
8274d9cf7dfSJeff LaBundy 		.event_regs	= &iqs620a_event_regs[IQS62X_UI_PROX],
8284d9cf7dfSJeff LaBundy 	},
8294d9cf7dfSJeff LaBundy 	{
8304d9cf7dfSJeff LaBundy 		.dev_name	= "iqs621",
8314d9cf7dfSJeff LaBundy 		.sub_devs	= iqs621_sub_devs,
8324d9cf7dfSJeff LaBundy 		.num_sub_devs	= ARRAY_SIZE(iqs621_sub_devs),
8334d9cf7dfSJeff LaBundy 		.prod_num	= IQS621_PROD_NUM,
8344d9cf7dfSJeff LaBundy 		.sw_num		= 0x09,
8354d9cf7dfSJeff LaBundy 		.cal_regs	= iqs621_cal_regs,
8364d9cf7dfSJeff LaBundy 		.num_cal_regs	= ARRAY_SIZE(iqs621_cal_regs),
8374d9cf7dfSJeff LaBundy 		.prox_mask	= BIT(0),
8384d9cf7dfSJeff LaBundy 		.hall_mask	= BIT(1),
8394d9cf7dfSJeff LaBundy 		.als_mask	= BIT(2),
8404d9cf7dfSJeff LaBundy 		.hyst_mask	= BIT(3),
8414d9cf7dfSJeff LaBundy 		.temp_mask	= BIT(4),
8424d9cf7dfSJeff LaBundy 		.als_flags	= IQS621_ALS_FLAGS,
8434d9cf7dfSJeff LaBundy 		.hall_flags	= IQS621_HALL_FLAGS,
8444d9cf7dfSJeff LaBundy 		.hyst_shift	= 5,
8454d9cf7dfSJeff LaBundy 		.fw_name	= "iqs621.bin",
8464d9cf7dfSJeff LaBundy 		.event_regs	= &iqs621_event_regs[IQS62X_UI_PROX],
8474d9cf7dfSJeff LaBundy 	},
8484d9cf7dfSJeff LaBundy 	{
8494d9cf7dfSJeff LaBundy 		.dev_name	= "iqs622",
8504d9cf7dfSJeff LaBundy 		.sub_devs	= iqs622_sub_devs,
8514d9cf7dfSJeff LaBundy 		.num_sub_devs	= ARRAY_SIZE(iqs622_sub_devs),
8524d9cf7dfSJeff LaBundy 		.prod_num	= IQS622_PROD_NUM,
8534d9cf7dfSJeff LaBundy 		.sw_num		= 0x06,
8544d9cf7dfSJeff LaBundy 		.prox_mask	= BIT(0),
8554d9cf7dfSJeff LaBundy 		.sar_mask	= BIT(1),
8564d9cf7dfSJeff LaBundy 		.hall_mask	= BIT(2),
8574d9cf7dfSJeff LaBundy 		.als_mask	= BIT(3),
8584d9cf7dfSJeff LaBundy 		.ir_mask	= BIT(4),
8594d9cf7dfSJeff LaBundy 		.prox_settings	= IQS622_PROX_SETTINGS_4,
8604d9cf7dfSJeff LaBundy 		.als_flags	= IQS622_ALS_FLAGS,
8614d9cf7dfSJeff LaBundy 		.hall_flags	= IQS622_HALL_FLAGS,
8624d9cf7dfSJeff LaBundy 		.fw_name	= "iqs622.bin",
8634d9cf7dfSJeff LaBundy 		.event_regs	= &iqs622_event_regs[IQS62X_UI_PROX],
8644d9cf7dfSJeff LaBundy 	},
8654d9cf7dfSJeff LaBundy 	{
8664d9cf7dfSJeff LaBundy 		.dev_name	= "iqs624",
8674d9cf7dfSJeff LaBundy 		.sub_devs	= iqs624_sub_devs,
8684d9cf7dfSJeff LaBundy 		.num_sub_devs	= ARRAY_SIZE(iqs624_sub_devs),
8694d9cf7dfSJeff LaBundy 		.prod_num	= IQS624_PROD_NUM,
8704d9cf7dfSJeff LaBundy 		.sw_num		= 0x0B,
8714d9cf7dfSJeff LaBundy 		.interval	= IQS624_INTERVAL_NUM,
8724d9cf7dfSJeff LaBundy 		.interval_div	= 3,
8734d9cf7dfSJeff LaBundy 		.fw_name	= "iqs624.bin",
8744d9cf7dfSJeff LaBundy 		.event_regs	= &iqs624_event_regs[IQS62X_UI_PROX],
8754d9cf7dfSJeff LaBundy 	},
8764d9cf7dfSJeff LaBundy 	{
8774d9cf7dfSJeff LaBundy 		.dev_name	= "iqs625",
8784d9cf7dfSJeff LaBundy 		.sub_devs	= iqs625_sub_devs,
8794d9cf7dfSJeff LaBundy 		.num_sub_devs	= ARRAY_SIZE(iqs625_sub_devs),
8804d9cf7dfSJeff LaBundy 		.prod_num	= IQS625_PROD_NUM,
8814d9cf7dfSJeff LaBundy 		.sw_num		= 0x0B,
8824d9cf7dfSJeff LaBundy 		.interval	= IQS625_INTERVAL_NUM,
8834d9cf7dfSJeff LaBundy 		.interval_div	= 10,
8844d9cf7dfSJeff LaBundy 		.fw_name	= "iqs625.bin",
8854d9cf7dfSJeff LaBundy 		.event_regs	= &iqs625_event_regs[IQS62X_UI_PROX],
8864d9cf7dfSJeff LaBundy 	},
8874d9cf7dfSJeff LaBundy };
8884d9cf7dfSJeff LaBundy 
889f0c71126SJeff LaBundy static const struct regmap_config iqs62x_regmap_config = {
8904d9cf7dfSJeff LaBundy 	.reg_bits = 8,
8914d9cf7dfSJeff LaBundy 	.val_bits = 8,
8924d9cf7dfSJeff LaBundy 	.max_register = IQS62X_MAX_REG,
8934d9cf7dfSJeff LaBundy };
8944d9cf7dfSJeff LaBundy 
iqs62x_probe(struct i2c_client * client)8954d9cf7dfSJeff LaBundy static int iqs62x_probe(struct i2c_client *client)
8964d9cf7dfSJeff LaBundy {
8974d9cf7dfSJeff LaBundy 	struct iqs62x_core *iqs62x;
8984d9cf7dfSJeff LaBundy 	struct iqs62x_info info;
8994d9cf7dfSJeff LaBundy 	unsigned int val;
9004d9cf7dfSJeff LaBundy 	int ret, i, j;
9014d9cf7dfSJeff LaBundy 	const char *fw_name = NULL;
9024d9cf7dfSJeff LaBundy 
9034d9cf7dfSJeff LaBundy 	iqs62x = devm_kzalloc(&client->dev, sizeof(*iqs62x), GFP_KERNEL);
9044d9cf7dfSJeff LaBundy 	if (!iqs62x)
9054d9cf7dfSJeff LaBundy 		return -ENOMEM;
9064d9cf7dfSJeff LaBundy 
9074d9cf7dfSJeff LaBundy 	i2c_set_clientdata(client, iqs62x);
9084d9cf7dfSJeff LaBundy 	iqs62x->client = client;
9094d9cf7dfSJeff LaBundy 
9104d9cf7dfSJeff LaBundy 	BLOCKING_INIT_NOTIFIER_HEAD(&iqs62x->nh);
9114d9cf7dfSJeff LaBundy 	INIT_LIST_HEAD(&iqs62x->fw_blk_head);
91202e550d5SJeff LaBundy 
91302e550d5SJeff LaBundy 	init_completion(&iqs62x->ati_done);
9144d9cf7dfSJeff LaBundy 	init_completion(&iqs62x->fw_done);
9154d9cf7dfSJeff LaBundy 
916f0c71126SJeff LaBundy 	iqs62x->regmap = devm_regmap_init_i2c(client, &iqs62x_regmap_config);
9174d9cf7dfSJeff LaBundy 	if (IS_ERR(iqs62x->regmap)) {
9184d9cf7dfSJeff LaBundy 		ret = PTR_ERR(iqs62x->regmap);
9194d9cf7dfSJeff LaBundy 		dev_err(&client->dev, "Failed to initialize register map: %d\n",
9204d9cf7dfSJeff LaBundy 			ret);
9214d9cf7dfSJeff LaBundy 		return ret;
9224d9cf7dfSJeff LaBundy 	}
9234d9cf7dfSJeff LaBundy 
9244d9cf7dfSJeff LaBundy 	ret = regmap_raw_read(iqs62x->regmap, IQS62X_PROD_NUM, &info,
9254d9cf7dfSJeff LaBundy 			      sizeof(info));
9264d9cf7dfSJeff LaBundy 	if (ret)
9274d9cf7dfSJeff LaBundy 		return ret;
9284d9cf7dfSJeff LaBundy 
9294d9cf7dfSJeff LaBundy 	/*
9304d9cf7dfSJeff LaBundy 	 * The following sequence validates the device's product and software
9314d9cf7dfSJeff LaBundy 	 * numbers. It then determines if the device is factory-calibrated by
9324d9cf7dfSJeff LaBundy 	 * checking for nonzero values in the device's designated calibration
9334d9cf7dfSJeff LaBundy 	 * registers (if applicable). Depending on the device, the absence of
9344d9cf7dfSJeff LaBundy 	 * calibration data indicates a reduced feature set or invalid device.
9354d9cf7dfSJeff LaBundy 	 *
9364d9cf7dfSJeff LaBundy 	 * For devices given in both calibrated and uncalibrated versions, the
9374d9cf7dfSJeff LaBundy 	 * calibrated version (e.g. IQS620AT) appears first in the iqs62x_devs
9384d9cf7dfSJeff LaBundy 	 * array. The uncalibrated version (e.g. IQS620A) appears next and has
9394d9cf7dfSJeff LaBundy 	 * the same product and software numbers, but no calibration registers
9404d9cf7dfSJeff LaBundy 	 * are specified.
9414d9cf7dfSJeff LaBundy 	 */
9424d9cf7dfSJeff LaBundy 	for (i = 0; i < ARRAY_SIZE(iqs62x_devs); i++) {
9434d9cf7dfSJeff LaBundy 		if (info.prod_num != iqs62x_devs[i].prod_num)
9444d9cf7dfSJeff LaBundy 			continue;
9454d9cf7dfSJeff LaBundy 
9464d9cf7dfSJeff LaBundy 		iqs62x->dev_desc = &iqs62x_devs[i];
9474d9cf7dfSJeff LaBundy 
9484d9cf7dfSJeff LaBundy 		if (info.sw_num < iqs62x->dev_desc->sw_num)
9494d9cf7dfSJeff LaBundy 			continue;
9504d9cf7dfSJeff LaBundy 
9511de785a5SJeff LaBundy 		iqs62x->sw_num = info.sw_num;
9521de785a5SJeff LaBundy 		iqs62x->hw_num = info.hw_num;
9534d9cf7dfSJeff LaBundy 
9544d9cf7dfSJeff LaBundy 		/*
9554d9cf7dfSJeff LaBundy 		 * Read each of the device's designated calibration registers,
9564d9cf7dfSJeff LaBundy 		 * if any, and exit from the inner loop early if any are equal
9574d9cf7dfSJeff LaBundy 		 * to zero (indicating the device is uncalibrated). This could
9584d9cf7dfSJeff LaBundy 		 * be acceptable depending on the device (e.g. IQS620A instead
9594d9cf7dfSJeff LaBundy 		 * of IQS620AT).
9604d9cf7dfSJeff LaBundy 		 */
9614d9cf7dfSJeff LaBundy 		for (j = 0; j < iqs62x->dev_desc->num_cal_regs; j++) {
9624d9cf7dfSJeff LaBundy 			ret = regmap_read(iqs62x->regmap,
9634d9cf7dfSJeff LaBundy 					  iqs62x->dev_desc->cal_regs[j], &val);
9644d9cf7dfSJeff LaBundy 			if (ret)
9654d9cf7dfSJeff LaBundy 				return ret;
9664d9cf7dfSJeff LaBundy 
9674d9cf7dfSJeff LaBundy 			if (!val)
9684d9cf7dfSJeff LaBundy 				break;
9694d9cf7dfSJeff LaBundy 		}
9704d9cf7dfSJeff LaBundy 
9714d9cf7dfSJeff LaBundy 		/*
9724d9cf7dfSJeff LaBundy 		 * If the number of nonzero values read from the device equals
9734d9cf7dfSJeff LaBundy 		 * the number of designated calibration registers (which could
9744d9cf7dfSJeff LaBundy 		 * be zero), exit from the outer loop early to signal that the
9754d9cf7dfSJeff LaBundy 		 * device's product and software numbers match a known device,
9764d9cf7dfSJeff LaBundy 		 * and the device is calibrated (if applicable).
9774d9cf7dfSJeff LaBundy 		 */
9784d9cf7dfSJeff LaBundy 		if (j == iqs62x->dev_desc->num_cal_regs)
9794d9cf7dfSJeff LaBundy 			break;
9804d9cf7dfSJeff LaBundy 	}
9814d9cf7dfSJeff LaBundy 
9824d9cf7dfSJeff LaBundy 	if (!iqs62x->dev_desc) {
9834d9cf7dfSJeff LaBundy 		dev_err(&client->dev, "Unrecognized product number: 0x%02X\n",
9844d9cf7dfSJeff LaBundy 			info.prod_num);
9854d9cf7dfSJeff LaBundy 		return -EINVAL;
9864d9cf7dfSJeff LaBundy 	}
9874d9cf7dfSJeff LaBundy 
9881de785a5SJeff LaBundy 	if (!iqs62x->sw_num) {
9894d9cf7dfSJeff LaBundy 		dev_err(&client->dev, "Unrecognized software number: 0x%02X\n",
9904d9cf7dfSJeff LaBundy 			info.sw_num);
9914d9cf7dfSJeff LaBundy 		return -EINVAL;
9924d9cf7dfSJeff LaBundy 	}
9934d9cf7dfSJeff LaBundy 
9944d9cf7dfSJeff LaBundy 	if (i == ARRAY_SIZE(iqs62x_devs)) {
9954d9cf7dfSJeff LaBundy 		dev_err(&client->dev, "Uncalibrated device\n");
9964d9cf7dfSJeff LaBundy 		return -ENODATA;
9974d9cf7dfSJeff LaBundy 	}
9984d9cf7dfSJeff LaBundy 
9994d9cf7dfSJeff LaBundy 	device_property_read_string(&client->dev, "firmware-name", &fw_name);
10004d9cf7dfSJeff LaBundy 
10010733d839SShawn Guo 	ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
10024d9cf7dfSJeff LaBundy 				      fw_name ? : iqs62x->dev_desc->fw_name,
10034d9cf7dfSJeff LaBundy 				      &client->dev, GFP_KERNEL, iqs62x,
10044d9cf7dfSJeff LaBundy 				      iqs62x_firmware_load);
10054d9cf7dfSJeff LaBundy 	if (ret)
10064d9cf7dfSJeff LaBundy 		dev_err(&client->dev, "Failed to request firmware: %d\n", ret);
10074d9cf7dfSJeff LaBundy 
10084d9cf7dfSJeff LaBundy 	return ret;
10094d9cf7dfSJeff LaBundy }
10104d9cf7dfSJeff LaBundy 
iqs62x_remove(struct i2c_client * client)1011ed5c2f5fSUwe Kleine-König static void iqs62x_remove(struct i2c_client *client)
10124d9cf7dfSJeff LaBundy {
10134d9cf7dfSJeff LaBundy 	struct iqs62x_core *iqs62x = i2c_get_clientdata(client);
10144d9cf7dfSJeff LaBundy 
10154d9cf7dfSJeff LaBundy 	wait_for_completion(&iqs62x->fw_done);
10164d9cf7dfSJeff LaBundy }
10174d9cf7dfSJeff LaBundy 
iqs62x_suspend(struct device * dev)10184d9cf7dfSJeff LaBundy static int __maybe_unused iqs62x_suspend(struct device *dev)
10194d9cf7dfSJeff LaBundy {
10204d9cf7dfSJeff LaBundy 	struct iqs62x_core *iqs62x = dev_get_drvdata(dev);
10214d9cf7dfSJeff LaBundy 	int ret;
10224d9cf7dfSJeff LaBundy 
10234d9cf7dfSJeff LaBundy 	wait_for_completion(&iqs62x->fw_done);
10244d9cf7dfSJeff LaBundy 
10254d9cf7dfSJeff LaBundy 	/*
10264d9cf7dfSJeff LaBundy 	 * As per the datasheet, automatic mode switching must be disabled
10274d9cf7dfSJeff LaBundy 	 * before the device is placed in or taken out of halt mode.
10284d9cf7dfSJeff LaBundy 	 */
10294d9cf7dfSJeff LaBundy 	ret = regmap_update_bits(iqs62x->regmap, IQS62X_PWR_SETTINGS,
10304d9cf7dfSJeff LaBundy 				 IQS62X_PWR_SETTINGS_DIS_AUTO, 0xFF);
10314d9cf7dfSJeff LaBundy 	if (ret)
10324d9cf7dfSJeff LaBundy 		return ret;
10334d9cf7dfSJeff LaBundy 
10344d9cf7dfSJeff LaBundy 	return regmap_update_bits(iqs62x->regmap, IQS62X_PWR_SETTINGS,
10354d9cf7dfSJeff LaBundy 				  IQS62X_PWR_SETTINGS_PWR_MODE_MASK,
10364d9cf7dfSJeff LaBundy 				  IQS62X_PWR_SETTINGS_PWR_MODE_HALT);
10374d9cf7dfSJeff LaBundy }
10384d9cf7dfSJeff LaBundy 
iqs62x_resume(struct device * dev)10394d9cf7dfSJeff LaBundy static int __maybe_unused iqs62x_resume(struct device *dev)
10404d9cf7dfSJeff LaBundy {
10414d9cf7dfSJeff LaBundy 	struct iqs62x_core *iqs62x = dev_get_drvdata(dev);
10424d9cf7dfSJeff LaBundy 	int ret;
10434d9cf7dfSJeff LaBundy 
10444d9cf7dfSJeff LaBundy 	ret = regmap_update_bits(iqs62x->regmap, IQS62X_PWR_SETTINGS,
10454d9cf7dfSJeff LaBundy 				 IQS62X_PWR_SETTINGS_PWR_MODE_MASK,
10464d9cf7dfSJeff LaBundy 				 IQS62X_PWR_SETTINGS_PWR_MODE_NORM);
10474d9cf7dfSJeff LaBundy 	if (ret)
10484d9cf7dfSJeff LaBundy 		return ret;
10494d9cf7dfSJeff LaBundy 
10504d9cf7dfSJeff LaBundy 	return regmap_update_bits(iqs62x->regmap, IQS62X_PWR_SETTINGS,
10514d9cf7dfSJeff LaBundy 				  IQS62X_PWR_SETTINGS_DIS_AUTO, 0);
10524d9cf7dfSJeff LaBundy }
10534d9cf7dfSJeff LaBundy 
10544d9cf7dfSJeff LaBundy static SIMPLE_DEV_PM_OPS(iqs62x_pm, iqs62x_suspend, iqs62x_resume);
10554d9cf7dfSJeff LaBundy 
10564d9cf7dfSJeff LaBundy static const struct of_device_id iqs62x_of_match[] = {
10574d9cf7dfSJeff LaBundy 	{ .compatible = "azoteq,iqs620a" },
10584d9cf7dfSJeff LaBundy 	{ .compatible = "azoteq,iqs621" },
10594d9cf7dfSJeff LaBundy 	{ .compatible = "azoteq,iqs622" },
10604d9cf7dfSJeff LaBundy 	{ .compatible = "azoteq,iqs624" },
10614d9cf7dfSJeff LaBundy 	{ .compatible = "azoteq,iqs625" },
10624d9cf7dfSJeff LaBundy 	{ }
10634d9cf7dfSJeff LaBundy };
10644d9cf7dfSJeff LaBundy MODULE_DEVICE_TABLE(of, iqs62x_of_match);
10654d9cf7dfSJeff LaBundy 
10664d9cf7dfSJeff LaBundy static struct i2c_driver iqs62x_i2c_driver = {
10674d9cf7dfSJeff LaBundy 	.driver = {
10684d9cf7dfSJeff LaBundy 		.name = "iqs62x",
10694d9cf7dfSJeff LaBundy 		.of_match_table = iqs62x_of_match,
10704d9cf7dfSJeff LaBundy 		.pm = &iqs62x_pm,
10714d9cf7dfSJeff LaBundy 	},
10729816d859SUwe Kleine-König 	.probe = iqs62x_probe,
10734d9cf7dfSJeff LaBundy 	.remove = iqs62x_remove,
10744d9cf7dfSJeff LaBundy };
10754d9cf7dfSJeff LaBundy module_i2c_driver(iqs62x_i2c_driver);
10764d9cf7dfSJeff LaBundy 
10774d9cf7dfSJeff LaBundy MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
10784d9cf7dfSJeff LaBundy MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Multi-Function Sensors");
10794d9cf7dfSJeff LaBundy MODULE_LICENSE("GPL");
1080