Received: by 2002:ac0:98c7:0:0:0:0:0 with SMTP id g7-v6csp1375418imd; Thu, 1 Nov 2018 14:44:03 -0700 (PDT) X-Google-Smtp-Source: AJdET5cUg+091S9REejSZoCkL6Mew6tgvSGX4YAlGHehgvOlWKV5IAGO+E/GXA7dpuMnmfloO/Yl X-Received: by 2002:a63:1555:: with SMTP id 21-v6mr8561311pgv.383.1541108643768; Thu, 01 Nov 2018 14:44:03 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1541108643; cv=none; d=google.com; s=arc-20160816; b=JieL4WmWG0XJy3nxE8flCC9F4IZZybs+oyAf9I2FKdg2utHicCQUU0AIxW9HbJ715I zoe42CpvIYWhgkn2iUi2hFgqSQL2JmwbAtTNUHTGr7TDE5v1KI4nHSG1hfq7CyNI0QvD j++3HLJcDpX8/nf/BVzngVOO/P9/esjTRyXETwQYb45dGsS1/G51wOipi3Gu8BCErC8l 69RRfZ2wtjjOAsgYmiHnm1tDYcnXJ9mmrUn1Scc+2AyPnH1TcyMJGqrwnt0q5yJUr4X5 WvQxv3UmuCJGADv/T3hVX3ewZkNN9btOH1MJX19xPMc81VmrPla/JsODOoNbOuOm662z ZRNQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:cc:to:from:subject:mime-version :message-id:date:dkim-signature; bh=IrOwn0OPQIaBnTAa8QC87WvwTgleSodGpzCL11t574k=; b=EWDdWM4rL4lCjw8C8sXIgOuL6nFYYM0V964eTlKddALJItudOCjdkw2rcUKYun8Y/Q H3q66utlTLbWa6CQogy0fWOPPFjUJivPo63b6w+i3zF+zHfbcpuB7J0VYJVY7SQI5eo6 5yShWtz4DX1JrTpOatDDz9Jl4MqcAjJgCZZcnpJs+TBn8lehRXxIOi9EUmudNh3XjlUx dVTB58L1Fv12C3OG8oK7rwqlZmOuEJXAntz7uiET/namZHxhryD+qB7hNuHTc6UueKN2 79XOdue5//GoTBsKp0+3X2hmHsliGCJNy2q3WPH0X5hDav6lB/OEdP6kdOaisllVPXVa xsNg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=vCbXWQQZ; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id l63-v6si22197831plb.385.2018.11.01.14.43.47; Thu, 01 Nov 2018 14:44:03 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=vCbXWQQZ; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727339AbeKBGsL (ORCPT + 99 others); Fri, 2 Nov 2018 02:48:11 -0400 Received: from mail-qk1-f201.google.com ([209.85.222.201]:44343 "EHLO mail-qk1-f201.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726701AbeKBGsL (ORCPT ); Fri, 2 Nov 2018 02:48:11 -0400 Received: by mail-qk1-f201.google.com with SMTP id f22so6674025qkm.11 for ; Thu, 01 Nov 2018 14:43:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=date:message-id:mime-version:subject:from:to:cc; bh=IrOwn0OPQIaBnTAa8QC87WvwTgleSodGpzCL11t574k=; b=vCbXWQQZOeKLjkGenN7e1jhKurJqQwJpn/Mtdit7O9AV/v7gEOjbcS0Fw/RzuLYgSn HE2H/i6FjAZnyOtRJJ6oAGLyKs8Sx6VPMP/uNwlJA/H1tSVvZzzwmiDOWGv+xXXG+4qa e4ZGsIs2u/zUc/q3FCF/U+SPQ6Z2rXfc5cdKkMUDLz95dPD8/fz2cWy45SRfKWaFrWCJ OMJEu38CHGQUZx8kAcE7mIp5XOeVSGddWthPub0uLKOSCWZrvJjCtTbLz0k3TMc3w2Xt LoyWqjDZnN6Ul93n6iimpXM9ePOCHUpKj0iAJn56V7iPEqFpW6+aJINmNvIw8JtPP1Yd hdCg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:message-id:mime-version:subject:from:to:cc; bh=IrOwn0OPQIaBnTAa8QC87WvwTgleSodGpzCL11t574k=; b=k+3+WvX7oSqSE2W5e4KMMIQ1y8QCECY6qRyKsF3qHd7hiz8iYyeFkr65WEpG7DRhLj C2WeIMD8PFrx3tPcfXBsKC0AhVxXUAqmiQ6STeUmK9yxdh8G0n+N9gy4CPa/5mJoiZPp t2GhW0vu362pg5azTPjWZMI5v7Cm9b0m4WjvLbSzpkU8+jjmZbfu22u2rvOeyfps73vy r/7v3rTgvkAQm/l/mScM50HDKSLqk4sbSpPkBiA3+DCKDl+BCcYkChjIYkcfdnoVYTRp DhHio2uFNh5xKc5l2ntVCnhPaTfkuBSFUpVHx2ziO+2BZm8HJVRNRGmIfSENQZ1xl//z noBg== X-Gm-Message-State: AGRZ1gLrp5F+Hp9DVJ9/wwvMOZrtZ8duqJJsUijup+IbE6gu0jPCA7lu q7qGvQnRmcBPHEPxUnL61XPGeRqVimhPsg== X-Received: by 2002:a0c:93ec:: with SMTP id g41mr7108621qvg.30.1541108604150; Thu, 01 Nov 2018 14:43:24 -0700 (PDT) Date: Thu, 1 Nov 2018 14:43:13 -0700 Message-Id: <20181101214313.157556-1-egranata@google.com> Mime-Version: 1.0 X-Mailer: git-send-email 2.19.1.930.g4563a0d9d0-goog Subject: [PATCH] iio: sx932x: Add driver for Semtech sx932x proximity sensor From: Enrico Granata To: Jonathan Cameron , Hartmut Knaack , Lars-Peter Clausen , Peter Meerwald-Stadler , Enrico Granata , gwendal@chromium.org, patplunkett@google.com, Enric Balletbo i Serra Cc: linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org Content-Type: text/plain; charset="UTF-8" Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Enrico Granata This driver allows interfacing with the Semtech sx9320, sx9321 and sx9324 proximity sensors via the IIO subsystem. Signed-off-by: Enrico Granata --- .../bindings/iio/proximity/sx932x.txt | 54 + drivers/iio/proximity/Kconfig | 36 +- drivers/iio/proximity/Makefile | 1 + drivers/iio/proximity/sx932x.c | 1462 +++++++++++++++++ 4 files changed, 1542 insertions(+), 11 deletions(-) create mode 100644 Documentation/devicetree/bindings/iio/proximity/sx932x.txt create mode 100644 drivers/iio/proximity/sx932x.c diff --git a/Documentation/devicetree/bindings/iio/proximity/sx932x.txt b/Documentation/devicetree/bindings/iio/proximity/sx932x.txt new file mode 100644 index 0000000000000..e1f1fefdbca08 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/proximity/sx932x.txt @@ -0,0 +1,54 @@ +Semtech's SX9320/SX9321/SX9324 capacitive proximity button device driver + +Required properties: + - compatible: must be "semtech,sx9320 or sx9321 or sx9324" + - reg: i2c address where to find the device + - interrupt-parent : should be the phandle for the interrupt controller + - interrupts : the sole interrupt generated by the device + - semtech,register-prefix: the string prefixing all register names in the + + Refer to interrupt-controller/interrupts.txt for generic + interrupt client node bindings. + +Optional properties: + - semtech,adv_ctrl10: Initial value for register AdvCtrl10. + - semtech,afe_ctrl4: Initial value for register AfeCtrl4. + - semtech,afe_ctrl7: Initial value for register AfeCtrl7. + - semtech,afe_ctrl9: Initial value for register AfeCtrl9. + - semtech,prox_ctrl0: Initial value for register ProxCtrl0. + - semtech,prox_ctrl1: Initial value for register ProxCtrl1. + - semtech,prox_ctrl2: Initial value for register ProxCtrl2. + - semtech,prox_ctrl4: Initial value for register ProxCtrl4. + - semtech,prox_ctrl5: Initial value for register ProxCtrl5. + - semtech,prox_ctrl6: Initial value for register ProxCtrl6. + - semtech,prox_ctrl7: Initial value for register Prox_Trl7. + - semtech,adv_ctrl16: Initial value for register AdvCtrl16. + - semtech,adv_ctrl17: Initial value for register AdvCtrl17. + - semtech,adv_ctrl18: Initial value for register AdvCtrl18. + - semtech,gnrl_ctrl1: Initial value for register GnrlCtrl1. + + Refer to the data sheet for register definitions. + +Example: + +sx9310@28 { + compatible = "semtech,sx9320"; + reg = <0x28>; + interrupt-parent = <&gpio2>; + interrupts = <16 IRQ_TYPE_LEVEL_LOW>; +}; + +Driver supports ACPI bindings. We can store registers configuration obtained +from per-model SAR sensor calibration. Refer to the data sheet for meaning. + +ACPI name is STH9320 or STH9321 or STH9324. + +Example: + chip drivers/i2c/generic + register "hid" = ""STH9321"" + register "name" = ""SEMTECH SX9321"" + register "desc" = ""SAR Proximity Sensor"" + register "irq" = "ACPI_IRQ_LEVEL_LOW(GPP_A18_IRQ)" + register "device_present_gpio" = "GPP_B20" + device i2c 28 on end + end diff --git a/drivers/iio/proximity/Kconfig b/drivers/iio/proximity/Kconfig index b99367a89f810..3b979b8198d7c 100644 --- a/drivers/iio/proximity/Kconfig +++ b/drivers/iio/proximity/Kconfig @@ -66,31 +66,45 @@ config SRF04 To compile this driver as a module, choose M here: the module will be called srf04. -config SX9500 - tristate "SX9500 Semtech proximity sensor" +config SRF08 + tristate "Devantech SRF02/SRF08/SRF10 ultrasonic ranger sensor" + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + depends on I2C + help + Say Y here to build a driver for Devantech SRF02/SRF08/SRF10 + ultrasonic ranger sensors with i2c interface. + This driver can be used to measure the distance of objects. + + To compile this driver as a module, choose M here: the + module will be called srf08. + +config SX932X + tristate "sx932x Semtech proximity sensor" select IIO_BUFFER select IIO_TRIGGERED_BUFFER select REGMAP_I2C depends on I2C help - Say Y here to build a driver for Semtech's SX9500 capacitive - proximity/button sensor. + Say Y here to build a driver for Semtech's sx932x capacitive + proximity/button sensor. This driver supports Semtech's + sx9320, sx9321 and sx9324 sensors. To compile this driver as a module, choose M here: the - module will be called sx9500. + module will be called sx932x. -config SRF08 - tristate "Devantech SRF02/SRF08/SRF10 ultrasonic ranger sensor" +config SX9500 + tristate "SX9500 Semtech proximity sensor" select IIO_BUFFER select IIO_TRIGGERED_BUFFER + select REGMAP_I2C depends on I2C help - Say Y here to build a driver for Devantech SRF02/SRF08/SRF10 - ultrasonic ranger sensors with i2c interface. - This driver can be used to measure the distance of objects. + Say Y here to build a driver for Semtech's SX9500 capacitive + proximity/button sensor. To compile this driver as a module, choose M here: the - module will be called srf08. + module will be called sx9500. config VL53L0X_I2C tristate "STMicroelectronics VL53L0X ToF ranger sensor (I2C)" diff --git a/drivers/iio/proximity/Makefile b/drivers/iio/proximity/Makefile index 6d031f903c4c2..65ac237d63dd6 100644 --- a/drivers/iio/proximity/Makefile +++ b/drivers/iio/proximity/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_LIDAR_LITE_V2) += pulsedlight-lidar-lite-v2.o obj-$(CONFIG_RFD77402) += rfd77402.o obj-$(CONFIG_SRF04) += srf04.o obj-$(CONFIG_SRF08) += srf08.o +obj-$(CONFIG_SX932X) += sx932x.o obj-$(CONFIG_SX9500) += sx9500.o obj-$(CONFIG_VL53L0X_I2C) += vl53l0x-i2c.o diff --git a/drivers/iio/proximity/sx932x.c b/drivers/iio/proximity/sx932x.c new file mode 100644 index 0000000000000..069d3678f6bc9 --- /dev/null +++ b/drivers/iio/proximity/sx932x.c @@ -0,0 +1,1462 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2018 Google LLC. + * + * Driver for Semtech's SX9320 capacitive proximity/button solution. + * Datasheet available at + * . + * Based on SX9500 driver and Semtech driver using the input framework + * . + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define SX932X_DRIVER_NAME "sx932x" +#define SX9320_ACPI_NAME "STH9320" +#define SX9321_ACPI_NAME "STH9321" +#define SX9324_ACPI_NAME "STH9324" +#define SX932X_DEV_NB 2 + +#define SX932X_IRQ_NAME "sx932x_event" + +#define SX932X_GPIO_INT "interrupt" + +/* Register definitions. */ +#define SX932X_REG_IRQ_SRC 0x00 +#define SX932X_REG_STAT0 0x01 +#define SX932X_REG_STAT1 0x02 +#define SX932X_REG_STAT2 0x03 +#define SX932X_REG_STAT3 0x04 +#define SX932X_REG_IRQ_MSK 0x05 +#define SX932X_REG_IRQ_CFG0 0x06 +#define SX932X_REG_IRQ_CFG2 0x07 + +#define SX932X_REG_GNRL_CTRL0 0x10 +#define SX932X_REG_GNRL_CTRL1 0x11 + +#define SX932X_REG_I2C_ADDR 0x14 +#define SX932X_REG_CLK_SPRD 0x15 + +#define SX932X_REG_AFE_CTRL0 0x20 +#define SX932X_REG_AFE_CTRL1 0x21 +#define SX932X_REG_AFE_CTRL2 0x22 +#define SX932X_REG_AFE_CTRL3 0x23 +#define SX932X_REG_AFE_CTRL4 0x24 +#define SX932X_REG_AFE_CTRL5 0x25 +#define SX932X_REG_AFE_CTRL6 0x26 +#define SX932X_REG_AFE_CTRL7 0x27 +#define SX932X_REG_AFE_PH0 0x28 +#define SX932X_REG_AFE_PH1 0x29 +#define SX932X_REG_AFE_PH2 0x2a +#define SX932X_REG_AFE_PH3 0x2b +#define SX932X_REG_AFE_CTRL8 0x2c +#define SX932X_REG_AFE_CTRL9 0x2d + +#define SX932X_REG_PROX_CTRL0 0x30 +#define SX932X_REG_PROX_CTRL1 0x31 +#define SX932X_REG_PROX_CTRL2 0x32 +#define SX932X_REG_PROX_CTRL3 0x33 +#define SX932X_REG_PROX_CTRL4 0x34 +#define SX932X_REG_PROX_CTRL5 0x35 +#define SX932X_REG_PROX_CTRL6 0x36 +#define SX932X_REG_PROX_CTRL7 0x37 + +#define SX932X_REG_ADV_CTRL0 0x40 +#define SX932X_REG_ADV_CTRL1 0x41 +#define SX932X_REG_ADV_CTRL2 0x42 +#define SX932X_REG_ADV_CTRL3 0x43 +#define SX932X_REG_ADV_CTRL4 0x44 +#define SX932X_REG_ADV_CTRL5 0x45 +#define SX932X_REG_ADV_CTRL6 0x46 +#define SX932X_REG_ADV_CTRL7 0x47 +#define SX932X_REG_ADV_CTRL8 0x48 +#define SX932X_REG_ADV_CTRL9 0x49 +#define SX932X_REG_ADV_CTRL10 0x4a +#define SX932X_REG_ADV_CTRL11 0x4b +#define SX932X_REG_ADV_CTRL12 0x4c +#define SX932X_REG_ADV_CTRL13 0x4d +#define SX932X_REG_ADV_CTRL14 0x4e +#define SX932X_REG_ADV_CTRL15 0x4f +#define SX932X_REG_ADV_CTRL16 0x50 +#define SX932X_REG_ADV_CTRL17 0x51 +#define SX932X_REG_ADV_CTRL18 0x52 +#define SX932X_REG_ADV_CTRL19 0x53 +#define SX932X_REG_ADV_CTRL20 0x54 + +#define SX932X_REG_PHASE_SEL 0x60 + +#define SX932X_REG_USE_MSB 0x61 +#define SX932X_REG_USE_LSB 0x62 + +#define SX932X_REG_AVG_MSB 0x63 +#define SX932X_REG_AVG_LSB 0x64 + +#define SX932X_REG_DIFF_MSB 0x65 +#define SX932X_REG_DIFF_LSB 0x66 + +#define SX932X_REG_OFFSET_MSB 0x67 +#define SX932X_REG_OFFSET_LSB 0x68 + +#define SX932X_REG_SAR_MSB 0x69 +#define SX932X_REG_SAR_LSB 0x6a + +#define SX932X_REG_RESET 0x9f +/* Write this to REG_RESET to do a soft reset. */ +#define SX932X_SOFT_RESET 0xde + +#define SX932X_REG_WHOAMI 0xfa +#define SX9320_WHOAMI 0x20 +#define SX9321_WHOAMI 0x21 +#define SX9324_WHOAMI 0x23 +#define SX932X_REG_REVISION 0xfe + + +/* Sensor Readback */ + +/* + * These serve for identifying IRQ source in the IRQ_SRC register, and + * also for masking the IRQs in the IRQ_MSK register. + */ +#define SX932X_RESET_IRQ BIT(7) +#define SX932X_CLOSE_IRQ BIT(6) +#define SX932X_FAR_IRQ BIT(5) +#define SX932X_COMPDONE_IRQ BIT(4) +#define SX932X_CONVDONE_IRQ BIT(3) + +#define SX932X_SCAN_PERIOD_MASK GENMASK(7, 4) +#define SX932X_SCAN_PERIOD_SHIFT 4 + +#define SX932X_COMPSTAT_MASK GENMASK(3, 0) + +/* 4 channels, as defined in STAT0: PH0, PH1, PH2 and PH3. */ +#define SX932X_NUM_CHANNELS 4 +#define SX932X_CHAN_MASK GENMASK(1, 0) + +struct sx932x_reg_config { + const char *property; + u8 reg_addr; + u8 def_value; +}; + +struct sx932x_data { + struct mutex mutex; + struct i2c_client *client; + const struct sx932x_reg_config *reg_config; + struct iio_trigger *trig; + struct regmap *regmap; + /* + * Last reading of the proximity status for each channel. + * We only send an event to user space when this changes. + */ + bool prox_stat[SX932X_NUM_CHANNELS]; + bool event_enabled[SX932X_NUM_CHANNELS]; + bool trigger_enabled; + u16 *buffer; + /* Remember which phases are enabled during a suspend. */ + unsigned int suspend_reg_gnrl_ctrl1; + struct completion completion; + int data_rdy_users, close_far_users; + int channel_users[SX932X_NUM_CHANNELS]; +}; + +static const struct iio_event_spec sx932x_events[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_EITHER, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + }, +}; + +#define SX932X_RAW_CHANNEL(idx, name, addr) \ + { \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = 1, \ + .channel = idx, \ + .address = addr, \ + .event_spec = sx932x_events, \ + .num_event_specs = ARRAY_SIZE(sx932x_events), \ + .extend_name = name, \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .shift = 0, \ + }, \ + } + +#define SX932X_PROCESSED_CHANNEL(idx, name, addr) \ + { \ + .type = IIO_PROXIMITY, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .indexed = 1, \ + .channel = idx, \ + .address = addr, \ + .event_spec = sx932x_events, \ + .num_event_specs = ARRAY_SIZE(sx932x_events), \ + .extend_name = name, \ + .scan_index = idx, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .shift = 0, \ + }, \ + } + +static const struct iio_chan_spec sx932x_channels[] = { + SX932X_RAW_CHANNEL(0, "UC0", SX932X_REG_USE_MSB), + SX932X_RAW_CHANNEL(1, "UC1", SX932X_REG_USE_MSB), + SX932X_RAW_CHANNEL(2, "UC2", SX932X_REG_USE_MSB), + SX932X_RAW_CHANNEL(3, "UC3", SX932X_REG_USE_MSB), + + SX932X_RAW_CHANNEL(4, "AC0", SX932X_REG_AVG_MSB), + SX932X_RAW_CHANNEL(5, "AC1", SX932X_REG_AVG_MSB), + SX932X_RAW_CHANNEL(6, "AC2", SX932X_REG_AVG_MSB), + SX932X_RAW_CHANNEL(7, "AC3", SX932X_REG_AVG_MSB), + + SX932X_RAW_CHANNEL(8, "DC0", SX932X_REG_DIFF_MSB), + SX932X_RAW_CHANNEL(9, "DC1", SX932X_REG_DIFF_MSB), + SX932X_RAW_CHANNEL(10, "DC2", SX932X_REG_DIFF_MSB), + SX932X_RAW_CHANNEL(11, "DC3", SX932X_REG_DIFF_MSB), + + SX932X_RAW_CHANNEL(12, "CO0", SX932X_REG_OFFSET_MSB), + SX932X_RAW_CHANNEL(13, "CO1", SX932X_REG_OFFSET_MSB), + SX932X_RAW_CHANNEL(14, "CO2", SX932X_REG_OFFSET_MSB), + SX932X_RAW_CHANNEL(15, "CO3", SX932X_REG_OFFSET_MSB), + + IIO_CHAN_SOFT_TIMESTAMP(16), +}; + +static const struct { + int val; + int val2; +} sx932x_samp_freq_table[] = { + {500, 0}, /* 0000: Min (no idle time) */ + {66, 666666}, /* 0001: 15 ms */ + {33, 333333}, /* 0010: 30 ms (Typ.) */ + {22, 222222}, /* 0011: 45 ms */ + {16, 666666}, /* 0100: 60 ms */ + {11, 111111}, /* 0101: 90 ms */ + {8, 333333}, /* 0110: 120 ms */ + {5, 0}, /* 0111: 200 ms */ + {2, 500000}, /* 1000: 400 ms */ + {1, 666666}, /* 1001: 600 ms */ + {1, 250000}, /* 1010: 800 ms */ + {1, 0}, /* 1011: 1 s */ + {0, 500000}, /* 1100: 2 s */ + {8, 333333}, /* 1101: 3 s */ + {0, 250000}, /* 1110: 4 s */ + {0, 200000}, /* 1111: 5 s */ +}; +static const unsigned int sx932x_scan_period_table[] = { + 2, 15, 30, 45, 60, 90, 120, 200, 400, 800, 1000, 2000, 3000, 4000, 5000, +}; + +static IIO_CONST_ATTR_SAMP_FREQ_AVAIL( + "500.0 66.666666 33.333333 22.222222 16.666666 " + "11.111111 8.333333 5.0 2.500000 1.666666 1.250000 " + "1.0 0.500000 8.333333 0.250000 0.200000"); + + +static const struct regmap_range sx932x_writable_reg_ranges[] = { + regmap_reg_range(SX932X_REG_IRQ_MSK, SX932X_REG_IRQ_CFG2), + regmap_reg_range(SX932X_REG_GNRL_CTRL0, SX932X_REG_GNRL_CTRL1), + /* Leave i2c and clock spreading as unavailable */ + regmap_reg_range(SX932X_REG_AFE_CTRL0, SX932X_REG_AFE_CTRL9), + regmap_reg_range(SX932X_REG_PROX_CTRL0, SX932X_REG_PROX_CTRL7), + regmap_reg_range(SX932X_REG_ADV_CTRL0, SX932X_REG_ADV_CTRL20), + regmap_reg_range(SX932X_REG_PHASE_SEL, SX932X_REG_PHASE_SEL), + regmap_reg_range(SX932X_REG_OFFSET_MSB, SX932X_REG_OFFSET_LSB), + regmap_reg_range(SX932X_REG_RESET, SX932X_REG_RESET), +}; + +static const struct regmap_access_table sx932x_writeable_regs = { + .yes_ranges = sx932x_writable_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(sx932x_writable_reg_ranges), +}; + +/* + * All allocated registers are readable, so we just list unallocated + * ones. + */ +static const struct regmap_range sx932x_non_readable_reg_ranges[] = { + regmap_reg_range(SX932X_REG_IRQ_CFG2 + 1, SX932X_REG_GNRL_CTRL0 - 1), + regmap_reg_range(SX932X_REG_GNRL_CTRL1 + 1, SX932X_REG_AFE_CTRL0 - 1), + regmap_reg_range(SX932X_REG_AFE_CTRL9 + 1, SX932X_REG_PROX_CTRL0 - 1), + regmap_reg_range(SX932X_REG_PROX_CTRL7 + 1, SX932X_REG_ADV_CTRL0 - 1), + regmap_reg_range(SX932X_REG_ADV_CTRL20 + 1, SX932X_REG_PHASE_SEL - 1), + regmap_reg_range(SX932X_REG_SAR_LSB + 1, SX932X_REG_RESET - 1), + regmap_reg_range(SX932X_REG_RESET + 1, SX932X_REG_WHOAMI - 1), + regmap_reg_range(SX932X_REG_WHOAMI + 1, SX932X_REG_REVISION - 1), +}; + +static const struct regmap_access_table sx932x_readable_regs = { + .no_ranges = sx932x_non_readable_reg_ranges, + .n_no_ranges = ARRAY_SIZE(sx932x_non_readable_reg_ranges), +}; + +static const struct regmap_range sx932x_volatile_reg_ranges[] = { + regmap_reg_range(SX932X_REG_IRQ_SRC, SX932X_REG_STAT3), + regmap_reg_range(SX932X_REG_USE_MSB, SX932X_REG_DIFF_LSB), + regmap_reg_range(SX932X_REG_SAR_MSB, SX932X_REG_SAR_LSB), + regmap_reg_range(SX932X_REG_WHOAMI, SX932X_REG_WHOAMI), + regmap_reg_range(SX932X_REG_REVISION, SX932X_REG_REVISION), +}; + +static const struct regmap_access_table sx932x_volatile_regs = { + .yes_ranges = sx932x_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(sx932x_volatile_reg_ranges), +}; + +static const struct regmap_config sx932x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = SX932X_REG_REVISION, + .cache_type = REGCACHE_RBTREE, + + .wr_table = &sx932x_writeable_regs, + .rd_table = &sx932x_readable_regs, + .volatile_table = &sx932x_volatile_regs, +}; + +static int sx932x_inc_users(struct sx932x_data *data, int *counter, + unsigned int reg, unsigned int bitmask) +{ + ++(*counter); + if (*counter != 1) + /* Bit is already active, nothing to do. */ + return 0; + + return regmap_update_bits(data->regmap, reg, bitmask, bitmask); +} + +static int sx932x_dec_users(struct sx932x_data *data, int *counter, + unsigned int reg, unsigned int bitmask) +{ + --(*counter); + if (*counter != 0) + /* There are more users, do not deactivate. */ + return 0; + + return regmap_update_bits(data->regmap, reg, bitmask, 0); +} + +static int sx932x_inc_chan_users(struct sx932x_data *data, int chan) +{ + return sx932x_inc_users(data, &data->channel_users[chan], + SX932X_REG_PROX_CTRL0, BIT(chan)); +} + +static int sx932x_dec_chan_users(struct sx932x_data *data, int chan) +{ + return sx932x_dec_users(data, &data->channel_users[chan], + SX932X_REG_PROX_CTRL0, BIT(chan)); +} + +static int sx932x_inc_data_rdy_users(struct sx932x_data *data) +{ + return sx932x_inc_users(data, &data->data_rdy_users, + SX932X_REG_IRQ_MSK, SX932X_CONVDONE_IRQ); +} + +static int sx932x_dec_data_rdy_users(struct sx932x_data *data) +{ + return sx932x_dec_users(data, &data->data_rdy_users, + SX932X_REG_IRQ_MSK, SX932X_CONVDONE_IRQ); +} + +static int sx932x_inc_close_far_users(struct sx932x_data *data) +{ + return sx932x_inc_users(data, &data->close_far_users, + SX932X_REG_IRQ_MSK, + SX932X_CLOSE_IRQ | SX932X_FAR_IRQ); +} + +static int sx932x_dec_close_far_users(struct sx932x_data *data) +{ + return sx932x_dec_users(data, &data->close_far_users, + SX932X_REG_IRQ_MSK, + SX932X_CLOSE_IRQ | SX932X_FAR_IRQ); +} + +static int sx932x_read_prox_data(struct sx932x_data *data, + const struct iio_chan_spec *chan, + int *val) +{ + int ret; + __be16 regval; + const int regid = chan->channel & 3; + + ret = regmap_write(data->regmap, SX932X_REG_PHASE_SEL, regid); + if (ret < 0) + return ret; + + ret = regmap_bulk_read(data->regmap, chan->address, ®val, 2); + if (ret < 0) + return ret; + + *val = sign_extend32(be16_to_cpu(regval), 15); + + return 0; +} + +static int sx932x_read_stat_data(struct sx932x_data *data, + const struct iio_chan_spec + *chan, + int *val) +{ + int ret; + + ret = regmap_read(data->regmap, chan->address, val); + if (ret < 0) + return ret; + + return 0; +} + +/* + * If we have no interrupt support, we have to wait for a scan period + * after enabling a channel to get a result. + */ +static int sx932x_wait_for_sample(struct sx932x_data *data) +{ + int ret; + unsigned int val; + + ret = regmap_read(data->regmap, SX932X_REG_PROX_CTRL0, &val); + if (ret < 0) + return ret; + + val = (val & SX932X_SCAN_PERIOD_MASK) >> SX932X_SCAN_PERIOD_SHIFT; + + msleep(sx932x_scan_period_table[val]); + + return 0; +} + +static int sx932x_read_stat(struct sx932x_data *data, + const struct iio_chan_spec *chan, + int *val) +{ + int ret; + + mutex_lock(&data->mutex); + + ret = sx932x_inc_chan_users(data, chan->channel); + if (ret < 0) + goto out; + + ret = sx932x_inc_data_rdy_users(data); + if (ret < 0) + goto out_dec_chan; + + mutex_unlock(&data->mutex); + + if (data->client->irq > 0) + ret = wait_for_completion_interruptible(&data->completion); + else + ret = sx932x_wait_for_sample(data); + + mutex_lock(&data->mutex); + + if (ret < 0) + goto out_dec_data_rdy; + + ret = sx932x_read_stat_data(data, chan, val); + if (ret < 0) + goto out_dec_data_rdy; + + ret = sx932x_dec_data_rdy_users(data); + if (ret < 0) + goto out_dec_chan; + + ret = sx932x_dec_chan_users(data, chan->channel); + if (ret < 0) + goto out; + + ret = IIO_VAL_INT; + + goto out; + +out_dec_data_rdy: + sx932x_dec_data_rdy_users(data); +out_dec_chan: + sx932x_dec_chan_users(data, chan->channel); +out: + mutex_unlock(&data->mutex); + reinit_completion(&data->completion); + + return ret; +} + +static int sx932x_read_proximity(struct sx932x_data *data, + const struct iio_chan_spec *chan, + int *val) +{ + int ret; + + mutex_lock(&data->mutex); + + ret = sx932x_inc_chan_users(data, chan->channel); + if (ret < 0) + goto out; + + ret = sx932x_inc_data_rdy_users(data); + if (ret < 0) + goto out_dec_chan; + + mutex_unlock(&data->mutex); + + if (data->client->irq > 0) + ret = wait_for_completion_interruptible(&data->completion); + else + ret = sx932x_wait_for_sample(data); + + mutex_lock(&data->mutex); + + if (ret < 0) + goto out_dec_data_rdy; + + ret = sx932x_read_prox_data(data, chan, val); + if (ret < 0) + goto out_dec_data_rdy; + + ret = sx932x_dec_data_rdy_users(data); + if (ret < 0) + goto out_dec_chan; + + ret = sx932x_dec_chan_users(data, chan->channel); + if (ret < 0) + goto out; + + ret = IIO_VAL_INT; + + goto out; + +out_dec_data_rdy: + sx932x_dec_data_rdy_users(data); +out_dec_chan: + sx932x_dec_chan_users(data, chan->channel); +out: + mutex_unlock(&data->mutex); + reinit_completion(&data->completion); + + return ret; +} + +static int sx932x_read_samp_freq(struct sx932x_data *data, + int *val, int *val2) +{ + int ret; + unsigned int regval; + + mutex_lock(&data->mutex); + ret = regmap_read(data->regmap, SX932X_REG_PROX_CTRL0, ®val); + mutex_unlock(&data->mutex); + + if (ret < 0) + return ret; + + regval = (regval & SX932X_SCAN_PERIOD_MASK) >> SX932X_SCAN_PERIOD_SHIFT; + *val = sx932x_samp_freq_table[regval].val; + *val2 = sx932x_samp_freq_table[regval].val2; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int sx932x_read_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int *val, int *val2, long mask) +{ + struct sx932x_data *data = iio_priv(indio_dev); + int ret; + + switch (chan->type) { + case IIO_PROXIMITY: + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&indio_dev->mlock); + + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + ret = sx932x_read_proximity(data, chan, val); + mutex_unlock(&indio_dev->mlock); + return ret; + case IIO_CHAN_INFO_PROCESSED: + mutex_lock(&indio_dev->mlock); + + if (iio_buffer_enabled(indio_dev)) { + mutex_unlock(&indio_dev->mlock); + return -EBUSY; + } + ret = sx932x_read_stat(data, chan, val); + mutex_unlock(&indio_dev->mlock); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return sx932x_read_samp_freq(data, val, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int sx932x_set_samp_freq(struct sx932x_data *data, + int val, int val2) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(sx932x_samp_freq_table); i++) + if (val == sx932x_samp_freq_table[i].val && + val2 == sx932x_samp_freq_table[i].val2) + break; + + if (i == ARRAY_SIZE(sx932x_samp_freq_table)) + return -EINVAL; + + mutex_lock(&data->mutex); + + ret = regmap_update_bits(data->regmap, SX932X_REG_PROX_CTRL0, + SX932X_SCAN_PERIOD_MASK, + i << SX932X_SCAN_PERIOD_SHIFT); + + mutex_unlock(&data->mutex); + + return ret; +} + +static int sx932x_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct sx932x_data *data = iio_priv(indio_dev); + + switch (chan->type) { + case IIO_PROXIMITY: + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return sx932x_set_samp_freq(data, val, val2); + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static irqreturn_t sx932x_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct sx932x_data *data = iio_priv(indio_dev); + + if (data->trigger_enabled) + iio_trigger_poll(data->trig); + + /* + * Even if no event is enabled, we need to wake the thread to + * clear the interrupt state by reading SX932X_REG_IRQ_SRC. It + * is not possible to do that here because regmap_read takes a + * mutex. + */ + return IRQ_WAKE_THREAD; +} + +static void sx932x_push_events(struct iio_dev *indio_dev) +{ + int ret; + unsigned int val, chan; + struct sx932x_data *data = iio_priv(indio_dev); + + ret = regmap_read(data->regmap, SX932X_REG_STAT0, &val); + if (ret < 0) { + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + return; + } + + for (chan = 0; chan < SX932X_NUM_CHANNELS; chan++) { + int dir; + u64 ev; + bool new_prox = val & BIT(chan); + + if (!data->event_enabled[chan]) + continue; + if (new_prox == data->prox_stat[chan]) + /* No change on this channel. */ + continue; + + dir = new_prox ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING; + ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, chan, + IIO_EV_TYPE_THRESH, dir); + iio_push_event(indio_dev, ev, iio_get_time_ns(indio_dev)); + data->prox_stat[chan] = new_prox; + } +} + +static irqreturn_t sx932x_irq_thread_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct sx932x_data *data = iio_priv(indio_dev); + int ret; + unsigned int val; + + mutex_lock(&data->mutex); + + ret = regmap_read(data->regmap, SX932X_REG_IRQ_SRC, &val); + if (ret < 0) { + dev_err(&data->client->dev, "i2c transfer error in irq\n"); + goto out; + } + + if (val & (SX932X_CLOSE_IRQ | SX932X_FAR_IRQ)) + sx932x_push_events(indio_dev); + + if (val & SX932X_CONVDONE_IRQ) + complete(&data->completion); + +out: + mutex_unlock(&data->mutex); + + return IRQ_HANDLED; +} + +static int sx932x_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct sx932x_data *data = iio_priv(indio_dev); + + if (chan->type != IIO_PROXIMITY || type != IIO_EV_TYPE_THRESH || + dir != IIO_EV_DIR_EITHER) + return -EINVAL; + + return data->event_enabled[chan->channel & SX932X_CHAN_MASK]; +} + +static int sx932x_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + int state) +{ + struct sx932x_data *data = iio_priv(indio_dev); + int ret; + + if (chan->type != IIO_PROXIMITY || type != IIO_EV_TYPE_THRESH || + dir != IIO_EV_DIR_EITHER) + return -EINVAL; + + mutex_lock(&data->mutex); + + if (state == 1) { + ret = sx932x_inc_chan_users(data, chan->channel); + if (ret < 0) + goto out_unlock; + ret = sx932x_inc_close_far_users(data); + if (ret < 0) + goto out_undo_chan; + } else { + ret = sx932x_dec_chan_users(data, chan->channel); + if (ret < 0) + goto out_unlock; + ret = sx932x_dec_close_far_users(data); + if (ret < 0) + goto out_undo_chan; + } + + data->event_enabled[chan->channel & SX932X_CHAN_MASK] = state; + goto out_unlock; + +out_undo_chan: + if (state == 1) + sx932x_dec_chan_users(data, chan->channel); + else + sx932x_inc_chan_users(data, chan->channel); +out_unlock: + mutex_unlock(&data->mutex); + return ret; +} + +static int sx932x_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct sx932x_data *data = iio_priv(indio_dev); + + mutex_lock(&data->mutex); + kfree(data->buffer); + data->buffer = kzalloc(indio_dev->scan_bytes, GFP_KERNEL); + mutex_unlock(&data->mutex); + + if (data->buffer == NULL) + return -ENOMEM; + + return 0; +} + +static ssize_t sx932x_uid_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); +#ifdef CONFIG_ACPI + struct acpi_device *adev = ACPI_COMPANION(dev); + + if (adev != NULL) + return scnprintf(buf, PAGE_SIZE, "%s\n", acpi_device_uid(adev)); +#endif + return scnprintf(buf, PAGE_SIZE, "%d\n", indio_dev->id); +} + +static IIO_DEVICE_ATTR(uid, 0444, sx932x_uid_show, NULL, 0); + +struct sx932x_ctrl_attribute { + struct device_attribute dev_attr; + u8 ctrl; +}; + +static inline struct sx932x_ctrl_attribute *sx932x_to_ctrl_attr( + struct device_attribute *attr) +{ + return container_of(attr, struct sx932x_ctrl_attribute, dev_attr); +} + +static ssize_t sx932x_show_ctrl_attr(struct device *dev, + struct device_attribute *dev_attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sx932x_ctrl_attribute *attr = sx932x_to_ctrl_attr(dev_attr); + struct sx932x_data *data = iio_priv(indio_dev); + unsigned int val; + int ret; + + mutex_lock(&data->mutex); + ret = regmap_read(data->regmap, attr->ctrl, &val); + mutex_unlock(&data->mutex); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%02x\n", val); +} + +static ssize_t sx932x_store_ctrl_attr(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct sx932x_ctrl_attribute *attr = sx932x_to_ctrl_attr(dev_attr); + struct sx932x_data *data = iio_priv(indio_dev); + u8 val; + int ret; + + if (kstrtou8(buf, 0, &val)) + return -EINVAL; + + mutex_lock(&data->mutex); + ret = regmap_write(data->regmap, attr->ctrl, val); + mutex_unlock(&data->mutex); + if (ret) + return ret; + + return len; +} + +#define SX932X_CTRL_ATTR_FILL(_name, _mode, _show, _store, _ctrl, _offset) \ + { .dev_attr = __ATTR(_name, _mode, _show, _store), \ + .ctrl = _ctrl + _offset } + +#define SX932X_CTRL_ATTR(_name, _mode, _show, _store, _ctrl, _offset) \ + struct sx932x_ctrl_attribute sx932x_ctrl_attr_##_name = \ + SX932X_CTRL_ATTR_FILL( \ + _name, _mode, _show, _store, _ctrl, _offset) + +#define SX932X_ADV_CTRL_RW(_ctrl) \ + SX932X_CTRL_ATTR(adv_ctrl##_ctrl##_raw, 0644, \ + sx932x_show_ctrl_attr, sx932x_store_ctrl_attr, \ + _ctrl, SX932X_REG_ADV_CTRL0) + +static SX932X_ADV_CTRL_RW(0); +static SX932X_ADV_CTRL_RW(1); +static SX932X_ADV_CTRL_RW(2); +static SX932X_ADV_CTRL_RW(3); +static SX932X_ADV_CTRL_RW(4); +static SX932X_ADV_CTRL_RW(5); +static SX932X_ADV_CTRL_RW(6); +static SX932X_ADV_CTRL_RW(7); +static SX932X_ADV_CTRL_RW(8); +static SX932X_ADV_CTRL_RW(9); +static SX932X_ADV_CTRL_RW(10); +static SX932X_ADV_CTRL_RW(11); +static SX932X_ADV_CTRL_RW(12); +static SX932X_ADV_CTRL_RW(13); +static SX932X_ADV_CTRL_RW(14); +static SX932X_ADV_CTRL_RW(15); +static SX932X_ADV_CTRL_RW(16); +static SX932X_ADV_CTRL_RW(17); +static SX932X_ADV_CTRL_RW(18); +static SX932X_ADV_CTRL_RW(19); +static SX932X_ADV_CTRL_RW(20); + +static struct attribute *sx932x_attributes[] = { + &iio_const_attr_sampling_frequency_available.dev_attr.attr, + &iio_dev_attr_uid.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl0_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl1_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl2_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl3_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl4_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl5_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl6_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl7_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl8_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl9_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl10_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl11_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl12_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl13_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl14_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl15_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl16_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl17_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl18_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl19_raw.dev_attr.attr, + &sx932x_ctrl_attr_adv_ctrl20_raw.dev_attr.attr, + NULL, +}; + +static const struct attribute_group sx932x_attribute_group = { + .attrs = sx932x_attributes, +}; + +static const struct iio_info sx932x_info = { + .attrs = &sx932x_attribute_group, + .read_raw = &sx932x_read_raw, + .write_raw = &sx932x_write_raw, + .read_event_config = &sx932x_read_event_config, + .write_event_config = &sx932x_write_event_config, + .update_scan_mode = &sx932x_update_scan_mode, +}; + +static int sx932x_set_trigger_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct sx932x_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + + if (state) + ret = sx932x_inc_data_rdy_users(data); + else + ret = sx932x_dec_data_rdy_users(data); + if (ret < 0) + goto out; + + data->trigger_enabled = state; + +out: + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_trigger_ops sx932x_trigger_ops = { + .set_trigger_state = sx932x_set_trigger_state, +}; + +static irqreturn_t sx932x_trigger_handler(int irq, void *private) +{ + struct iio_poll_func *pf = private; + struct iio_dev *indio_dev = pf->indio_dev; + struct sx932x_data *data = iio_priv(indio_dev); + int val, bit, ret, i = 0; + + mutex_lock(&data->mutex); + + for_each_set_bit(bit, indio_dev->active_scan_mask, + indio_dev->masklength) { + ret = sx932x_read_prox_data(data, &indio_dev->channels[bit], + &val); + if (ret < 0) + goto out; + + data->buffer[i++] = val; + } + + iio_push_to_buffers_with_timestamp(indio_dev, data->buffer, + iio_get_time_ns(indio_dev)); +out: + mutex_unlock(&data->mutex); + + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int sx932x_buffer_preenable(struct iio_dev *indio_dev) +{ + struct sx932x_data *data = iio_priv(indio_dev); + int ret = 0, i; + + mutex_lock(&data->mutex); + + for (i = 0; i < SX932X_NUM_CHANNELS; i++) + if (test_bit(i, indio_dev->active_scan_mask)) { + ret = sx932x_inc_chan_users(data, i); + if (ret) + break; + } + + if (ret) + for (i = i - 1; i >= 0; i--) + if (test_bit(i, indio_dev->active_scan_mask)) + sx932x_dec_chan_users(data, i); + + mutex_unlock(&data->mutex); + + return ret; +} + +static int sx932x_buffer_predisable(struct iio_dev *indio_dev) +{ + struct sx932x_data *data = iio_priv(indio_dev); + int ret = 0, i; + + iio_triggered_buffer_predisable(indio_dev); + + mutex_lock(&data->mutex); + + for (i = 0; i < SX932X_NUM_CHANNELS; i++) + if (test_bit(i, indio_dev->active_scan_mask)) { + ret = sx932x_dec_chan_users(data, i); + if (ret) + break; + } + + if (ret) + for (i = i - 1; i >= 0; i--) + if (test_bit(i, indio_dev->active_scan_mask)) + sx932x_inc_chan_users(data, i); + + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct iio_buffer_setup_ops sx932x_buffer_setup_ops = { + .preenable = sx932x_buffer_preenable, + .postenable = iio_triggered_buffer_postenable, + .predisable = sx932x_buffer_predisable, +}; +#define SX932X_REG_CONFIG(_property_prefix, _property_name, _reg_addr, \ + _def_value) \ + { \ + .property = _property_prefix ",reg_" _property_name, \ + .reg_addr = SX932X_REG_##_reg_addr, .def_value = _def_value, \ + } + +#define SX932X_DEFAULT_REG_CONFIGS(_property_prefix) \ + { \ + { \ + .property = NULL, \ + .reg_addr = SX932X_REG_ADV_CTRL11, \ + .def_value = (5 << 4) | 2, \ + }, \ + { \ + .property = NULL, \ + .reg_addr = SX932X_REG_ADV_CTRL12, \ + .def_value = (11 << 4) | 5, \ + }, \ + SX932X_REG_CONFIG(_property_prefix, "adv_ctrl10", \ + ADV_CTRL10, (5 << 4) | 12), \ + SX932X_REG_CONFIG(_property_prefix, "afe_ctrl4", \ + AFE_CTRL4, 7), \ + SX932X_REG_CONFIG(_property_prefix, "afe_ctrl7", \ + AFE_CTRL7, 7), \ + SX932X_REG_CONFIG(_property_prefix, "afe_ctrl9", \ + AFE_CTRL9, 15), \ + SX932X_REG_CONFIG(_property_prefix, "prox_ctrl0", \ + PROX_CTRL0, (2 << 3) | 2), \ + SX932X_REG_CONFIG(_property_prefix, "prox_ctrl1", \ + PROX_CTRL1, (2 << 3) | 2), \ + SX932X_REG_CONFIG(_property_prefix, "prox_ctrl2", \ + PROX_CTRL2, 0x80 | 16), \ + SX932X_REG_CONFIG(_property_prefix, "prox_ctrl4", \ + PROX_CTRL4, (1 << 3) | 4), \ + SX932X_REG_CONFIG(_property_prefix, "prox_ctrl5", \ + PROX_CTRL5, (1 << 4) | 2), \ + SX932X_REG_CONFIG(_property_prefix, "prox_ctrl6", \ + PROX_CTRL6, 60), \ + SX932X_REG_CONFIG(_property_prefix, "prox_ctrl7", \ + PROX_CTRL7, 88), \ + SX932X_REG_CONFIG(_property_prefix, "adv_ctrl16", \ + ADV_CTRL16, (3 << 4) | (2 << 2)), \ + SX932X_REG_CONFIG(_property_prefix, "adv_ctrl17", \ + ADV_CTRL17, (5 << 4) | 6), \ + SX932X_REG_CONFIG(_property_prefix, "adv_ctrl18", \ + ADV_CTRL18, (3 << 4) | 3), \ + SX932X_REG_CONFIG(_property_prefix, "gnrl_ctrl1", \ + GNRL_CTRL1, 0x2f), \ + { \ + .property = NULL, \ + .reg_addr = 0, \ + .def_value = 0, \ + }, \ + } + +#ifdef CONFIG_ACPI +static const struct sx932x_reg_config sx9320_reg_config[] = + SX932X_DEFAULT_REG_CONFIGS(SX9320_ACPI_NAME); +static const struct sx932x_reg_config sx9321_reg_config[] = + SX932X_DEFAULT_REG_CONFIGS(SX9321_ACPI_NAME); +static const struct sx932x_reg_config sx9324_reg_config[] = + SX932X_DEFAULT_REG_CONFIGS(SX9324_ACPI_NAME); +#else +static const struct sx932x_reg_config sx9320_reg_config[] = + SX932X_DEFAULT_REG_CONFIGS("semtech,"); +static const struct sx932x_reg_config sx9321_reg_config[] = + SX932X_DEFAULT_REG_CONFIGS("semtech,"); +static const struct sx932x_reg_config sx9324_reg_config[] = + SX932X_DEFAULT_REG_CONFIGS("semtech,"); +#endif +static const struct sx932x_reg_config *sx932x_reg_configs[] = { + sx9320_reg_config, sx9321_reg_config, sx9324_reg_config +}; +static int sx932x_read_register_property(struct device *dev, + const struct sx932x_reg_config *cfg, + u8 *value) +{ +#ifdef CONFIG_ACPI + const union acpi_object *acpi_value = NULL; +#endif + int ret; + + if ((dev == NULL) || (cfg->property == NULL)) { + *value = cfg->def_value; + return 0; + } + +#ifdef CONFIG_ACPI + ret = acpi_dev_get_property(ACPI_COMPANION(dev), cfg->property, + ACPI_TYPE_INTEGER, &acpi_value); +#else + ret = of_property_read_u8(dev->of_node, cfg->property, value); +#endif + if (ret) + *value = cfg->def_value; +#ifdef CONFIG_ACPI + *value = acpi_value ? (u8)acpi_value->integer.value : cfg->def_value; +#endif + return 0; +} + +static int sx932x_load_config(struct device *dev, struct sx932x_data *data) +{ + u8 val; + int i, ret; + const struct sx932x_reg_config *cfg; + + for (i = 0; data->reg_config[i].reg_addr != 0; i++) { + cfg = &data->reg_config[i]; + ret = sx932x_read_register_property(dev, cfg, &val); + if (ret < 0) + return ret; + ret = regmap_write(data->regmap, cfg->reg_addr, val); + if (ret < 0) + return ret; + } + return 0; +} + +/* Activate all channels and perform an initial compensation. */ +static int sx932x_init_compensation(struct iio_dev *indio_dev) +{ + struct sx932x_data *data = iio_priv(indio_dev); + int i, ret; + bool success = false; + unsigned int val; + + for (i = 100; i >= 0; i--) { + usleep_range(10000, 20000); + ret = regmap_read(data->regmap, SX932X_REG_STAT2, &val); + if (ret < 0) + goto out; + if (!(val & SX932X_COMPSTAT_MASK)) { + success = true; + break; + } + } + + if (success) { + dev_info(&data->client->dev, + "initial compensation success"); + } else { + dev_err(&data->client->dev, + "initial compensation timed out: 0x%02x", val); + ret = -ETIMEDOUT; + } + +out: + return ret; +} + +static int sx932x_init_device(struct iio_dev *indio_dev) +{ + struct device *dev = &indio_dev->dev; + struct sx932x_data *data = iio_priv(indio_dev); + int ret; + unsigned int val; + + ret = regmap_read(data->regmap, SX932X_REG_WHOAMI, &val); + if (ret < 0 || (val != SX9320_WHOAMI && val != SX9321_WHOAMI && + val != SX9324_WHOAMI)) { + dev_err(&data->client->dev, + "Unable to identify the chip: %d - 0x%02x", ret, val); + return -ENODEV; + } + + ret = regmap_write(data->regmap, SX932X_REG_IRQ_MSK, 0); + if (ret < 0) + return ret; + + ret = regmap_write(data->regmap, SX932X_REG_RESET, + SX932X_SOFT_RESET); + if (ret < 0) + return ret; + + // wait for the reset to be completed + usleep_range(10000, 20000); + + ret = regmap_write(data->regmap, SX932X_REG_RESET, 0); + if (ret < 0) + return ret; + + ret = regmap_read(data->regmap, SX932X_REG_IRQ_SRC, &val); + if (ret < 0) + return ret; + + ret = sx932x_load_config(dev, data); + if (ret < 0) + return ret; + + return sx932x_init_compensation(indio_dev); +} + +static int sx932x_probe(struct i2c_client *client, + const struct i2c_device_id *i2c_id) +{ + int ret; + struct iio_dev *indio_dev; + const struct acpi_device_id *acpi_id; + struct sx932x_data *data; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data)); + if (indio_dev == NULL) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + if (i2c_id) { + data->reg_config = sx932x_reg_configs[i2c_id->driver_data]; + } else if (ACPI_HANDLE(&client->dev)) { + acpi_id = acpi_match_device( + client->dev.driver->acpi_match_table, + &client->dev); + if (!acpi_id) { + dev_err(&client->dev, "No driver data\n"); + return -EINVAL; + } + data->reg_config = sx932x_reg_configs[acpi_id->driver_data]; + } else { + return -ENODEV; + } + mutex_init(&data->mutex); + init_completion(&data->completion); + data->trigger_enabled = false; + + data->regmap = devm_regmap_init_i2c(client, &sx932x_regmap_config); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + ACPI_COMPANION_SET(&indio_dev->dev, ACPI_COMPANION(&client->dev)); + indio_dev->dev.parent = &client->dev; + indio_dev->name = SX932X_DRIVER_NAME; + indio_dev->channels = sx932x_channels; + indio_dev->num_channels = ARRAY_SIZE(sx932x_channels); + indio_dev->info = &sx932x_info; + indio_dev->modes = INDIO_DIRECT_MODE; + i2c_set_clientdata(client, indio_dev); + + ret = sx932x_init_device(indio_dev); + if (ret < 0) { + dev_err(&client->dev, "tried to init sx932x - error %d\n", + ret); + return ret; + } + + if (client->irq <= 0) + dev_warn(&client->dev, "no valid irq found\n"); + else { + ret = devm_request_threaded_irq(&client->dev, client->irq, + sx932x_irq_handler, sx932x_irq_thread_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + SX932X_IRQ_NAME, indio_dev); + if (ret < 0) + return ret; + + data->trig = devm_iio_trigger_alloc(&client->dev, + "%s-dev%d", indio_dev->name, indio_dev->id); + if (!data->trig) + return -ENOMEM; + + data->trig->dev.parent = &client->dev; + data->trig->ops = &sx932x_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = iio_trigger_register(data->trig); + if (ret) + return ret; + } + + ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev, + NULL, sx932x_trigger_handler, + &sx932x_buffer_setup_ops); + if (ret < 0) + goto out_trigger_unregister; + + ret = devm_iio_device_register(&client->dev, indio_dev); + if (ret < 0) + goto out_trigger_unregister; + + return 0; + +out_trigger_unregister: + if (client->irq > 0) + iio_trigger_unregister(data->trig); + + return ret; +} + +static int sx932x_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct sx932x_data *data = iio_priv(indio_dev); + + if (client->irq > 0) + iio_trigger_unregister(data->trig); + kfree(data->buffer); + return 0; +} + +static int __maybe_unused sx932x_suspend(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct sx932x_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = regmap_read(data->regmap, SX932X_REG_GNRL_CTRL1, + &data->suspend_reg_gnrl_ctrl1); + + if (ret < 0) + goto out; + + // disable all phases, send the device to sleep + ret = regmap_write(data->regmap, SX932X_REG_GNRL_CTRL1, 0); + +out: + mutex_unlock(&data->mutex); + return ret; +} + +static int __maybe_unused sx932x_resume(struct device *dev) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(to_i2c_client(dev)); + struct sx932x_data *data = iio_priv(indio_dev); + int ret; + + mutex_lock(&data->mutex); + ret = regmap_write(data->regmap, SX932X_REG_GNRL_CTRL1, + data->suspend_reg_gnrl_ctrl1); + mutex_unlock(&data->mutex); + + return ret; +} + +static const struct dev_pm_ops sx932x_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sx932x_suspend, sx932x_resume) +}; +#ifdef CONFIG_ACPI +static const struct acpi_device_id sx932x_acpi_match[] = { + { SX9320_ACPI_NAME, 0 }, + { SX9321_ACPI_NAME, 1 }, + { SX9324_ACPI_NAME, 2 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, sx932x_acpi_match); +#endif +#ifdef CONFIG_OF +static const struct of_device_id sx932x_of_match[] = { + { .compatible = "semtech,sx9320" }, + { .compatible = "semtech,sx9321" }, + { .compatible = "semtech,sx9324" }, + {} +}; +MODULE_DEVICE_TABLE(of, sx932x_of_match); +#endif +static const struct i2c_device_id sx932x_id[] = { + { "sx9320", 0 }, + { "sx9321", 1 }, + { "sx9324", 2 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, sx932x_id); + +static struct i2c_driver sx932x_driver = { + .driver = { + .name = SX932X_DRIVER_NAME, + .acpi_match_table = ACPI_PTR(sx932x_acpi_match), + .of_match_table = of_match_ptr(sx932x_of_match), + .pm = &sx932x_pm_ops, + }, + .probe = sx932x_probe, + .remove = sx932x_remove, + .id_table = sx932x_id, +}; +module_i2c_driver(sx932x_driver); + +MODULE_AUTHOR("Enrico Granata "); +MODULE_AUTHOR("Gwendal Grignou "); +MODULE_AUTHOR("Pat Plunkett