12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079 |
- // SPDX-License-Identifier: GPL-2.0+
- /*
- * Azoteq IQS620A/621/622/624/625 Multi-Function Sensors
- *
- * Copyright (C) 2019 Jeff LaBundy <[email protected]>
- *
- * These devices rely on application-specific register settings and calibration
- * data developed in and exported from a suite of GUIs offered by the vendor. A
- * separate tool converts the GUIs' ASCII-based output into a standard firmware
- * file parsed by the driver.
- *
- * Link to datasheets and GUIs: https://www.azoteq.com/
- *
- * Link to conversion tool: https://github.com/jlabundy/iqs62x-h2bin.git
- */
- #include <linux/completion.h>
- #include <linux/delay.h>
- #include <linux/device.h>
- #include <linux/err.h>
- #include <linux/firmware.h>
- #include <linux/i2c.h>
- #include <linux/interrupt.h>
- #include <linux/kernel.h>
- #include <linux/list.h>
- #include <linux/mfd/core.h>
- #include <linux/mfd/iqs62x.h>
- #include <linux/module.h>
- #include <linux/notifier.h>
- #include <linux/of_device.h>
- #include <linux/property.h>
- #include <linux/regmap.h>
- #include <linux/slab.h>
- #include <asm/unaligned.h>
- #define IQS62X_PROD_NUM 0x00
- #define IQS62X_SYS_FLAGS 0x10
- #define IQS620_HALL_FLAGS 0x16
- #define IQS621_HALL_FLAGS 0x19
- #define IQS622_HALL_FLAGS IQS621_HALL_FLAGS
- #define IQS624_INTERVAL_NUM 0x18
- #define IQS625_INTERVAL_NUM 0x12
- #define IQS622_PROX_SETTINGS_4 0x48
- #define IQS620_PROX_SETTINGS_4 0x50
- #define IQS620_PROX_SETTINGS_4_SAR_EN BIT(7)
- #define IQS621_ALS_CAL_DIV_LUX 0x82
- #define IQS621_ALS_CAL_DIV_IR 0x83
- #define IQS620_TEMP_CAL_MULT 0xC2
- #define IQS620_TEMP_CAL_DIV 0xC3
- #define IQS620_TEMP_CAL_OFFS 0xC4
- #define IQS62X_SYS_SETTINGS 0xD0
- #define IQS62X_SYS_SETTINGS_ACK_RESET BIT(6)
- #define IQS62X_SYS_SETTINGS_EVENT_MODE BIT(5)
- #define IQS62X_SYS_SETTINGS_CLK_DIV BIT(4)
- #define IQS62X_SYS_SETTINGS_COMM_ATI BIT(3)
- #define IQS62X_SYS_SETTINGS_REDO_ATI BIT(1)
- #define IQS62X_PWR_SETTINGS 0xD2
- #define IQS62X_PWR_SETTINGS_DIS_AUTO BIT(5)
- #define IQS62X_PWR_SETTINGS_PWR_MODE_MASK (BIT(4) | BIT(3))
- #define IQS62X_PWR_SETTINGS_PWR_MODE_HALT (BIT(4) | BIT(3))
- #define IQS62X_PWR_SETTINGS_PWR_MODE_NORM 0
- #define IQS62X_OTP_CMD 0xF0
- #define IQS62X_OTP_CMD_FG3 0x13
- #define IQS62X_OTP_DATA 0xF1
- #define IQS62X_MAX_REG 0xFF
- #define IQS62X_HALL_CAL_MASK GENMASK(3, 0)
- #define IQS62X_FW_REC_TYPE_INFO 0
- #define IQS62X_FW_REC_TYPE_PROD 1
- #define IQS62X_FW_REC_TYPE_HALL 2
- #define IQS62X_FW_REC_TYPE_MASK 3
- #define IQS62X_FW_REC_TYPE_DATA 4
- #define IQS62X_ATI_STARTUP_MS 350
- #define IQS62X_FILT_SETTLE_MS 250
- struct iqs62x_fw_rec {
- u8 type;
- u8 addr;
- u8 len;
- u8 data;
- } __packed;
- struct iqs62x_fw_blk {
- struct list_head list;
- u8 addr;
- u8 mask;
- u8 len;
- u8 data[];
- };
- struct iqs62x_info {
- u8 prod_num;
- u8 sw_num;
- u8 hw_num;
- } __packed;
- static int iqs62x_dev_init(struct iqs62x_core *iqs62x)
- {
- struct iqs62x_fw_blk *fw_blk;
- unsigned int val;
- int ret;
- list_for_each_entry(fw_blk, &iqs62x->fw_blk_head, list) {
- /*
- * In case ATI is in progress, wait for it to complete before
- * lowering the core clock frequency.
- */
- if (fw_blk->addr == IQS62X_SYS_SETTINGS &&
- *fw_blk->data & IQS62X_SYS_SETTINGS_CLK_DIV)
- msleep(IQS62X_ATI_STARTUP_MS);
- if (fw_blk->mask)
- ret = regmap_update_bits(iqs62x->regmap, fw_blk->addr,
- fw_blk->mask, *fw_blk->data);
- else
- ret = regmap_raw_write(iqs62x->regmap, fw_blk->addr,
- fw_blk->data, fw_blk->len);
- if (ret)
- return ret;
- }
- switch (iqs62x->dev_desc->prod_num) {
- case IQS620_PROD_NUM:
- case IQS622_PROD_NUM:
- ret = regmap_read(iqs62x->regmap,
- iqs62x->dev_desc->prox_settings, &val);
- if (ret)
- return ret;
- if (val & IQS620_PROX_SETTINGS_4_SAR_EN)
- iqs62x->ui_sel = IQS62X_UI_SAR1;
- fallthrough;
- case IQS621_PROD_NUM:
- ret = regmap_write(iqs62x->regmap, IQS620_GLBL_EVENT_MASK,
- IQS620_GLBL_EVENT_MASK_PMU |
- iqs62x->dev_desc->prox_mask |
- iqs62x->dev_desc->sar_mask |
- iqs62x->dev_desc->hall_mask |
- iqs62x->dev_desc->hyst_mask |
- iqs62x->dev_desc->temp_mask |
- iqs62x->dev_desc->als_mask |
- iqs62x->dev_desc->ir_mask);
- if (ret)
- return ret;
- break;
- default:
- ret = regmap_write(iqs62x->regmap, IQS624_HALL_UI,
- IQS624_HALL_UI_WHL_EVENT |
- IQS624_HALL_UI_INT_EVENT |
- IQS624_HALL_UI_AUTO_CAL);
- if (ret)
- return ret;
- /*
- * The IQS625 default interval divider is below the minimum
- * permissible value, and the datasheet mandates that it is
- * corrected during initialization (unless an updated value
- * has already been provided by firmware).
- *
- * To protect against an unacceptably low user-entered value
- * stored in the firmware, the same check is extended to the
- * IQS624 as well.
- */
- ret = regmap_read(iqs62x->regmap, IQS624_INTERVAL_DIV, &val);
- if (ret)
- return ret;
- if (val >= iqs62x->dev_desc->interval_div)
- break;
- ret = regmap_write(iqs62x->regmap, IQS624_INTERVAL_DIV,
- iqs62x->dev_desc->interval_div);
- if (ret)
- return ret;
- }
- /*
- * Place the device in streaming mode at first so as not to miss the
- * limited number of interrupts that would otherwise occur after ATI
- * completes. The device is subsequently placed in event mode by the
- * interrupt handler.
- *
- * In the meantime, mask interrupts during ATI to prevent the device
- * from soliciting I2C traffic until the noise-sensitive ATI process
- * is complete.
- */
- ret = regmap_update_bits(iqs62x->regmap, IQS62X_SYS_SETTINGS,
- IQS62X_SYS_SETTINGS_ACK_RESET |
- IQS62X_SYS_SETTINGS_EVENT_MODE |
- IQS62X_SYS_SETTINGS_COMM_ATI |
- IQS62X_SYS_SETTINGS_REDO_ATI,
- IQS62X_SYS_SETTINGS_ACK_RESET |
- IQS62X_SYS_SETTINGS_REDO_ATI);
- if (ret)
- return ret;
- /*
- * The following delay gives the device time to deassert its RDY output
- * in case a communication window was open while the REDO_ATI field was
- * written. This prevents an interrupt from being serviced prematurely.
- */
- usleep_range(5000, 5100);
- return 0;
- }
- static int iqs62x_firmware_parse(struct iqs62x_core *iqs62x,
- const struct firmware *fw)
- {
- struct i2c_client *client = iqs62x->client;
- struct iqs62x_fw_rec *fw_rec;
- struct iqs62x_fw_blk *fw_blk;
- unsigned int val;
- size_t pos = 0;
- int ret = 0;
- u8 mask, len, *data;
- u8 hall_cal_index = 0;
- while (pos < fw->size) {
- if (pos + sizeof(*fw_rec) > fw->size) {
- ret = -EINVAL;
- break;
- }
- fw_rec = (struct iqs62x_fw_rec *)(fw->data + pos);
- pos += sizeof(*fw_rec);
- if (pos + fw_rec->len - 1 > fw->size) {
- ret = -EINVAL;
- break;
- }
- pos += fw_rec->len - 1;
- switch (fw_rec->type) {
- case IQS62X_FW_REC_TYPE_INFO:
- continue;
- case IQS62X_FW_REC_TYPE_PROD:
- if (fw_rec->data == iqs62x->dev_desc->prod_num)
- continue;
- dev_err(&client->dev,
- "Incompatible product number: 0x%02X\n",
- fw_rec->data);
- ret = -EINVAL;
- break;
- case IQS62X_FW_REC_TYPE_HALL:
- if (!hall_cal_index) {
- ret = regmap_write(iqs62x->regmap,
- IQS62X_OTP_CMD,
- IQS62X_OTP_CMD_FG3);
- if (ret)
- break;
- ret = regmap_read(iqs62x->regmap,
- IQS62X_OTP_DATA, &val);
- if (ret)
- break;
- hall_cal_index = val & IQS62X_HALL_CAL_MASK;
- if (!hall_cal_index) {
- dev_err(&client->dev,
- "Uncalibrated device\n");
- ret = -ENODATA;
- break;
- }
- }
- if (hall_cal_index > fw_rec->len) {
- ret = -EINVAL;
- break;
- }
- mask = 0;
- data = &fw_rec->data + hall_cal_index - 1;
- len = sizeof(*data);
- break;
- case IQS62X_FW_REC_TYPE_MASK:
- if (fw_rec->len < (sizeof(mask) + sizeof(*data))) {
- ret = -EINVAL;
- break;
- }
- mask = fw_rec->data;
- data = &fw_rec->data + sizeof(mask);
- len = sizeof(*data);
- break;
- case IQS62X_FW_REC_TYPE_DATA:
- mask = 0;
- data = &fw_rec->data;
- len = fw_rec->len;
- break;
- default:
- dev_err(&client->dev,
- "Unrecognized record type: 0x%02X\n",
- fw_rec->type);
- ret = -EINVAL;
- }
- if (ret)
- break;
- fw_blk = devm_kzalloc(&client->dev,
- struct_size(fw_blk, data, len),
- GFP_KERNEL);
- if (!fw_blk) {
- ret = -ENOMEM;
- break;
- }
- fw_blk->addr = fw_rec->addr;
- fw_blk->mask = mask;
- fw_blk->len = len;
- memcpy(fw_blk->data, data, len);
- list_add(&fw_blk->list, &iqs62x->fw_blk_head);
- }
- release_firmware(fw);
- return ret;
- }
- const struct iqs62x_event_desc iqs62x_events[IQS62X_NUM_EVENTS] = {
- [IQS62X_EVENT_PROX_CH0_T] = {
- .reg = IQS62X_EVENT_PROX,
- .mask = BIT(4),
- .val = BIT(4),
- },
- [IQS62X_EVENT_PROX_CH0_P] = {
- .reg = IQS62X_EVENT_PROX,
- .mask = BIT(0),
- .val = BIT(0),
- },
- [IQS62X_EVENT_PROX_CH1_T] = {
- .reg = IQS62X_EVENT_PROX,
- .mask = BIT(5),
- .val = BIT(5),
- },
- [IQS62X_EVENT_PROX_CH1_P] = {
- .reg = IQS62X_EVENT_PROX,
- .mask = BIT(1),
- .val = BIT(1),
- },
- [IQS62X_EVENT_PROX_CH2_T] = {
- .reg = IQS62X_EVENT_PROX,
- .mask = BIT(6),
- .val = BIT(6),
- },
- [IQS62X_EVENT_PROX_CH2_P] = {
- .reg = IQS62X_EVENT_PROX,
- .mask = BIT(2),
- .val = BIT(2),
- },
- [IQS62X_EVENT_HYST_POS_T] = {
- .reg = IQS62X_EVENT_HYST,
- .mask = BIT(6) | BIT(7),
- .val = BIT(6),
- },
- [IQS62X_EVENT_HYST_POS_P] = {
- .reg = IQS62X_EVENT_HYST,
- .mask = BIT(5) | BIT(7),
- .val = BIT(5),
- },
- [IQS62X_EVENT_HYST_NEG_T] = {
- .reg = IQS62X_EVENT_HYST,
- .mask = BIT(6) | BIT(7),
- .val = BIT(6) | BIT(7),
- },
- [IQS62X_EVENT_HYST_NEG_P] = {
- .reg = IQS62X_EVENT_HYST,
- .mask = BIT(5) | BIT(7),
- .val = BIT(5) | BIT(7),
- },
- [IQS62X_EVENT_SAR1_ACT] = {
- .reg = IQS62X_EVENT_HYST,
- .mask = BIT(4),
- .val = BIT(4),
- },
- [IQS62X_EVENT_SAR1_QRD] = {
- .reg = IQS62X_EVENT_HYST,
- .mask = BIT(2),
- .val = BIT(2),
- },
- [IQS62X_EVENT_SAR1_MOVE] = {
- .reg = IQS62X_EVENT_HYST,
- .mask = BIT(1),
- .val = BIT(1),
- },
- [IQS62X_EVENT_SAR1_HALT] = {
- .reg = IQS62X_EVENT_HYST,
- .mask = BIT(0),
- .val = BIT(0),
- },
- [IQS62X_EVENT_WHEEL_UP] = {
- .reg = IQS62X_EVENT_WHEEL,
- .mask = BIT(7) | BIT(6),
- .val = BIT(7),
- },
- [IQS62X_EVENT_WHEEL_DN] = {
- .reg = IQS62X_EVENT_WHEEL,
- .mask = BIT(7) | BIT(6),
- .val = BIT(7) | BIT(6),
- },
- [IQS62X_EVENT_HALL_N_T] = {
- .reg = IQS62X_EVENT_HALL,
- .mask = BIT(2) | BIT(0),
- .val = BIT(2),
- },
- [IQS62X_EVENT_HALL_N_P] = {
- .reg = IQS62X_EVENT_HALL,
- .mask = BIT(1) | BIT(0),
- .val = BIT(1),
- },
- [IQS62X_EVENT_HALL_S_T] = {
- .reg = IQS62X_EVENT_HALL,
- .mask = BIT(2) | BIT(0),
- .val = BIT(2) | BIT(0),
- },
- [IQS62X_EVENT_HALL_S_P] = {
- .reg = IQS62X_EVENT_HALL,
- .mask = BIT(1) | BIT(0),
- .val = BIT(1) | BIT(0),
- },
- [IQS62X_EVENT_SYS_RESET] = {
- .reg = IQS62X_EVENT_SYS,
- .mask = BIT(7),
- .val = BIT(7),
- },
- [IQS62X_EVENT_SYS_ATI] = {
- .reg = IQS62X_EVENT_SYS,
- .mask = BIT(2),
- .val = BIT(2),
- },
- };
- EXPORT_SYMBOL_GPL(iqs62x_events);
- static irqreturn_t iqs62x_irq(int irq, void *context)
- {
- struct iqs62x_core *iqs62x = context;
- struct i2c_client *client = iqs62x->client;
- struct iqs62x_event_data event_data;
- struct iqs62x_event_desc event_desc;
- enum iqs62x_event_reg event_reg;
- unsigned long event_flags = 0;
- int ret, i, j;
- u8 event_map[IQS62X_EVENT_SIZE];
- /*
- * The device asserts the RDY output to signal the beginning of a
- * communication window, which is closed by an I2C stop condition.
- * As such, all interrupt status is captured in a single read and
- * broadcast to any interested sub-device drivers.
- */
- ret = regmap_raw_read(iqs62x->regmap, IQS62X_SYS_FLAGS, event_map,
- sizeof(event_map));
- if (ret) {
- dev_err(&client->dev, "Failed to read device status: %d\n",
- ret);
- return IRQ_NONE;
- }
- for (i = 0; i < sizeof(event_map); i++) {
- event_reg = iqs62x->dev_desc->event_regs[iqs62x->ui_sel][i];
- switch (event_reg) {
- case IQS62X_EVENT_UI_LO:
- event_data.ui_data = get_unaligned_le16(&event_map[i]);
- fallthrough;
- case IQS62X_EVENT_UI_HI:
- case IQS62X_EVENT_NONE:
- continue;
- case IQS62X_EVENT_ALS:
- event_data.als_flags = event_map[i];
- continue;
- case IQS62X_EVENT_IR:
- event_data.ir_flags = event_map[i];
- continue;
- case IQS62X_EVENT_INTER:
- event_data.interval = event_map[i];
- continue;
- case IQS62X_EVENT_HYST:
- event_map[i] <<= iqs62x->dev_desc->hyst_shift;
- fallthrough;
- case IQS62X_EVENT_WHEEL:
- case IQS62X_EVENT_HALL:
- case IQS62X_EVENT_PROX:
- case IQS62X_EVENT_SYS:
- break;
- }
- for (j = 0; j < IQS62X_NUM_EVENTS; j++) {
- event_desc = iqs62x_events[j];
- if (event_desc.reg != event_reg)
- continue;
- if ((event_map[i] & event_desc.mask) == event_desc.val)
- event_flags |= BIT(j);
- }
- }
- /*
- * The device resets itself in response to the I2C master stalling
- * communication past a fixed timeout. In this case, all registers
- * are restored and any interested sub-device drivers are notified.
- */
- if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {
- dev_err(&client->dev, "Unexpected device reset\n");
- ret = iqs62x_dev_init(iqs62x);
- if (ret) {
- dev_err(&client->dev,
- "Failed to re-initialize device: %d\n", ret);
- return IRQ_NONE;
- }
- iqs62x->event_cache |= BIT(IQS62X_EVENT_SYS_RESET);
- reinit_completion(&iqs62x->ati_done);
- } else if (event_flags & BIT(IQS62X_EVENT_SYS_ATI)) {
- iqs62x->event_cache |= BIT(IQS62X_EVENT_SYS_ATI);
- reinit_completion(&iqs62x->ati_done);
- } else if (!completion_done(&iqs62x->ati_done)) {
- ret = regmap_update_bits(iqs62x->regmap, IQS62X_SYS_SETTINGS,
- IQS62X_SYS_SETTINGS_EVENT_MODE, 0xFF);
- if (ret) {
- dev_err(&client->dev,
- "Failed to enable event mode: %d\n", ret);
- return IRQ_NONE;
- }
- msleep(IQS62X_FILT_SETTLE_MS);
- complete_all(&iqs62x->ati_done);
- }
- /*
- * Reset and ATI events are not broadcast to the sub-device drivers
- * until ATI has completed. Any other events that may have occurred
- * during ATI are ignored.
- */
- if (completion_done(&iqs62x->ati_done)) {
- event_flags |= iqs62x->event_cache;
- ret = blocking_notifier_call_chain(&iqs62x->nh, event_flags,
- &event_data);
- if (ret & NOTIFY_STOP_MASK)
- return IRQ_NONE;
- iqs62x->event_cache = 0;
- }
- /*
- * Once the communication window is closed, a small delay is added to
- * ensure the device's RDY output has been deasserted by the time the
- * interrupt handler returns.
- */
- usleep_range(150, 200);
- return IRQ_HANDLED;
- }
- static void iqs62x_firmware_load(const struct firmware *fw, void *context)
- {
- struct iqs62x_core *iqs62x = context;
- struct i2c_client *client = iqs62x->client;
- int ret;
- if (fw) {
- ret = iqs62x_firmware_parse(iqs62x, fw);
- if (ret) {
- dev_err(&client->dev, "Failed to parse firmware: %d\n",
- ret);
- goto err_out;
- }
- }
- ret = iqs62x_dev_init(iqs62x);
- if (ret) {
- dev_err(&client->dev, "Failed to initialize device: %d\n", ret);
- goto err_out;
- }
- ret = devm_request_threaded_irq(&client->dev, client->irq,
- NULL, iqs62x_irq, IRQF_ONESHOT,
- client->name, iqs62x);
- if (ret) {
- dev_err(&client->dev, "Failed to request IRQ: %d\n", ret);
- goto err_out;
- }
- if (!wait_for_completion_timeout(&iqs62x->ati_done,
- msecs_to_jiffies(2000))) {
- dev_err(&client->dev, "Failed to complete ATI\n");
- goto err_out;
- }
- ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE,
- iqs62x->dev_desc->sub_devs,
- iqs62x->dev_desc->num_sub_devs,
- NULL, 0, NULL);
- if (ret)
- dev_err(&client->dev, "Failed to add sub-devices: %d\n", ret);
- err_out:
- complete_all(&iqs62x->fw_done);
- }
- static const struct mfd_cell iqs620at_sub_devs[] = {
- {
- .name = "iqs62x-keys",
- .of_compatible = "azoteq,iqs620a-keys",
- },
- {
- .name = "iqs620a-pwm",
- .of_compatible = "azoteq,iqs620a-pwm",
- },
- { .name = "iqs620at-temp", },
- };
- static const struct mfd_cell iqs620a_sub_devs[] = {
- {
- .name = "iqs62x-keys",
- .of_compatible = "azoteq,iqs620a-keys",
- },
- {
- .name = "iqs620a-pwm",
- .of_compatible = "azoteq,iqs620a-pwm",
- },
- };
- static const struct mfd_cell iqs621_sub_devs[] = {
- {
- .name = "iqs62x-keys",
- .of_compatible = "azoteq,iqs621-keys",
- },
- { .name = "iqs621-als", },
- };
- static const struct mfd_cell iqs622_sub_devs[] = {
- {
- .name = "iqs62x-keys",
- .of_compatible = "azoteq,iqs622-keys",
- },
- { .name = "iqs621-als", },
- };
- static const struct mfd_cell iqs624_sub_devs[] = {
- {
- .name = "iqs62x-keys",
- .of_compatible = "azoteq,iqs624-keys",
- },
- { .name = "iqs624-pos", },
- };
- static const struct mfd_cell iqs625_sub_devs[] = {
- {
- .name = "iqs62x-keys",
- .of_compatible = "azoteq,iqs625-keys",
- },
- { .name = "iqs624-pos", },
- };
- static const u8 iqs620at_cal_regs[] = {
- IQS620_TEMP_CAL_MULT,
- IQS620_TEMP_CAL_DIV,
- IQS620_TEMP_CAL_OFFS,
- };
- static const u8 iqs621_cal_regs[] = {
- IQS621_ALS_CAL_DIV_LUX,
- IQS621_ALS_CAL_DIV_IR,
- };
- static const enum iqs62x_event_reg iqs620a_event_regs[][IQS62X_EVENT_SIZE] = {
- [IQS62X_UI_PROX] = {
- IQS62X_EVENT_SYS, /* 0x10 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_PROX, /* 0x12 */
- IQS62X_EVENT_HYST, /* 0x13 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_HALL, /* 0x16 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- },
- [IQS62X_UI_SAR1] = {
- IQS62X_EVENT_SYS, /* 0x10 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_HYST, /* 0x13 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_HALL, /* 0x16 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- },
- };
- static const enum iqs62x_event_reg iqs621_event_regs[][IQS62X_EVENT_SIZE] = {
- [IQS62X_UI_PROX] = {
- IQS62X_EVENT_SYS, /* 0x10 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_PROX, /* 0x12 */
- IQS62X_EVENT_HYST, /* 0x13 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_ALS, /* 0x16 */
- IQS62X_EVENT_UI_LO, /* 0x17 */
- IQS62X_EVENT_UI_HI, /* 0x18 */
- IQS62X_EVENT_HALL, /* 0x19 */
- },
- };
- static const enum iqs62x_event_reg iqs622_event_regs[][IQS62X_EVENT_SIZE] = {
- [IQS62X_UI_PROX] = {
- IQS62X_EVENT_SYS, /* 0x10 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_PROX, /* 0x12 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_ALS, /* 0x14 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_IR, /* 0x16 */
- IQS62X_EVENT_UI_LO, /* 0x17 */
- IQS62X_EVENT_UI_HI, /* 0x18 */
- IQS62X_EVENT_HALL, /* 0x19 */
- },
- [IQS62X_UI_SAR1] = {
- IQS62X_EVENT_SYS, /* 0x10 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_HYST, /* 0x13 */
- IQS62X_EVENT_ALS, /* 0x14 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_IR, /* 0x16 */
- IQS62X_EVENT_UI_LO, /* 0x17 */
- IQS62X_EVENT_UI_HI, /* 0x18 */
- IQS62X_EVENT_HALL, /* 0x19 */
- },
- };
- static const enum iqs62x_event_reg iqs624_event_regs[][IQS62X_EVENT_SIZE] = {
- [IQS62X_UI_PROX] = {
- IQS62X_EVENT_SYS, /* 0x10 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_PROX, /* 0x12 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_WHEEL, /* 0x14 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_UI_LO, /* 0x16 */
- IQS62X_EVENT_UI_HI, /* 0x17 */
- IQS62X_EVENT_INTER, /* 0x18 */
- IQS62X_EVENT_NONE,
- },
- };
- static const enum iqs62x_event_reg iqs625_event_regs[][IQS62X_EVENT_SIZE] = {
- [IQS62X_UI_PROX] = {
- IQS62X_EVENT_SYS, /* 0x10 */
- IQS62X_EVENT_PROX, /* 0x11 */
- IQS62X_EVENT_INTER, /* 0x12 */
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- IQS62X_EVENT_NONE,
- },
- };
- static const struct iqs62x_dev_desc iqs62x_devs[] = {
- {
- .dev_name = "iqs620at",
- .sub_devs = iqs620at_sub_devs,
- .num_sub_devs = ARRAY_SIZE(iqs620at_sub_devs),
- .prod_num = IQS620_PROD_NUM,
- .sw_num = 0x08,
- .cal_regs = iqs620at_cal_regs,
- .num_cal_regs = ARRAY_SIZE(iqs620at_cal_regs),
- .prox_mask = BIT(0),
- .sar_mask = BIT(1) | BIT(7),
- .hall_mask = BIT(2),
- .hyst_mask = BIT(3),
- .temp_mask = BIT(4),
- .prox_settings = IQS620_PROX_SETTINGS_4,
- .hall_flags = IQS620_HALL_FLAGS,
- .fw_name = "iqs620a.bin",
- .event_regs = &iqs620a_event_regs[IQS62X_UI_PROX],
- },
- {
- .dev_name = "iqs620a",
- .sub_devs = iqs620a_sub_devs,
- .num_sub_devs = ARRAY_SIZE(iqs620a_sub_devs),
- .prod_num = IQS620_PROD_NUM,
- .sw_num = 0x08,
- .prox_mask = BIT(0),
- .sar_mask = BIT(1) | BIT(7),
- .hall_mask = BIT(2),
- .hyst_mask = BIT(3),
- .temp_mask = BIT(4),
- .prox_settings = IQS620_PROX_SETTINGS_4,
- .hall_flags = IQS620_HALL_FLAGS,
- .fw_name = "iqs620a.bin",
- .event_regs = &iqs620a_event_regs[IQS62X_UI_PROX],
- },
- {
- .dev_name = "iqs621",
- .sub_devs = iqs621_sub_devs,
- .num_sub_devs = ARRAY_SIZE(iqs621_sub_devs),
- .prod_num = IQS621_PROD_NUM,
- .sw_num = 0x09,
- .cal_regs = iqs621_cal_regs,
- .num_cal_regs = ARRAY_SIZE(iqs621_cal_regs),
- .prox_mask = BIT(0),
- .hall_mask = BIT(1),
- .als_mask = BIT(2),
- .hyst_mask = BIT(3),
- .temp_mask = BIT(4),
- .als_flags = IQS621_ALS_FLAGS,
- .hall_flags = IQS621_HALL_FLAGS,
- .hyst_shift = 5,
- .fw_name = "iqs621.bin",
- .event_regs = &iqs621_event_regs[IQS62X_UI_PROX],
- },
- {
- .dev_name = "iqs622",
- .sub_devs = iqs622_sub_devs,
- .num_sub_devs = ARRAY_SIZE(iqs622_sub_devs),
- .prod_num = IQS622_PROD_NUM,
- .sw_num = 0x06,
- .prox_mask = BIT(0),
- .sar_mask = BIT(1),
- .hall_mask = BIT(2),
- .als_mask = BIT(3),
- .ir_mask = BIT(4),
- .prox_settings = IQS622_PROX_SETTINGS_4,
- .als_flags = IQS622_ALS_FLAGS,
- .hall_flags = IQS622_HALL_FLAGS,
- .fw_name = "iqs622.bin",
- .event_regs = &iqs622_event_regs[IQS62X_UI_PROX],
- },
- {
- .dev_name = "iqs624",
- .sub_devs = iqs624_sub_devs,
- .num_sub_devs = ARRAY_SIZE(iqs624_sub_devs),
- .prod_num = IQS624_PROD_NUM,
- .sw_num = 0x0B,
- .interval = IQS624_INTERVAL_NUM,
- .interval_div = 3,
- .fw_name = "iqs624.bin",
- .event_regs = &iqs624_event_regs[IQS62X_UI_PROX],
- },
- {
- .dev_name = "iqs625",
- .sub_devs = iqs625_sub_devs,
- .num_sub_devs = ARRAY_SIZE(iqs625_sub_devs),
- .prod_num = IQS625_PROD_NUM,
- .sw_num = 0x0B,
- .interval = IQS625_INTERVAL_NUM,
- .interval_div = 10,
- .fw_name = "iqs625.bin",
- .event_regs = &iqs625_event_regs[IQS62X_UI_PROX],
- },
- };
- static const struct regmap_config iqs62x_regmap_config = {
- .reg_bits = 8,
- .val_bits = 8,
- .max_register = IQS62X_MAX_REG,
- };
- static int iqs62x_probe(struct i2c_client *client)
- {
- struct iqs62x_core *iqs62x;
- struct iqs62x_info info;
- unsigned int val;
- int ret, i, j;
- 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_new = iqs62x_probe,
- .remove = iqs62x_remove,
- };
- module_i2c_driver(iqs62x_i2c_driver);
- MODULE_AUTHOR("Jeff LaBundy <[email protected]>");
- MODULE_DESCRIPTION("Azoteq IQS620A/621/622/624/625 Multi-Function Sensors");
- MODULE_LICENSE("GPL");
|