Re: [PATCH v4 2/7] mfd: Add support for Azoteq IQS620A/621/622/624/625

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, 17 Jan 2020, Jeff LaBundy wrote:

> This patch adds core support for the Azoteq IQS620A, IQS621, IQS622,
> IQS624 and IQS625 multi-function sensors.
> 
> Signed-off-by: Jeff LaBundy <jeff@xxxxxxxxxxx>
> ---
> Changes in v4:
>   - None
> 
> Changes in v3:
>   - None
> 
> Changes in v2:
>   - Merged 'Copyright' and 'Author' lines into one in introductory comments
>   - Replaced 'error' with 'ret' throughout
>   - Updated iqs62x_dev_init to account for 4/8/16-MHz clock divider in start-up
>     delays and replaced ATI timeout routine with regmap_read_poll_timeout
>   - Added an error message to iqs62x_irq in case device status fails to be read
>   - Replaced sw_num member of iqs62x_core with a local variable in iqs62x_probe
>     as the former was unused anywhere else
>   - Added comments throughout iqs62x_probe to clarify how devices are matched
>     based on the presence of calibration data
>   - Inverted the product and software number comparison logic in iqs62x_probe
>     to avoid an else...continue branch
>   - Changed iqs62x_probe from .probe callback to .probe_new callback, thereby
>     eliminating the otherwise unused iqs62x_id array
>   - Moved iqs62x_suspend and iqs62x_resume below iqs62x_remove
>   - Eliminated tabbed alignment of regmap_config and i2c_driver struct members
>   - Added register definitions for register addresses used in iqs621_cal_regs,
>     iqs620at_cal_regs and iqs62x_devs arrays
>   - Removed of_compatible string from IQS622 mfd_cell struct as its proximity
>     (now ambient light) sensing functionality need not be represented using a
>     child node
>   - Dissolved union in iqs62x_event_data to allow simultaneous use of ir_flags
>     and als_flags
>   - Removed temp_flags member of iqs62x_event_data, IQS62X_EVENT_TEMP register
>     enumeration and IQS62X_EVENT_UI_HI/LO from iqs620a_event_regs (thereby re-
>     ducing IQS62X_EVENT_SIZE to 10) as they were unused
> 
>  drivers/mfd/Kconfig         |  13 +
>  drivers/mfd/Makefile        |   3 +
>  drivers/mfd/iqs62x-core.c   | 639 ++++++++++++++++++++++++++++++++++++++++++++
>  drivers/mfd/iqs62x-tables.c | 438 ++++++++++++++++++++++++++++++
>  include/linux/mfd/iqs62x.h  | 146 ++++++++++
>  5 files changed, 1239 insertions(+)
>  create mode 100644 drivers/mfd/iqs62x-core.c
>  create mode 100644 drivers/mfd/iqs62x-tables.c
>  create mode 100644 include/linux/mfd/iqs62x.h
> 
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 4209008..151984c 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -642,6 +642,19 @@ config MFD_IPAQ_MICRO
>  	  AT90LS8535 microcontroller flashed with a special iPAQ
>  	  firmware using the custom protocol implemented in this driver.
> 
> +config MFD_IQS62X
> +	tristate "Azoteq IQS620A/621/622/624/625 core support"
> +	depends on I2C
> +	select MFD_CORE
> +	select REGMAP_I2C
> +	help
> +	  Say Y here if you want to build core support for the Azoteq IQS620A,
> +	  IQS621, IQS622, IQS624 and IQS625 multi-function sensors. Additional
> +	  options must be selected to enable device-specific functions.
> +
> +	  To compile this driver as a module, choose M here: the module will
> +	  be called iqs62x.
> +
>  config MFD_JANZ_CMODIO
>  	tristate "Janz CMOD-IO PCI MODULbus Carrier Board"
>  	select MFD_CORE
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index aed99f0..c4fc26b 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -232,6 +232,9 @@ obj-$(CONFIG_MFD_DLN2)		+= dln2.o
>  obj-$(CONFIG_MFD_RT5033)	+= rt5033.o
>  obj-$(CONFIG_MFD_SKY81452)	+= sky81452.o
> 
> +iqs62x-objs			:= iqs62x-core.o iqs62x-tables.o
> +obj-$(CONFIG_MFD_IQS62X)	+= iqs62x.o
> +
>  intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o
>  obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o
>  obj-$(CONFIG_INTEL_SOC_PMIC_BXTWC)	+= intel_soc_pmic_bxtwc.o
> diff --git a/drivers/mfd/iqs62x-core.c b/drivers/mfd/iqs62x-core.c
> new file mode 100644
> index 0000000..767f9d8
> --- /dev/null
> +++ b/drivers/mfd/iqs62x-core.c
> @@ -0,0 +1,639 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Azoteq IQS620A/621/622/624/625 Multi-Function Sensors
> + *
> + * Copyright (C) 2019 Jeff LaBundy <jeff@xxxxxxxxxxx>
> + *
> + * 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 data sheets 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/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>
> +
> +#include <linux/mfd/iqs62x.h>
> +
> +#define IQS62X_PROD_NUM				0x00
> +
> +#define IQS62X_SYS_FLAGS			0x10
> +#define IQS62X_SYS_FLAGS_IN_ATI			BIT(2)
> +
> +#define IQS622_PROX_SETTINGS_4			0x48
> +#define IQS620_PROX_SETTINGS_4			0x50
> +#define IQS620_PROX_SETTINGS_4_SAR_EN		BIT(7)
> +
> +#define IQS62X_SYS_SETTINGS			0xD0
> +#define IQS62X_SYS_SETTINGS_SOFT_RESET		BIT(7)
> +#define IQS62X_SYS_SETTINGS_ACK_RESET		BIT(6)
> +#define IQS62X_SYS_SETTINGS_EVENT_MODE		BIT(5)
> +#define IQS62X_SYS_SETTINGS_CLK_DIV		BIT(4)
> +#define IQS62X_SYS_SETTINGS_REDO_ATI		BIT(1)
> +
> +#define IQS62X_PWR_SETTINGS			0xD2
> +#define IQS62X_PWR_SETTINGS_DIS_AUTO		BIT(5)
> +#define IQS62X_PWR_SETTINGS_PWR_MODE_MASK	(BIT(4) | BIT(3))
> +#define IQS62X_PWR_SETTINGS_PWR_MODE_HALT	(BIT(4) | BIT(3))
> +#define IQS62X_PWR_SETTINGS_PWR_MODE_NORM	0
> +
> +#define IQS62X_OTP_CMD				0xF0
> +#define IQS62X_OTP_CMD_FG3			0x13
> +#define IQS62X_OTP_DATA				0xF1
> +#define IQS62X_MAX_REG				0xFF
> +
> +#define IQS62X_HALL_CAL_MASK			GENMASK(3, 0)
> +
> +#define IQS62X_FW_REC_TYPE_INFO			0
> +#define IQS62X_FW_REC_TYPE_PROD			1
> +#define IQS62X_FW_REC_TYPE_HALL			2
> +#define IQS62X_FW_REC_TYPE_MASK			3
> +#define IQS62X_FW_REC_TYPE_DATA			4
> +
> +struct iqs62x_fw_rec {
> +	u8 type;
> +	u8 addr;
> +	u8 len;
> +	u8 data;
> +} __packed;
> +
> +struct iqs62x_fw_blk {
> +	struct list_head list;
> +	u8 addr;
> +	u8 mask;
> +	u8 len;
> +	u8 data[];
> +};
> +
> +struct iqs62x_info {
> +	u8 prod_num;
> +	u8 sw_num;
> +	u8 hw_num;
> +} __packed;
> +
> +static int iqs62x_dev_init(struct iqs62x_core *iqs62x)
> +{
> +	struct iqs62x_fw_blk *fw_blk;
> +	unsigned int val;
> +	int ret;
> +	u8 clk_div = 1;
> +
> +	list_for_each_entry(fw_blk, &iqs62x->fw_blk_head, list) {
> +		if (fw_blk->mask)
> +			ret = regmap_update_bits(iqs62x->map, fw_blk->addr,
> +						 fw_blk->mask, *fw_blk->data);
> +		else
> +			ret = regmap_raw_write(iqs62x->map, 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->map, iqs62x->dev_desc->prod_num ==
> +				  IQS620_PROD_NUM ? IQS620_PROX_SETTINGS_4 :
> +						    IQS622_PROX_SETTINGS_4,

Not overly taken by this.  Can you pull it out please?

> +				  &val);
> +		if (ret)
> +			return ret;
> +
> +		if (val & IQS620_PROX_SETTINGS_4_SAR_EN)
> +			iqs62x->ui_sel = IQS62X_UI_SAR1;
> +		/* fall through */
> +

Prefer the '\n' above the comment.

> +	case IQS621_PROD_NUM:
> +		ret = regmap_write(iqs62x->map, 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->map, IQS624_HALL_UI,
> +				   IQS624_HALL_UI_WHL_EVENT |
> +				   IQS624_HALL_UI_INT_EVENT |
> +				   IQS624_HALL_UI_AUTO_CAL);
> +		if (ret)
> +			return ret;
> +
> +		ret = regmap_read(iqs62x->map, IQS624_INTERVAL_DIV, &val);
> +		if (ret)
> +			return ret;
> +
> +		if (val >= iqs62x->dev_desc->interval_div)
> +			break;

I think this would benefit from a comment.

> +		ret = regmap_write(iqs62x->map, IQS624_INTERVAL_DIV,
> +				   iqs62x->dev_desc->interval_div);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	ret = regmap_read(iqs62x->map, IQS62X_SYS_SETTINGS, &val);
> +	if (ret)
> +		return ret;
> +
> +	if (val & IQS62X_SYS_SETTINGS_CLK_DIV)
> +		clk_div = iqs62x->dev_desc->clk_div;
> +
> +	ret = regmap_write(iqs62x->map, IQS62X_SYS_SETTINGS, val |
> +			   IQS62X_SYS_SETTINGS_ACK_RESET |
> +			   IQS62X_SYS_SETTINGS_EVENT_MODE |
> +			   IQS62X_SYS_SETTINGS_REDO_ATI);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_read_poll_timeout(iqs62x->map, IQS62X_SYS_FLAGS, val,
> +				       !(val & IQS62X_SYS_FLAGS_IN_ATI),
> +				       10000, clk_div * 500000);

Would prefer if you define the magic numbers.

> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * The following delay accommodates the post-ATI stabilization time
> +	 * specified in the data sheet (with additional margin).

Nit: "datasheet"

> +	 */
> +	msleep(clk_div * 150);
> +
> +	return 0;
> +}
> +
> +static int iqs62x_fw_prs(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->map, IQS62X_OTP_CMD,
> +						   IQS62X_OTP_CMD_FG3);
> +				if (ret)
> +					break;
> +
> +				ret = regmap_read(iqs62x->map, 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;
> +}

Not sure I'm the best person to be reviewing this.

Maybe someone from drivers/firmware can help out?

> +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->map, 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]);
> +			/* fall through */
> +		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;
> +			/* fall through */
> +		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;
> +		}
> +	}
> +
> +	ret = blocking_notifier_call_chain(&iqs62x->nh, event_flags,
> +					   &event_data);
> +	if (ret & NOTIFY_STOP_MASK)
> +		return IRQ_NONE;
> +
> +	/*
> +	 * 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(50, 100);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static void iqs62x_fw_cb(const struct firmware *fw, void *context)

Not keen on these overly abbreviated function names.

What's 'cb'?

> +{
> +	struct iqs62x_core *iqs62x = context;
> +	struct i2c_client *client = iqs62x->client;
> +	int ret;
> +
> +	if (fw) {
> +		ret = iqs62x_fw_prs(iqs62x, fw);

I'm guessing 'prs' is parse, but only by the comment.

Please expand it to something more meaningful.

> +		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;
> +	}
> +
> +	ret = devm_mfd_add_devices(&client->dev, -1,

Please use the defines.

> +				   iqs62x->dev_desc->sub_devs,
> +				   iqs62x->dev_desc->num_sub_devs,
> +				   NULL, 0, NULL);
> +	if (ret)
> +		dev_err(&client->dev, "Failed to add devices: %d\n", ret);

"Failed to add child (or sub-) devices.

> +err_out:
> +	complete_all(&iqs62x->fw_done);
> +}
> +
> +static const struct regmap_config iqs62x_map_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->fw_done);
> +
> +	iqs62x->map = devm_regmap_init_i2c(client, &iqs62x_map_config);
> +	if (IS_ERR(iqs62x->map)) {
> +		ret = PTR_ERR(iqs62x->map);
> +		dev_err(&client->dev, "Failed to initialize register map: %d\n",
> +			ret);
> +		return ret;
> +	}
> +
> +	ret = regmap_raw_read(iqs62x->map, 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.
> +	 */

How long did it take you to supplement words out such that the lines
were all matched up at the ends? ;)

> +	for (i = 0; i < IQS62X_NUM_DEV; i++) {
> +		if (info.prod_num != iqs62x_devs[i].prod_num)
> +			continue;

'\n'

> +		iqs62x->dev_desc = &iqs62x_devs[i];
> +
> +		if (info.sw_num < iqs62x->dev_desc->sw_num)
> +			continue;

'\n'

> +		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.

You should mention why zeroed calibration values are a problem.

> +		 */
> +		for (j = 0; j < iqs62x->dev_desc->num_cal_regs; j++) {
> +			ret = regmap_read(iqs62x->map,
> +					  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 a device
> +		 * has been matched.

This is *what* you are doing, but *why* are you doing it?

> +		 */
> +		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 == IQS62X_NUM_DEV) {
> +		dev_err(&client->dev, "Uncalibrated device\n");
> +		return -ENODATA;
> +	}
> +
> +	ret = regmap_write(iqs62x->map, IQS62X_SYS_SETTINGS,
> +			   IQS62X_SYS_SETTINGS_SOFT_RESET);
> +	if (ret)
> +		return ret;

'\n'

> +	usleep_range(10000, 10100);
> +
> +	device_property_read_string(&client->dev, "firmware-name", &fw_name);
> +
> +	ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
> +				      fw_name ? : iqs62x->dev_desc->fw_name,
> +				      &client->dev, GFP_KERNEL, iqs62x,
> +				      iqs62x_fw_cb);
> +	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 data sheet, automatic mode switching must be disabled

"datasheet"

> +	 * before the device is placed in or taken out of halt mode.
> +	 */
> +	ret = regmap_update_bits(iqs62x->map, IQS62X_PWR_SETTINGS,
> +				 IQS62X_PWR_SETTINGS_DIS_AUTO,
> +				 IQS62X_PWR_SETTINGS_DIS_AUTO);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_update_bits(iqs62x->map, 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->map, IQS62X_PWR_SETTINGS,
> +				 IQS62X_PWR_SETTINGS_PWR_MODE_MASK,
> +				 IQS62X_PWR_SETTINGS_PWR_MODE_NORM);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_update_bits(iqs62x->map, 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@xxxxxxxxxxx>");
> +MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Multi-Function Sensors");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/mfd/iqs62x-tables.c b/drivers/mfd/iqs62x-tables.c

Why have you separated this out?

Prefer to see it as part of the main source file.

> new file mode 100644
> index 0000000..580f6ac
> --- /dev/null
> +++ b/drivers/mfd/iqs62x-tables.c
> @@ -0,0 +1,438 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Azoteq IQS620A/621/622/624/625 Multi-Function Sensors
> + *
> + * Copyright (C) 2019 Jeff LaBundy <jeff@xxxxxxxxxxx>
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/iqs62x.h>
> +
> +#define IQS620_HALL_FLAGS			0x16
> +#define IQS620_TEMP_CAL_MULT			0xC2
> +#define IQS620_TEMP_CAL_DIV			0xC3
> +#define IQS620_TEMP_CAL_OFFS			0xC4
> +
> +#define IQS621_HALL_FLAGS			0x19
> +#define IQS621_ALS_CAL_DIV_LUX			0x82
> +#define IQS621_ALS_CAL_DIV_IR			0x83
> +
> +#define IQS622_HALL_FLAGS			IQS621_HALL_FLAGS
> +
> +#define IQS624_INTERVAL_NUM			0x18
> +#define IQS625_INTERVAL_NUM			0x12
> +
> +static const struct mfd_cell iqs620at_sub_devs[] = {
> +	{
> +		.name = IQS62X_DRV_NAME_KEYS,

Really not keen on names being defined.

Can you use the proper names please?

> +		.of_compatible = "azoteq,iqs620a-keys",
> +	},
> +	{
> +		.name = IQS620_DRV_NAME_PWM,
> +		.of_compatible = "azoteq,iqs620a-pwm",
> +	},
> +	{
> +		.name = IQS620_DRV_NAME_TEMP,
> +	},
> +};
> +
> +static const struct mfd_cell iqs620a_sub_devs[] = {
> +	{
> +		.name = IQS62X_DRV_NAME_KEYS,
> +		.of_compatible = "azoteq,iqs620a-keys",
> +	},
> +	{
> +		.name = IQS620_DRV_NAME_PWM,
> +		.of_compatible = "azoteq,iqs620a-pwm",
> +	},
> +};
> +
> +static const struct mfd_cell iqs621_sub_devs[] = {
> +	{
> +		.name = IQS62X_DRV_NAME_KEYS,
> +		.of_compatible = "azoteq,iqs621-keys",
> +	},
> +	{
> +		.name = IQS621_DRV_NAME_ALS,
> +	},

One line please:

	{ .name = IQS621_DRV_NAME_ALS, },

> +};
> +
> +static const struct mfd_cell iqs622_sub_devs[] = {
> +	{
> +		.name = IQS62X_DRV_NAME_KEYS,
> +		.of_compatible = "azoteq,iqs622-keys",
> +	},
> +	{
> +		.name = IQS621_DRV_NAME_ALS,
> +	},

As above.

> +};
> +
> +static const struct mfd_cell iqs624_sub_devs[] = {
> +	{
> +		.name = IQS62X_DRV_NAME_KEYS,
> +		.of_compatible = "azoteq,iqs624-keys",
> +	},
> +	{
> +		.name = IQS624_DRV_NAME_POS,
> +	},
> +};

As above.

> +
> +static const struct mfd_cell iqs625_sub_devs[] = {
> +	{
> +		.name = IQS62X_DRV_NAME_KEYS,
> +		.of_compatible = "azoteq,iqs625-keys",
> +	},
> +	{
> +		.name = IQS624_DRV_NAME_POS,
> +	},

As above.

> +};
> +
> +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,
> +	},
> +};
> +
> +enum {
> +	IQS620AT_DEV,
> +	IQS620A_DEV,
> +	IQS621_DEV,
> +	IQS622_DEV,
> +	IQS624_DEV,
> +	IQS625_DEV,
> +};
> +
> +const struct iqs62x_dev_desc iqs62x_devs[IQS62X_NUM_DEV] = {
> +	[IQS620AT_DEV] = {
> +		.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),
> +
> +		.hall_flags	= IQS620_HALL_FLAGS,
> +
> +		.clk_div	= 4,
> +		.fw_name	= "iqs620a.bin",
> +		.event_regs	= &iqs620a_event_regs[IQS62X_UI_PROX],
> +	},
> +	[IQS620A_DEV] = {
> +		.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),
> +
> +		.hall_flags	= IQS620_HALL_FLAGS,
> +
> +		.clk_div	= 4,
> +		.fw_name	= "iqs620a.bin",
> +		.event_regs	= &iqs620a_event_regs[IQS62X_UI_PROX],
> +	},
> +	[IQS621_DEV] = {
> +		.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,
> +
> +		.clk_div	= 2,
> +		.fw_name	= "iqs621.bin",
> +		.event_regs	= &iqs621_event_regs[IQS62X_UI_PROX],
> +	},
> +	[IQS622_DEV] = {
> +		.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),
> +
> +		.als_flags	= IQS622_ALS_FLAGS,
> +		.hall_flags	= IQS622_HALL_FLAGS,
> +
> +		.clk_div	= 2,
> +		.fw_name	= "iqs622.bin",
> +		.event_regs	= &iqs622_event_regs[IQS62X_UI_PROX],
> +	},
> +	[IQS624_DEV] = {
> +		.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,
> +
> +		.clk_div	= 2,
> +		.fw_name	= "iqs624.bin",
> +		.event_regs	= &iqs624_event_regs[IQS62X_UI_PROX],
> +	},
> +	[IQS625_DEV] = {
> +		.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,
> +
> +		.clk_div	= 2,
> +		.fw_name	= "iqs625.bin",
> +		.event_regs	= &iqs625_event_regs[IQS62X_UI_PROX],
> +	},
> +};
> +EXPORT_SYMBOL_GPL(iqs62x_devs);
> +
> +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),
> +	},
> +};
> +EXPORT_SYMBOL_GPL(iqs62x_events);
> diff --git a/include/linux/mfd/iqs62x.h b/include/linux/mfd/iqs62x.h
> new file mode 100644
> index 0000000..0dc5997
> --- /dev/null
> +++ b/include/linux/mfd/iqs62x.h
> @@ -0,0 +1,146 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Azoteq IQS620A/621/622/624/625 Multi-Function Sensors
> + *
> + * Copyright (C) 2019 Jeff LaBundy <jeff@xxxxxxxxxxx>
> + */
> +
> +#ifndef __LINUX_MFD_IQS62X_H
> +#define __LINUX_MFD_IQS62X_H
> +
> +#define IQS620_PROD_NUM				0x41
> +#define IQS621_PROD_NUM				0x46
> +#define IQS622_PROD_NUM				0x42
> +#define IQS624_PROD_NUM				0x43
> +#define IQS625_PROD_NUM				0x4E
> +
> +#define IQS621_ALS_FLAGS			0x16
> +#define IQS622_ALS_FLAGS			0x14
> +
> +#define IQS624_HALL_UI				0x70
> +#define IQS624_HALL_UI_WHL_EVENT		BIT(4)
> +#define IQS624_HALL_UI_INT_EVENT		BIT(3)
> +#define IQS624_HALL_UI_AUTO_CAL			BIT(2)
> +
> +#define IQS624_INTERVAL_DIV			0x7D
> +
> +#define IQS620_GLBL_EVENT_MASK			0xD7
> +#define IQS620_GLBL_EVENT_MASK_PMU		BIT(6)
> +
> +#define IQS62X_NUM_DEV				6
> +#define IQS62X_NUM_KEYS				16
> +#define IQS62X_NUM_EVENTS			(IQS62X_NUM_KEYS + 5)
> +
> +#define IQS62X_EVENT_SIZE			10
> +
> +#define IQS62X_DRV_NAME_KEYS			"iqs62x-keys"
> +#define IQS620_DRV_NAME_TEMP			"iqs620at-temp"
> +#define IQS620_DRV_NAME_PWM			"iqs620a-pwm"
> +#define IQS621_DRV_NAME_ALS			"iqs621-als"
> +#define IQS624_DRV_NAME_POS			"iqs624-pos"
> +
> +enum iqs62x_ui_sel {
> +	IQS62X_UI_PROX,
> +	IQS62X_UI_SAR1,
> +};
> +
> +enum iqs62x_event_reg {
> +	IQS62X_EVENT_NONE,
> +	IQS62X_EVENT_SYS,
> +	IQS62X_EVENT_PROX,
> +	IQS62X_EVENT_HYST,
> +	IQS62X_EVENT_HALL,
> +	IQS62X_EVENT_ALS,
> +	IQS62X_EVENT_IR,
> +	IQS62X_EVENT_WHEEL,
> +	IQS62X_EVENT_INTER,
> +	IQS62X_EVENT_UI_LO,
> +	IQS62X_EVENT_UI_HI,
> +};
> +
> +enum iqs62x_event_flag {
> +	/* keys */
> +	IQS62X_EVENT_PROX_CH0_T,
> +	IQS62X_EVENT_PROX_CH0_P,
> +	IQS62X_EVENT_PROX_CH1_T,
> +	IQS62X_EVENT_PROX_CH1_P,
> +	IQS62X_EVENT_PROX_CH2_T,
> +	IQS62X_EVENT_PROX_CH2_P,
> +	IQS62X_EVENT_HYST_POS_T,
> +	IQS62X_EVENT_HYST_POS_P,
> +	IQS62X_EVENT_HYST_NEG_T,
> +	IQS62X_EVENT_HYST_NEG_P,
> +	IQS62X_EVENT_SAR1_ACT,
> +	IQS62X_EVENT_SAR1_QRD,
> +	IQS62X_EVENT_SAR1_MOVE,
> +	IQS62X_EVENT_SAR1_HALT,
> +	IQS62X_EVENT_WHEEL_UP,
> +	IQS62X_EVENT_WHEEL_DN,
> +
> +	/* switches */
> +	IQS62X_EVENT_HALL_N_T,
> +	IQS62X_EVENT_HALL_N_P,
> +	IQS62X_EVENT_HALL_S_T,
> +	IQS62X_EVENT_HALL_S_P,
> +
> +	/* everything else */
> +	IQS62X_EVENT_SYS_RESET,
> +};
> +
> +struct iqs62x_event_data {
> +	u16 ui_data;
> +	u8 als_flags;
> +	u8 ir_flags;
> +	u8 interval;
> +};
> +
> +struct iqs62x_event_desc {
> +	enum iqs62x_event_reg reg;
> +	u8 mask;
> +	u8 val;
> +};
> +
> +struct iqs62x_dev_desc {
> +	const char *dev_name;
> +	const struct mfd_cell *sub_devs;
> +	int num_sub_devs;
> +
> +	u8 prod_num;
> +	u8 sw_num;
> +	const u8 *cal_regs;
> +	int num_cal_regs;
> +
> +	u8 prox_mask;
> +	u8 sar_mask;
> +	u8 hall_mask;
> +	u8 hyst_mask;
> +	u8 temp_mask;
> +	u8 als_mask;
> +	u8 ir_mask;
> +
> +	u8 als_flags;
> +	u8 hall_flags;
> +	u8 hyst_shift;
> +
> +	u8 interval;
> +	u8 interval_div;
> +
> +	u8 clk_div;
> +	const char *fw_name;
> +	const enum iqs62x_event_reg (*event_regs)[IQS62X_EVENT_SIZE];
> +};
> +
> +struct iqs62x_core {
> +	const struct iqs62x_dev_desc *dev_desc;
> +	struct i2c_client *client;
> +	struct regmap *map;

Prefer 'regmap', it's less ambiguous.

> +	struct blocking_notifier_head nh;
> +	struct list_head fw_blk_head;
> +	struct completion fw_done;
> +	enum iqs62x_ui_sel ui_sel;
> +};
> +
> +extern const struct iqs62x_dev_desc iqs62x_devs[IQS62X_NUM_DEV];
> +extern const struct iqs62x_event_desc iqs62x_events[IQS62X_NUM_EVENTS];
> +
> +#endif /* __LINUX_MFD_IQS62X_H */

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog



[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux