root/drivers/mfd/iqs62x.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Azoteq IQS620A/621/622/624/625 Multi-Function Sensors
 *
 * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
 *
 * These devices rely on application-specific register settings and calibration
 * data developed in and exported from a suite of GUIs offered by the vendor. A
 * separate tool converts the GUIs' ASCII-based output into a standard firmware
 * file parsed by the driver.
 *
 * Link to datasheets and GUIs: https://www.azoteq.com/
 *
 * Link to conversion tool: https://github.com/jlabundy/iqs62x-h2bin.git
 */

#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mfd/core.h>
#include <linux/mfd/iqs62x.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/unaligned.h>

#define IQS62X_PROD_NUM                         0x00

#define IQS62X_SYS_FLAGS                        0x10

#define IQS620_HALL_FLAGS                       0x16
#define IQS621_HALL_FLAGS                       0x19
#define IQS622_HALL_FLAGS                       IQS621_HALL_FLAGS

#define IQS624_INTERVAL_NUM                     0x18
#define IQS625_INTERVAL_NUM                     0x12

#define IQS622_PROX_SETTINGS_4                  0x48
#define IQS620_PROX_SETTINGS_4                  0x50
#define IQS620_PROX_SETTINGS_4_SAR_EN           BIT(7)

#define IQS621_ALS_CAL_DIV_LUX                  0x82
#define IQS621_ALS_CAL_DIV_IR                   0x83

#define IQS620_TEMP_CAL_MULT                    0xC2
#define IQS620_TEMP_CAL_DIV                     0xC3
#define IQS620_TEMP_CAL_OFFS                    0xC4

#define IQS62X_SYS_SETTINGS                     0xD0
#define IQS62X_SYS_SETTINGS_ACK_RESET           BIT(6)
#define IQS62X_SYS_SETTINGS_EVENT_MODE          BIT(5)
#define IQS62X_SYS_SETTINGS_CLK_DIV             BIT(4)
#define IQS62X_SYS_SETTINGS_COMM_ATI            BIT(3)
#define IQS62X_SYS_SETTINGS_REDO_ATI            BIT(1)

#define IQS62X_PWR_SETTINGS                     0xD2
#define IQS62X_PWR_SETTINGS_DIS_AUTO            BIT(5)
#define IQS62X_PWR_SETTINGS_PWR_MODE_MASK       (BIT(4) | BIT(3))
#define IQS62X_PWR_SETTINGS_PWR_MODE_HALT       (BIT(4) | BIT(3))
#define IQS62X_PWR_SETTINGS_PWR_MODE_NORM       0

#define IQS62X_OTP_CMD                          0xF0
#define IQS62X_OTP_CMD_FG3                      0x13
#define IQS62X_OTP_DATA                         0xF1
#define IQS62X_MAX_REG                          0xFF

#define IQS62X_HALL_CAL_MASK                    GENMASK(3, 0)

#define IQS62X_FW_REC_TYPE_INFO                 0
#define IQS62X_FW_REC_TYPE_PROD                 1
#define IQS62X_FW_REC_TYPE_HALL                 2
#define IQS62X_FW_REC_TYPE_MASK                 3
#define IQS62X_FW_REC_TYPE_DATA                 4

#define IQS62X_ATI_STARTUP_MS                   350
#define IQS62X_FILT_SETTLE_MS                   250

struct iqs62x_fw_rec {
        u8 type;
        u8 addr;
        u8 len;
        u8 data;
} __packed;

struct iqs62x_fw_blk {
        struct list_head list;
        u8 addr;
        u8 mask;
        u8 len;
        u8 data[] __counted_by(len);
};

struct iqs62x_info {
        u8 prod_num;
        u8 sw_num;
        u8 hw_num;
} __packed;

static int iqs62x_dev_init(struct iqs62x_core *iqs62x)
{
        struct iqs62x_fw_blk *fw_blk;
        unsigned int val;
        int ret;

        list_for_each_entry(fw_blk, &iqs62x->fw_blk_head, list) {
                /*
                 * In case ATI is in progress, wait for it to complete before
                 * lowering the core clock frequency.
                 */
                if (fw_blk->addr == IQS62X_SYS_SETTINGS &&
                    *fw_blk->data & IQS62X_SYS_SETTINGS_CLK_DIV)
                        msleep(IQS62X_ATI_STARTUP_MS);

                if (fw_blk->mask)
                        ret = regmap_update_bits(iqs62x->regmap, fw_blk->addr,
                                                 fw_blk->mask, *fw_blk->data);
                else
                        ret = regmap_raw_write(iqs62x->regmap, fw_blk->addr,
                                               fw_blk->data, fw_blk->len);
                if (ret)
                        return ret;
        }

        switch (iqs62x->dev_desc->prod_num) {
        case IQS620_PROD_NUM:
        case IQS622_PROD_NUM:
                ret = regmap_read(iqs62x->regmap,
                                  iqs62x->dev_desc->prox_settings, &val);
                if (ret)
                        return ret;

                if (val & IQS620_PROX_SETTINGS_4_SAR_EN)
                        iqs62x->ui_sel = IQS62X_UI_SAR1;
                fallthrough;

        case IQS621_PROD_NUM:
                ret = regmap_write(iqs62x->regmap, IQS620_GLBL_EVENT_MASK,
                                   IQS620_GLBL_EVENT_MASK_PMU |
                                   iqs62x->dev_desc->prox_mask |
                                   iqs62x->dev_desc->sar_mask |
                                   iqs62x->dev_desc->hall_mask |
                                   iqs62x->dev_desc->hyst_mask |
                                   iqs62x->dev_desc->temp_mask |
                                   iqs62x->dev_desc->als_mask |
                                   iqs62x->dev_desc->ir_mask);
                if (ret)
                        return ret;
                break;

        default:
                ret = regmap_write(iqs62x->regmap, IQS624_HALL_UI,
                                   IQS624_HALL_UI_WHL_EVENT |
                                   IQS624_HALL_UI_INT_EVENT |
                                   IQS624_HALL_UI_AUTO_CAL);
                if (ret)
                        return ret;

                /*
                 * The IQS625 default interval divider is below the minimum
                 * permissible value, and the datasheet mandates that it is
                 * corrected during initialization (unless an updated value
                 * has already been provided by firmware).
                 *
                 * To protect against an unacceptably low user-entered value
                 * stored in the firmware, the same check is extended to the
                 * IQS624 as well.
                 */
                ret = regmap_read(iqs62x->regmap, IQS624_INTERVAL_DIV, &val);
                if (ret)
                        return ret;

                if (val >= iqs62x->dev_desc->interval_div)
                        break;

                ret = regmap_write(iqs62x->regmap, IQS624_INTERVAL_DIV,
                                   iqs62x->dev_desc->interval_div);
                if (ret)
                        return ret;
        }

        /*
         * Place the device in streaming mode at first so as not to miss the
         * limited number of interrupts that would otherwise occur after ATI
         * completes. The device is subsequently placed in event mode by the
         * interrupt handler.
         *
         * In the meantime, mask interrupts during ATI to prevent the device
         * from soliciting I2C traffic until the noise-sensitive ATI process
         * is complete.
         */
        ret = regmap_update_bits(iqs62x->regmap, IQS62X_SYS_SETTINGS,
                                 IQS62X_SYS_SETTINGS_ACK_RESET |
                                 IQS62X_SYS_SETTINGS_EVENT_MODE |
                                 IQS62X_SYS_SETTINGS_COMM_ATI |
                                 IQS62X_SYS_SETTINGS_REDO_ATI,
                                 IQS62X_SYS_SETTINGS_ACK_RESET |
                                 IQS62X_SYS_SETTINGS_REDO_ATI);
        if (ret)
                return ret;

        /*
         * The following delay gives the device time to deassert its RDY output
         * in case a communication window was open while the REDO_ATI field was
         * written. This prevents an interrupt from being serviced prematurely.
         */
        usleep_range(5000, 5100);

        return 0;
}

static int iqs62x_firmware_parse(struct iqs62x_core *iqs62x,
                                 const struct firmware *fw)
{
        struct i2c_client *client = iqs62x->client;
        struct iqs62x_fw_rec *fw_rec;
        struct iqs62x_fw_blk *fw_blk;
        unsigned int val;
        size_t pos = 0;
        int ret = 0;
        u8 mask, len, *data;
        u8 hall_cal_index = 0;

        while (pos < fw->size) {
                if (pos + sizeof(*fw_rec) > fw->size) {
                        ret = -EINVAL;
                        break;
                }
                fw_rec = (struct iqs62x_fw_rec *)(fw->data + pos);
                pos += sizeof(*fw_rec);

                if (pos + fw_rec->len - 1 > fw->size) {
                        ret = -EINVAL;
                        break;
                }
                pos += fw_rec->len - 1;

                switch (fw_rec->type) {
                case IQS62X_FW_REC_TYPE_INFO:
                        continue;

                case IQS62X_FW_REC_TYPE_PROD:
                        if (fw_rec->data == iqs62x->dev_desc->prod_num)
                                continue;

                        dev_err(&client->dev,
                                "Incompatible product number: 0x%02X\n",
                                fw_rec->data);
                        ret = -EINVAL;
                        break;

                case IQS62X_FW_REC_TYPE_HALL:
                        if (!hall_cal_index) {
                                ret = regmap_write(iqs62x->regmap,
                                                   IQS62X_OTP_CMD,
                                                   IQS62X_OTP_CMD_FG3);
                                if (ret)
                                        break;

                                ret = regmap_read(iqs62x->regmap,
                                                  IQS62X_OTP_DATA, &val);
                                if (ret)
                                        break;

                                hall_cal_index = val & IQS62X_HALL_CAL_MASK;
                                if (!hall_cal_index) {
                                        dev_err(&client->dev,
                                                "Uncalibrated device\n");
                                        ret = -ENODATA;
                                        break;
                                }
                        }

                        if (hall_cal_index > fw_rec->len) {
                                ret = -EINVAL;
                                break;
                        }

                        mask = 0;
                        data = &fw_rec->data + hall_cal_index - 1;
                        len = sizeof(*data);
                        break;

                case IQS62X_FW_REC_TYPE_MASK:
                        if (fw_rec->len < (sizeof(mask) + sizeof(*data))) {
                                ret = -EINVAL;
                                break;
                        }

                        mask = fw_rec->data;
                        data = &fw_rec->data + sizeof(mask);
                        len = sizeof(*data);
                        break;

                case IQS62X_FW_REC_TYPE_DATA:
                        mask = 0;
                        data = &fw_rec->data;
                        len = fw_rec->len;
                        break;

                default:
                        dev_err(&client->dev,
                                "Unrecognized record type: 0x%02X\n",
                                fw_rec->type);
                        ret = -EINVAL;
                }

                if (ret)
                        break;

                fw_blk = devm_kzalloc(&client->dev,
                                      struct_size(fw_blk, data, len),
                                      GFP_KERNEL);
                if (!fw_blk) {
                        ret = -ENOMEM;
                        break;
                }

                fw_blk->addr = fw_rec->addr;
                fw_blk->mask = mask;
                fw_blk->len = len;
                memcpy(fw_blk->data, data, len);

                list_add(&fw_blk->list, &iqs62x->fw_blk_head);
        }

        release_firmware(fw);

        return ret;
}

const struct iqs62x_event_desc iqs62x_events[IQS62X_NUM_EVENTS] = {
        [IQS62X_EVENT_PROX_CH0_T] = {
                .reg    = IQS62X_EVENT_PROX,
                .mask   = BIT(4),
                .val    = BIT(4),
        },
        [IQS62X_EVENT_PROX_CH0_P] = {
                .reg    = IQS62X_EVENT_PROX,
                .mask   = BIT(0),
                .val    = BIT(0),
        },
        [IQS62X_EVENT_PROX_CH1_T] = {
                .reg    = IQS62X_EVENT_PROX,
                .mask   = BIT(5),
                .val    = BIT(5),
        },
        [IQS62X_EVENT_PROX_CH1_P] = {
                .reg    = IQS62X_EVENT_PROX,
                .mask   = BIT(1),
                .val    = BIT(1),
        },
        [IQS62X_EVENT_PROX_CH2_T] = {
                .reg    = IQS62X_EVENT_PROX,
                .mask   = BIT(6),
                .val    = BIT(6),
        },
        [IQS62X_EVENT_PROX_CH2_P] = {
                .reg    = IQS62X_EVENT_PROX,
                .mask   = BIT(2),
                .val    = BIT(2),
        },
        [IQS62X_EVENT_HYST_POS_T] = {
                .reg    = IQS62X_EVENT_HYST,
                .mask   = BIT(6) | BIT(7),
                .val    = BIT(6),
        },
        [IQS62X_EVENT_HYST_POS_P] = {
                .reg    = IQS62X_EVENT_HYST,
                .mask   = BIT(5) | BIT(7),
                .val    = BIT(5),
        },
        [IQS62X_EVENT_HYST_NEG_T] = {
                .reg    = IQS62X_EVENT_HYST,
                .mask   = BIT(6) | BIT(7),
                .val    = BIT(6) | BIT(7),
        },
        [IQS62X_EVENT_HYST_NEG_P] = {
                .reg    = IQS62X_EVENT_HYST,
                .mask   = BIT(5) | BIT(7),
                .val    = BIT(5) | BIT(7),
        },
        [IQS62X_EVENT_SAR1_ACT] = {
                .reg    = IQS62X_EVENT_HYST,
                .mask   = BIT(4),
                .val    = BIT(4),
        },
        [IQS62X_EVENT_SAR1_QRD] = {
                .reg    = IQS62X_EVENT_HYST,
                .mask   = BIT(2),
                .val    = BIT(2),
        },
        [IQS62X_EVENT_SAR1_MOVE] = {
                .reg    = IQS62X_EVENT_HYST,
                .mask   = BIT(1),
                .val    = BIT(1),
        },
        [IQS62X_EVENT_SAR1_HALT] = {
                .reg    = IQS62X_EVENT_HYST,
                .mask   = BIT(0),
                .val    = BIT(0),
        },
        [IQS62X_EVENT_WHEEL_UP] = {
                .reg    = IQS62X_EVENT_WHEEL,
                .mask   = BIT(7) | BIT(6),
                .val    = BIT(7),
        },
        [IQS62X_EVENT_WHEEL_DN] = {
                .reg    = IQS62X_EVENT_WHEEL,
                .mask   = BIT(7) | BIT(6),
                .val    = BIT(7) | BIT(6),
        },
        [IQS62X_EVENT_HALL_N_T] = {
                .reg    = IQS62X_EVENT_HALL,
                .mask   = BIT(2) | BIT(0),
                .val    = BIT(2),
        },
        [IQS62X_EVENT_HALL_N_P] = {
                .reg    = IQS62X_EVENT_HALL,
                .mask   = BIT(1) | BIT(0),
                .val    = BIT(1),
        },
        [IQS62X_EVENT_HALL_S_T] = {
                .reg    = IQS62X_EVENT_HALL,
                .mask   = BIT(2) | BIT(0),
                .val    = BIT(2) | BIT(0),
        },
        [IQS62X_EVENT_HALL_S_P] = {
                .reg    = IQS62X_EVENT_HALL,
                .mask   = BIT(1) | BIT(0),
                .val    = BIT(1) | BIT(0),
        },
        [IQS62X_EVENT_SYS_RESET] = {
                .reg    = IQS62X_EVENT_SYS,
                .mask   = BIT(7),
                .val    = BIT(7),
        },
        [IQS62X_EVENT_SYS_ATI] = {
                .reg    = IQS62X_EVENT_SYS,
                .mask   = BIT(2),
                .val    = BIT(2),
        },
};
EXPORT_SYMBOL_GPL(iqs62x_events);

static irqreturn_t iqs62x_irq(int irq, void *context)
{
        struct iqs62x_core *iqs62x = context;
        struct i2c_client *client = iqs62x->client;
        struct iqs62x_event_data event_data;
        struct iqs62x_event_desc event_desc;
        enum iqs62x_event_reg event_reg;
        unsigned long event_flags = 0;
        int ret, i, j;
        u8 event_map[IQS62X_EVENT_SIZE];

        /*
         * The device asserts the RDY output to signal the beginning of a
         * communication window, which is closed by an I2C stop condition.
         * As such, all interrupt status is captured in a single read and
         * broadcast to any interested sub-device drivers.
         */
        ret = regmap_raw_read(iqs62x->regmap, IQS62X_SYS_FLAGS, event_map,
                              sizeof(event_map));
        if (ret) {
                dev_err(&client->dev, "Failed to read device status: %d\n",
                        ret);
                return IRQ_NONE;
        }

        for (i = 0; i < sizeof(event_map); i++) {
                event_reg = iqs62x->dev_desc->event_regs[iqs62x->ui_sel][i];

                switch (event_reg) {
                case IQS62X_EVENT_UI_LO:
                        event_data.ui_data = get_unaligned_le16(&event_map[i]);
                        fallthrough;

                case IQS62X_EVENT_UI_HI:
                case IQS62X_EVENT_NONE:
                        continue;

                case IQS62X_EVENT_ALS:
                        event_data.als_flags = event_map[i];
                        continue;

                case IQS62X_EVENT_IR:
                        event_data.ir_flags = event_map[i];
                        continue;

                case IQS62X_EVENT_INTER:
                        event_data.interval = event_map[i];
                        continue;

                case IQS62X_EVENT_HYST:
                        event_map[i] <<= iqs62x->dev_desc->hyst_shift;
                        fallthrough;

                case IQS62X_EVENT_WHEEL:
                case IQS62X_EVENT_HALL:
                case IQS62X_EVENT_PROX:
                case IQS62X_EVENT_SYS:
                        break;
                }

                for (j = 0; j < IQS62X_NUM_EVENTS; j++) {
                        event_desc = iqs62x_events[j];

                        if (event_desc.reg != event_reg)
                                continue;

                        if ((event_map[i] & event_desc.mask) == event_desc.val)
                                event_flags |= BIT(j);
                }
        }

        /*
         * The device resets itself in response to the I2C master stalling
         * communication past a fixed timeout. In this case, all registers
         * are restored and any interested sub-device drivers are notified.
         */
        if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
                dev_err(&client->dev, "Unexpected device reset\n");

                ret = iqs62x_dev_init(iqs62x);
                if (ret) {
                        dev_err(&client->dev,
                                "Failed to re-initialize device: %d\n", ret);
                        return IRQ_NONE;
                }

                iqs62x->event_cache |= BIT(IQS62X_EVENT_SYS_RESET);
                reinit_completion(&iqs62x->ati_done);
        } else if (event_flags & BIT(IQS62X_EVENT_SYS_ATI)) {
                iqs62x->event_cache |= BIT(IQS62X_EVENT_SYS_ATI);
                reinit_completion(&iqs62x->ati_done);
        } else if (!completion_done(&iqs62x->ati_done)) {
                ret = regmap_update_bits(iqs62x->regmap, IQS62X_SYS_SETTINGS,
                                         IQS62X_SYS_SETTINGS_EVENT_MODE, 0xFF);
                if (ret) {
                        dev_err(&client->dev,
                                "Failed to enable event mode: %d\n", ret);
                        return IRQ_NONE;
                }

                msleep(IQS62X_FILT_SETTLE_MS);
                complete_all(&iqs62x->ati_done);
        }

        /*
         * Reset and ATI events are not broadcast to the sub-device drivers
         * until ATI has completed. Any other events that may have occurred
         * during ATI are ignored.
         */
        if (completion_done(&iqs62x->ati_done)) {
                event_flags |= iqs62x->event_cache;
                ret = blocking_notifier_call_chain(&iqs62x->nh, event_flags,
                                                   &event_data);
                if (ret & NOTIFY_STOP_MASK)
                        return IRQ_NONE;

                iqs62x->event_cache = 0;
        }

        /*
         * Once the communication window is closed, a small delay is added to
         * ensure the device's RDY output has been deasserted by the time the
         * interrupt handler returns.
         */
        usleep_range(150, 200);

        return IRQ_HANDLED;
}

static void iqs62x_firmware_load(const struct firmware *fw, void *context)
{
        struct iqs62x_core *iqs62x = context;
        struct i2c_client *client = iqs62x->client;
        int ret;

        if (fw) {
                ret = iqs62x_firmware_parse(iqs62x, fw);
                if (ret) {
                        dev_err(&client->dev, "Failed to parse firmware: %d\n",
                                ret);
                        goto err_out;
                }
        }

        ret = iqs62x_dev_init(iqs62x);
        if (ret) {
                dev_err(&client->dev, "Failed to initialize device: %d\n", ret);
                goto err_out;
        }

        ret = devm_request_threaded_irq(&client->dev, client->irq,
                                        NULL, iqs62x_irq, IRQF_ONESHOT,
                                        client->name, iqs62x);
        if (ret) {
                dev_err(&client->dev, "Failed to request IRQ: %d\n", ret);
                goto err_out;
        }

        if (!wait_for_completion_timeout(&iqs62x->ati_done,
                                         msecs_to_jiffies(2000))) {
                dev_err(&client->dev, "Failed to complete ATI\n");
                goto err_out;
        }

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

err_out:
        complete_all(&iqs62x->fw_done);
}

static const struct mfd_cell iqs620at_sub_devs[] = {
        {
                .name = "iqs62x-keys",
                .of_compatible = "azoteq,iqs620a-keys",
        },
        {
                .name = "iqs620a-pwm",
                .of_compatible = "azoteq,iqs620a-pwm",
        },
        { .name = "iqs620at-temp", },
};

static const struct mfd_cell iqs620a_sub_devs[] = {
        {
                .name = "iqs62x-keys",
                .of_compatible = "azoteq,iqs620a-keys",
        },
        {
                .name = "iqs620a-pwm",
                .of_compatible = "azoteq,iqs620a-pwm",
        },
};

static const struct mfd_cell iqs621_sub_devs[] = {
        {
                .name = "iqs62x-keys",
                .of_compatible = "azoteq,iqs621-keys",
        },
        { .name = "iqs621-als", },
};

static const struct mfd_cell iqs622_sub_devs[] = {
        {
                .name = "iqs62x-keys",
                .of_compatible = "azoteq,iqs622-keys",
        },
        { .name = "iqs621-als", },
};

static const struct mfd_cell iqs624_sub_devs[] = {
        {
                .name = "iqs62x-keys",
                .of_compatible = "azoteq,iqs624-keys",
        },
        { .name = "iqs624-pos", },
};

static const struct mfd_cell iqs625_sub_devs[] = {
        {
                .name = "iqs62x-keys",
                .of_compatible = "azoteq,iqs625-keys",
        },
        { .name = "iqs624-pos", },
};

static const u8 iqs620at_cal_regs[] = {
        IQS620_TEMP_CAL_MULT,
        IQS620_TEMP_CAL_DIV,
        IQS620_TEMP_CAL_OFFS,
};

static const u8 iqs621_cal_regs[] = {
        IQS621_ALS_CAL_DIV_LUX,
        IQS621_ALS_CAL_DIV_IR,
};

static const enum iqs62x_event_reg iqs620a_event_regs[][IQS62X_EVENT_SIZE] = {
        [IQS62X_UI_PROX] = {
                IQS62X_EVENT_SYS,       /* 0x10 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_PROX,      /* 0x12 */
                IQS62X_EVENT_HYST,      /* 0x13 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_HALL,      /* 0x16 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
        },
        [IQS62X_UI_SAR1] = {
                IQS62X_EVENT_SYS,       /* 0x10 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_HYST,      /* 0x13 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_HALL,      /* 0x16 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
        },
};

static const enum iqs62x_event_reg iqs621_event_regs[][IQS62X_EVENT_SIZE] = {
        [IQS62X_UI_PROX] = {
                IQS62X_EVENT_SYS,       /* 0x10 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_PROX,      /* 0x12 */
                IQS62X_EVENT_HYST,      /* 0x13 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_ALS,       /* 0x16 */
                IQS62X_EVENT_UI_LO,     /* 0x17 */
                IQS62X_EVENT_UI_HI,     /* 0x18 */
                IQS62X_EVENT_HALL,      /* 0x19 */
        },
};

static const enum iqs62x_event_reg iqs622_event_regs[][IQS62X_EVENT_SIZE] = {
        [IQS62X_UI_PROX] = {
                IQS62X_EVENT_SYS,       /* 0x10 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_PROX,      /* 0x12 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_ALS,       /* 0x14 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_IR,        /* 0x16 */
                IQS62X_EVENT_UI_LO,     /* 0x17 */
                IQS62X_EVENT_UI_HI,     /* 0x18 */
                IQS62X_EVENT_HALL,      /* 0x19 */
        },
        [IQS62X_UI_SAR1] = {
                IQS62X_EVENT_SYS,       /* 0x10 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_HYST,      /* 0x13 */
                IQS62X_EVENT_ALS,       /* 0x14 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_IR,        /* 0x16 */
                IQS62X_EVENT_UI_LO,     /* 0x17 */
                IQS62X_EVENT_UI_HI,     /* 0x18 */
                IQS62X_EVENT_HALL,      /* 0x19 */
        },
};

static const enum iqs62x_event_reg iqs624_event_regs[][IQS62X_EVENT_SIZE] = {
        [IQS62X_UI_PROX] = {
                IQS62X_EVENT_SYS,       /* 0x10 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_PROX,      /* 0x12 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_WHEEL,     /* 0x14 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_UI_LO,     /* 0x16 */
                IQS62X_EVENT_UI_HI,     /* 0x17 */
                IQS62X_EVENT_INTER,     /* 0x18 */
                IQS62X_EVENT_NONE,
        },
};

static const enum iqs62x_event_reg iqs625_event_regs[][IQS62X_EVENT_SIZE] = {
        [IQS62X_UI_PROX] = {
                IQS62X_EVENT_SYS,       /* 0x10 */
                IQS62X_EVENT_PROX,      /* 0x11 */
                IQS62X_EVENT_INTER,     /* 0x12 */
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
                IQS62X_EVENT_NONE,
        },
};

static const struct iqs62x_dev_desc iqs62x_devs[] = {
        {
                .dev_name       = "iqs620at",
                .sub_devs       = iqs620at_sub_devs,
                .num_sub_devs   = ARRAY_SIZE(iqs620at_sub_devs),
                .prod_num       = IQS620_PROD_NUM,
                .sw_num         = 0x08,
                .cal_regs       = iqs620at_cal_regs,
                .num_cal_regs   = ARRAY_SIZE(iqs620at_cal_regs),
                .prox_mask      = BIT(0),
                .sar_mask       = BIT(1) | BIT(7),
                .hall_mask      = BIT(2),
                .hyst_mask      = BIT(3),
                .temp_mask      = BIT(4),
                .prox_settings  = IQS620_PROX_SETTINGS_4,
                .hall_flags     = IQS620_HALL_FLAGS,
                .fw_name        = "iqs620a.bin",
                .event_regs     = &iqs620a_event_regs[IQS62X_UI_PROX],
        },
        {
                .dev_name       = "iqs620a",
                .sub_devs       = iqs620a_sub_devs,
                .num_sub_devs   = ARRAY_SIZE(iqs620a_sub_devs),
                .prod_num       = IQS620_PROD_NUM,
                .sw_num         = 0x08,
                .prox_mask      = BIT(0),
                .sar_mask       = BIT(1) | BIT(7),
                .hall_mask      = BIT(2),
                .hyst_mask      = BIT(3),
                .temp_mask      = BIT(4),
                .prox_settings  = IQS620_PROX_SETTINGS_4,
                .hall_flags     = IQS620_HALL_FLAGS,
                .fw_name        = "iqs620a.bin",
                .event_regs     = &iqs620a_event_regs[IQS62X_UI_PROX],
        },
        {
                .dev_name       = "iqs621",
                .sub_devs       = iqs621_sub_devs,
                .num_sub_devs   = ARRAY_SIZE(iqs621_sub_devs),
                .prod_num       = IQS621_PROD_NUM,
                .sw_num         = 0x09,
                .cal_regs       = iqs621_cal_regs,
                .num_cal_regs   = ARRAY_SIZE(iqs621_cal_regs),
                .prox_mask      = BIT(0),
                .hall_mask      = BIT(1),
                .als_mask       = BIT(2),
                .hyst_mask      = BIT(3),
                .temp_mask      = BIT(4),
                .als_flags      = IQS621_ALS_FLAGS,
                .hall_flags     = IQS621_HALL_FLAGS,
                .hyst_shift     = 5,
                .fw_name        = "iqs621.bin",
                .event_regs     = &iqs621_event_regs[IQS62X_UI_PROX],
        },
        {
                .dev_name       = "iqs622",
                .sub_devs       = iqs622_sub_devs,
                .num_sub_devs   = ARRAY_SIZE(iqs622_sub_devs),
                .prod_num       = IQS622_PROD_NUM,
                .sw_num         = 0x06,
                .prox_mask      = BIT(0),
                .sar_mask       = BIT(1),
                .hall_mask      = BIT(2),
                .als_mask       = BIT(3),
                .ir_mask        = BIT(4),
                .prox_settings  = IQS622_PROX_SETTINGS_4,
                .als_flags      = IQS622_ALS_FLAGS,
                .hall_flags     = IQS622_HALL_FLAGS,
                .fw_name        = "iqs622.bin",
                .event_regs     = &iqs622_event_regs[IQS62X_UI_PROX],
        },
        {
                .dev_name       = "iqs624",
                .sub_devs       = iqs624_sub_devs,
                .num_sub_devs   = ARRAY_SIZE(iqs624_sub_devs),
                .prod_num       = IQS624_PROD_NUM,
                .sw_num         = 0x0B,
                .interval       = IQS624_INTERVAL_NUM,
                .interval_div   = 3,
                .fw_name        = "iqs624.bin",
                .event_regs     = &iqs624_event_regs[IQS62X_UI_PROX],
        },
        {
                .dev_name       = "iqs625",
                .sub_devs       = iqs625_sub_devs,
                .num_sub_devs   = ARRAY_SIZE(iqs625_sub_devs),
                .prod_num       = IQS625_PROD_NUM,
                .sw_num         = 0x0B,
                .interval       = IQS625_INTERVAL_NUM,
                .interval_div   = 10,
                .fw_name        = "iqs625.bin",
                .event_regs     = &iqs625_event_regs[IQS62X_UI_PROX],
        },
};

static const struct regmap_config iqs62x_regmap_config = {
        .reg_bits = 8,
        .val_bits = 8,
        .max_register = IQS62X_MAX_REG,
};

static int iqs62x_probe(struct i2c_client *client)
{
        struct iqs62x_core *iqs62x;
        struct iqs62x_info info;
        unsigned int val;
        int ret, i, j;
        const char *fw_name = NULL;

        iqs62x = devm_kzalloc(&client->dev, sizeof(*iqs62x), GFP_KERNEL);
        if (!iqs62x)
                return -ENOMEM;

        i2c_set_clientdata(client, iqs62x);
        iqs62x->client = client;

        BLOCKING_INIT_NOTIFIER_HEAD(&iqs62x->nh);
        INIT_LIST_HEAD(&iqs62x->fw_blk_head);

        init_completion(&iqs62x->ati_done);
        init_completion(&iqs62x->fw_done);

        iqs62x->regmap = devm_regmap_init_i2c(client, &iqs62x_regmap_config);
        if (IS_ERR(iqs62x->regmap)) {
                ret = PTR_ERR(iqs62x->regmap);
                dev_err(&client->dev, "Failed to initialize register map: %d\n",
                        ret);
                return ret;
        }

        ret = regmap_raw_read(iqs62x->regmap, IQS62X_PROD_NUM, &info,
                              sizeof(info));
        if (ret)
                return ret;

        /*
         * The following sequence validates the device's product and software
         * numbers. It then determines if the device is factory-calibrated by
         * checking for nonzero values in the device's designated calibration
         * registers (if applicable). Depending on the device, the absence of
         * calibration data indicates a reduced feature set or invalid device.
         *
         * For devices given in both calibrated and uncalibrated versions, the
         * calibrated version (e.g. IQS620AT) appears first in the iqs62x_devs
         * array. The uncalibrated version (e.g. IQS620A) appears next and has
         * the same product and software numbers, but no calibration registers
         * are specified.
         */
        for (i = 0; i < ARRAY_SIZE(iqs62x_devs); i++) {
                if (info.prod_num != iqs62x_devs[i].prod_num)
                        continue;

                iqs62x->dev_desc = &iqs62x_devs[i];

                if (info.sw_num < iqs62x->dev_desc->sw_num)
                        continue;

                iqs62x->sw_num = info.sw_num;
                iqs62x->hw_num = info.hw_num;

                /*
                 * Read each of the device's designated calibration registers,
                 * if any, and exit from the inner loop early if any are equal
                 * to zero (indicating the device is uncalibrated). This could
                 * be acceptable depending on the device (e.g. IQS620A instead
                 * of IQS620AT).
                 */
                for (j = 0; j < iqs62x->dev_desc->num_cal_regs; j++) {
                        ret = regmap_read(iqs62x->regmap,
                                          iqs62x->dev_desc->cal_regs[j], &val);
                        if (ret)
                                return ret;

                        if (!val)
                                break;
                }

                /*
                 * If the number of nonzero values read from the device equals
                 * the number of designated calibration registers (which could
                 * be zero), exit from the outer loop early to signal that the
                 * device's product and software numbers match a known device,
                 * and the device is calibrated (if applicable).
                 */
                if (j == iqs62x->dev_desc->num_cal_regs)
                        break;
        }

        if (!iqs62x->dev_desc) {
                dev_err(&client->dev, "Unrecognized product number: 0x%02X\n",
                        info.prod_num);
                return -EINVAL;
        }

        if (!iqs62x->sw_num) {
                dev_err(&client->dev, "Unrecognized software number: 0x%02X\n",
                        info.sw_num);
                return -EINVAL;
        }

        if (i == ARRAY_SIZE(iqs62x_devs)) {
                dev_err(&client->dev, "Uncalibrated device\n");
                return -ENODATA;
        }

        device_property_read_string(&client->dev, "firmware-name", &fw_name);

        ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
                                      fw_name ? : iqs62x->dev_desc->fw_name,
                                      &client->dev, GFP_KERNEL, iqs62x,
                                      iqs62x_firmware_load);
        if (ret)
                dev_err(&client->dev, "Failed to request firmware: %d\n", ret);

        return ret;
}

static void iqs62x_remove(struct i2c_client *client)
{
        struct iqs62x_core *iqs62x = i2c_get_clientdata(client);

        wait_for_completion(&iqs62x->fw_done);
}

static int __maybe_unused iqs62x_suspend(struct device *dev)
{
        struct iqs62x_core *iqs62x = dev_get_drvdata(dev);
        int ret;

        wait_for_completion(&iqs62x->fw_done);

        /*
         * As per the datasheet, automatic mode switching must be disabled
         * before the device is placed in or taken out of halt mode.
         */
        ret = regmap_update_bits(iqs62x->regmap, IQS62X_PWR_SETTINGS,
                                 IQS62X_PWR_SETTINGS_DIS_AUTO, 0xFF);
        if (ret)
                return ret;

        return regmap_update_bits(iqs62x->regmap, IQS62X_PWR_SETTINGS,
                                  IQS62X_PWR_SETTINGS_PWR_MODE_MASK,
                                  IQS62X_PWR_SETTINGS_PWR_MODE_HALT);
}

static int __maybe_unused iqs62x_resume(struct device *dev)
{
        struct iqs62x_core *iqs62x = dev_get_drvdata(dev);
        int ret;

        ret = regmap_update_bits(iqs62x->regmap, IQS62X_PWR_SETTINGS,
                                 IQS62X_PWR_SETTINGS_PWR_MODE_MASK,
                                 IQS62X_PWR_SETTINGS_PWR_MODE_NORM);
        if (ret)
                return ret;

        return regmap_update_bits(iqs62x->regmap, IQS62X_PWR_SETTINGS,
                                  IQS62X_PWR_SETTINGS_DIS_AUTO, 0);
}

static SIMPLE_DEV_PM_OPS(iqs62x_pm, iqs62x_suspend, iqs62x_resume);

static const struct of_device_id iqs62x_of_match[] = {
        { .compatible = "azoteq,iqs620a" },
        { .compatible = "azoteq,iqs621" },
        { .compatible = "azoteq,iqs622" },
        { .compatible = "azoteq,iqs624" },
        { .compatible = "azoteq,iqs625" },
        { }
};
MODULE_DEVICE_TABLE(of, iqs62x_of_match);

static struct i2c_driver iqs62x_i2c_driver = {
        .driver = {
                .name = "iqs62x",
                .of_match_table = iqs62x_of_match,
                .pm = &iqs62x_pm,
        },
        .probe = iqs62x_probe,
        .remove = iqs62x_remove,
};
module_i2c_driver(iqs62x_i2c_driver);

MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Multi-Function Sensors");
MODULE_LICENSE("GPL");