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> 304d9cf7dfSJeff LaBundy #include <linux/of_device.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 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 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 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 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 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 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 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 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 }, 1072*9816d859SUwe 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