From nobody Sat Feb 7 11:05:10 2026 Received: from mail-wm1-f68.google.com (mail-wm1-f68.google.com [209.85.128.68]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2C4EC274B3A for ; Sun, 1 Feb 2026 17:08:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.68 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769965688; cv=none; b=P533ropK6N5kmyoWYN4jhHzPhoJGsLMqrcNYJNVel2zkucphqviHB/ICtFmD5zvshEq+Tc1ihdNicGs6meGOdv2GyluI1+Jj7/D3nZqHfUeTQK8xMtZnLmcf+kz+AWyuSOCwoJQF0M2og7iDFPAI2bQF00w3gdQEHE8ofRkO2nQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769965688; c=relaxed/simple; bh=oZVM0jsRUZFOAD5WZ3E63gXjNmhECNXFhQvLake1fM8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=qimfApSAgZET3j2Y+S8gulKkDimTzUIV0dUpi0Y6ty/2ZxDe4eKlFmoCT9j0soRcOs8Qz6tSMnhwTp11JHNyfJcnTYstKTMpgUi3IFmmTzaAZWVWj/lHVku+JO+fs2Wqge1ay0/Oywt01sTLOciIOB9NkMsi8b1tVS+/pJVn970= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=W62HedvX; arc=none smtp.client-ip=209.85.128.68 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="W62HedvX" Received: by mail-wm1-f68.google.com with SMTP id 5b1f17b1804b1-48069a48629so38265935e9.0 for ; Sun, 01 Feb 2026 09:08:07 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769965685; x=1770570485; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=aiP44f+/iNWWPqnIpAiWlM8Sc6MEYUGh+76c97VATbg=; b=W62HedvXZHq5oo2iN1BxGFnFHcGm9JNQJVgneXQhHTy6XE/G5HFHXZo4mhi0brhSo9 N2WS0xx3e8+S3injvEHcDYHMMmZ4hpO7nr3KseH7BTUFgIZ6ON0/YSRzffX+EHDu+39H vVNAStUFGPiVRmfNcDFK7sOyYnN9DT/aPPUzNN94YyOpGFQkrSEk6kkPyexqQnmV0zPP SV/eMPUHJMvDFomQqkWcwDEE1zIKg+Qm7kmebjLJmq7RAnIWkQ9ilH313pKim6GtVe+x tvXcnkW8UxhZdaY3AGdg7UIGkzaVg+GwcmjyT9Poe05XoQITb/6MCQsO5SrI2fZKbt3f 1+OQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769965685; x=1770570485; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=aiP44f+/iNWWPqnIpAiWlM8Sc6MEYUGh+76c97VATbg=; b=dGZaCNQ7z/gJkr4fxvdDgM9vBCfw5ZPZmqoAJJYXq+o2dWOeoENNtclily1SXHOdMj yzA6Q9/+VezTwmUzz/ygyqZApOWTmX0y4dDKDl7hJhBaurcNguk4njhP4YXjC77V9WXW j2vvBCe7RHz1kv8Mi9hj9P3EVl5qXtSsUnzEKEJ56k4N2r7AkILLsv5cByaF5c/Qv2+F XHPU3LsI7iLtvGJ2sIcVkvo2YnzL8N4qYPFqkPlfAPxJ0mhjOOZ3XIW/DB/DHc7VHEG9 00iMvm5jWgZvdiiSTTBJK58REjZNw1Wx0/5yeKxIFKCMvOcHgCj7ZMTLYokZ0bhbXJGb GhUA== X-Forwarded-Encrypted: i=1; AJvYcCXGer3JqvdbcAQ/UuW9IHrgt7k6isZ2NvVu1yypfwwFyHZY8tfYgWFbByHAxD7FW8B0n9jNZ0XTqQiGV6w=@vger.kernel.org X-Gm-Message-State: AOJu0Ywo+LAkpTPlGT3kflQEcuIRO1wAqwI2ELZElVOgX4GsIIO+lp3C P/3KwhQuVmBsNPw0euWpspQ3H4GKtiWieqVIX1MnWPRjw5It2cGYCm39 X-Gm-Gg: AZuq6aK+e/hhOWOkAAas9xIPDfLyOUgWRswMuvbSYvyrUSQFVRorm+S2ynry/+YP1ix gD27VDOWhDKAMX0zDYMwadH2/YiI9OoQSh7xPfAd9tE6Eng0PLyt7tpRgliTu+oo0DzGIvCiFBN o91y+8+1zVU9sghmZ5QiGYFOHlnjJJgVnzP1Cs2VrURLrfcYmvbujCRfheHrfekmceJmR4n8ysq gw+lDADcRNZ57HlfGu3JPrTplrzVMmkIqpBCUxEYgr7IeVkPZb9Wn9IY9xYpQHUlMhYmL+1zgFl 8DP+xD/rz9O9+Rjxs6fqZnnTqLsENbKBzB3LdKHOmNVkaGB3qM4FgawZS6Gar6Bzo8Rw2sDZtjN YwHAmgUqj43MIUj5AHnxL9tk9ZaWwbobl6T4tS8Jkf82jlgq0Z4QX0oLPfX/CikJbG4HiE5uGRo ngcvmYXET9ze0U1g== X-Received: by 2002:adf:fa02:0:b0:436:348:9a7f with SMTP id ffacd0b85a97d-43603489b39mr2583532f8f.28.1769965685455; Sun, 01 Feb 2026 09:08:05 -0800 (PST) Received: from [192.168.8.10] ([2a00:f502:260:44d4:a26c:adcb:8da8:2]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-435e10f82aesm40615460f8f.19.2026.02.01.09.08.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 01 Feb 2026 09:08:05 -0800 (PST) From: Erikas Bitovtas Date: Sun, 01 Feb 2026 19:03:48 +0200 Subject: [PATCH 1/2] dt-bindings: Add binding document for cm36686 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260201-cm36686-v1-1-4949a2a9ba63@gmail.com> References: <20260201-cm36686-v1-0-4949a2a9ba63@gmail.com> In-Reply-To: <20260201-cm36686-v1-0-4949a2a9ba63@gmail.com> To: Jonathan Cameron , David Lechner , =?utf-8?q?Nuno_S=C3=A1?= , Andy Shevchenko , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Kevin Tsai Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, ~postmarketos/upstreaming@lists.sr.ht, phone-devel@vger.kernel.org, Erikas Bitovtas X-Mailer: b4 0.14.3 Document the Capella cm36686 ambient light and proximity sensor devicetree bindings. Signed-off-by: Erikas Bitovtas --- .../bindings/iio/light/capella,cm36686.yaml | 74 ++++++++++++++++++= ++++ 1 file changed, 74 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/light/capella,cm36686.ya= ml b/Documentation/devicetree/bindings/iio/light/capella,cm36686.yaml new file mode 100644 index 000000000000..5f0a585e3d87 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/light/capella,cm36686.yaml @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/light/capella,cm36686.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Capella cm36686 I2C Ambient Light and Proximity sensor + +maintainers: + - Erikas Bitovtas + +properties: + compatible: + enum: + - capella,cm36686 + - capella,cm36672p + + reg: + maxItems: 1 + description: + I2C slave address of the device. Must be 0x60 for both cm36686 + and cm36672p sensors. + + interrupts: + maxItems: 1 + + vdd-supply: + description: + Regulator that provides power to the sensor. + + vddio-supply: + description: + Regulator used to power IO and I2C bus. + + vled-supply: + description: + Regulator used to power proximity LED + + proximity-near-level: true + + capella,proximity-led-current: + description: + Current for proximity IR LED (in uA) + $ref: /schemas/types.yaml#/definitions/uint32 + enum: [50, 75, 100, 120, 140, 160, 180, 200] + +additionalProperties: false + +required: + - compatible + - reg + +examples: + - | + #include + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + light-sensor@60 { + compatible =3D "capella,cm36686"; + reg =3D <0x60>; + + vdd-supply =3D <&pm8916_l8>; + vddio-supply =3D <&pm8916_l6>; + vled-supply =3D <®_prox_vled>; + + interrupts-extended =3D <&tlmm 113 IRQ_TYPE_EDGE_FALLING>; + + proximity-near-level =3D <30>; + capella,proximity-led-current =3D <100>; + }; + }; --=20 2.52.0 From nobody Sat Feb 7 11:05:10 2026 Received: from mail-wr1-f47.google.com (mail-wr1-f47.google.com [209.85.221.47]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CFAB626E708 for ; Sun, 1 Feb 2026 17:08:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.47 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769965697; cv=none; b=RQCcgATS9RgBPQxhF/GrsjUrD0RWEE286buVsEwlMTxwSCWcKi32V0Q7SdC7ou8+AcRVQFHVmKLSZD0mxeea/UR5+uvEkgqxe3+ZZwlFTylmlbdl0PMh36NDdxco7PljLdffudCZMAihGnROMVhxpL6YH0tG3G/TM2rhjVeSF44= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1769965697; c=relaxed/simple; bh=NsjM3Vqmz5bxaNm0AHbcScfL5jByrlw2DczzcSwJgPc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=lGMrfKXjA2VKVQAu2AcsJHISM5Kk41YwL04LIS9fkRm3l3TkZYJbvrrRQ780ZsWex/J4nqLfwulthPy4XEPINGTzdq47NPMAHYVk4hY9trQPv0w5FL3iuCDg8Z6NuMETHUkA03V1PXUW5r1qChxM2dBvBM4oRQmtTt7LJJOgChg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=bpIY1dzj; arc=none smtp.client-ip=209.85.221.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="bpIY1dzj" Received: by mail-wr1-f47.google.com with SMTP id ffacd0b85a97d-4359108fd24so2248186f8f.2 for ; Sun, 01 Feb 2026 09:08:14 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769965693; x=1770570493; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=Y20VaDeZYMQyMcj3pIKCpi0UHIkq7N+B9ttn6x7b5+g=; b=bpIY1dzj31GNYxSeVksfqx87M3lcd9vSxAdIb4yTkF+VUwnC3pkgMAjERt+iPQZtgW mNuILfVr4+7j+8SwBETS8iwt0rFbXVfkKF/N4zUR64j6l+TtxGNw/w8PZiPkPXDR14wn o9DVF5p2KfUWiQIni3ll43Ag4EB9zeqcPoxtf1gSkjoR83d+/iVnn8hbJpHh52+OwoF4 w+TDwqTqZe58XUaxsKP2GRrnG3xcoeA4YAqjiJf/dcnEKvnxYwdcBqUTmhz1h14znFPB SGP9L/p3iJSVY8kl2qQaF7QLir3u2RTgZhzsYEjxIUnECEefpgO03YPXo8Wlr/eA/Nm8 YvzQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769965693; x=1770570493; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=Y20VaDeZYMQyMcj3pIKCpi0UHIkq7N+B9ttn6x7b5+g=; b=lZb2zTZaYGqL2vv42J/91rvmMhxINNoSKtuJUew6YqAXjy8tPZxJy+b4h2pjLFu8xc FbyzXvn0N2c3LYemHdfVM3tUbhWspFZMwfC7L7AUAdtFS17nJQsM3OUrEzhE6zWvRsPz GjjSMsd7NlpyR88N92OX+Ldz9TJmda+eg/i+DByP/I1vfaRCsqAbKfDbcD0oUtypQYgs SN7NCjFe4j5Q1Fx3c0+STDYuxFr4eSNmpVUzJtKM0xpmxMCjeTZOmbJCXLYPXiiyB9ez 5d+Eh2pxlriD2PlD4Sgjkc0Rl0Pz2BBbBPZPScM+dKDMYd4xYwjy8reTs5u6nd8zTaQU SGLQ== X-Forwarded-Encrypted: i=1; AJvYcCXGGY8q7rE5Hp0ZUpXMylUZdgpDOPh82mK3ysP1j2XEhjmoZ9R6ygV4Nu/IeqFMQVi1AnR2ydpdtYfxR5s=@vger.kernel.org X-Gm-Message-State: AOJu0Yw2+l51RqrKa4R73x8ixd8oXpu5r2vMUtG+8v/vmcC22AHKfdYe BderbWtkqNxl0KDaJe4Y8iWFcq1AdJuqJpXEuEIs3h/JKpovbmNbDJTL X-Gm-Gg: AZuq6aKWk4OtcS2tdMLIeTeI2/3GjRUh5xKkgGQj7o08GMjgT+ji3qWR/ge4cICARWj c8CoVf8YBpSx7gRwco0sv4nqBQVlA+p6y3kLes7t6OjQpgSYERpM426Pmmyrm5Lgj/D2Yqakih0 1INbyYJhQrolH57MiZN9gc80EZ0qzjz/Ovv9ZXVpFSDHWt5OkICHZXoAh1FdpgYfrxWKBP7FA+H rogfOl4StkkuhRxysyjHbHkyaRNDTKknG7EnIg8SVUQ/WT3nlxkqsBEKRoa1bQQvvS1GF9gnwZG 06Zs6WJGXIPmlw+82NL5YprjQtVQQvzlDICnHGOQu9dkhbV4Jk8J9cavhkNIVtRR5+2YZJjCUnA QqZauPwwALYD7KaQ/Qz4UMVDfKA75tInVf+JXy6ZqaYqki8DrupaOJobTmOO9lKfhLu/W2BT4n9 XT4R9g8UWKf5UmxQ== X-Received: by 2002:a05:6000:3105:b0:435:db9b:5eb5 with SMTP id ffacd0b85a97d-435f3a7dcefmr11975821f8f.3.1769965692954; Sun, 01 Feb 2026 09:08:12 -0800 (PST) Received: from [192.168.8.10] ([2a00:f502:260:44d4:a26c:adcb:8da8:2]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-435e10f82aesm40615460f8f.19.2026.02.01.09.08.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 01 Feb 2026 09:08:12 -0800 (PST) From: Erikas Bitovtas Date: Sun, 01 Feb 2026 19:03:49 +0200 Subject: [PATCH 2/2] iio: light: Add support for Capella cm36686 and cm36672p sensors Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260201-cm36686-v1-2-4949a2a9ba63@gmail.com> References: <20260201-cm36686-v1-0-4949a2a9ba63@gmail.com> In-Reply-To: <20260201-cm36686-v1-0-4949a2a9ba63@gmail.com> To: Jonathan Cameron , David Lechner , =?utf-8?q?Nuno_S=C3=A1?= , Andy Shevchenko , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Kevin Tsai Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, ~postmarketos/upstreaming@lists.sr.ht, phone-devel@vger.kernel.org, Erikas Bitovtas X-Mailer: b4 0.14.3 This driver adds the initial support for the Capella cm36686 ambient light and proximity sensor and cm36672p proximity sensor. Signed-off-by: Erikas Bitovtas --- drivers/iio/light/Kconfig | 11 + drivers/iio/light/Makefile | 1 + drivers/iio/light/cm36686.c | 810 ++++++++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 822 insertions(+) diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index ac1408d374c9..b1d1943dec33 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -220,6 +220,17 @@ config CM36651 To compile this driver as a module, choose M here: the module will be called cm36651. =20 +config CM36686 + depends on I2C + tristate "CM36686 driver" + help + Say Y here if you use cm36686. + This option enables ambient light & proximity sensor using + Capella cm36686 device driver. + + To compile this driver as a module, choose M here: + the module will be called cm36686. + config IIO_CROS_EC_LIGHT_PROX tristate "ChromeOS EC Light and Proximity Sensors" depends on IIO_CROS_EC_SENSORS_CORE diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index c0048e0d5ca8..806df80f6454 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_CM3232) +=3D cm3232.o obj-$(CONFIG_CM3323) +=3D cm3323.o obj-$(CONFIG_CM3605) +=3D cm3605.o obj-$(CONFIG_CM36651) +=3D cm36651.o +obj-$(CONFIG_CM36686) +=3D cm36686.o obj-$(CONFIG_IIO_CROS_EC_LIGHT_PROX) +=3D cros_ec_light_prox.o obj-$(CONFIG_GP2AP002) +=3D gp2ap002.o obj-$(CONFIG_GP2AP020A00F) +=3D gp2ap020a00f.o diff --git a/drivers/iio/light/cm36686.c b/drivers/iio/light/cm36686.c new file mode 100644 index 000000000000..eb108af7226d --- /dev/null +++ b/drivers/iio/light/cm36686.c @@ -0,0 +1,810 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Device registers */ +#define CM36686_REG_ALS_CONF 0x00 +#define CM36686_REG_PS_CONF1 0x03 +#define CM36686_REG_PS_CONF3 0x04 +#define CM36686_REG_PS_THDL 0x06 +#define CM36686_REG_PS_THDH 0x07 +#define CM36686_REG_PS_DATA 0x08 +#define CM36686_REG_ALS_DATA 0x09 +#define CM36686_REG_INT_FLAG 0x0B +#define CM36686_REG_ID_FLAG 0x0C + +/* ALS_CONF */ +#define CM36686_ALS_IT GENMASK(7, 6) +#define CM36686_ALS_GAIN GENMASK(3, 2) +#define CM36686_ALS_INT_EN BIT(1) +#define CM36686_ALS_SD BIT(0) + +/* PS_CONF1 */ +#define CM36686_PS_DR GENMASK(7, 6) +#define CM36686_PS_PERS GENMASK(5, 4) +#define CM36686_PS_IT GENMASK(3, 1) +#define CM36686_PS_SD BIT(0) + +#define CM36686_PS_INT_OUT BIT(9) +#define CM36686_PS_INT_IN BIT(8) + +/* PS_CONF3 */ +#define CM36686_PS_SMART_PERS_ENABLE BIT(4) + +#define CM36686_LED_I GENMASK(10, 8) + +/* INT_FLAG */ +#define CM36686_PS_IF GENMASK(9, 8) + +/* Default values */ +#define CM36686_ALS_ENABLE 0x00 +#define CM36686_PS_DR_1_320 FIELD_PREP(CM36686_PS_DR, 3) +#define CM36686_PS_PERS_2 FIELD_PREP(CM36686_PS_PERS, 1) +#define CM36686_PS_IT_2_5T FIELD_PREP(CM36686_PS_IT, 3) +#define CM36686_LED_I_100 FIELD_PREP(CM36686_LED_I, 2) + +/* Shifts */ +#define CM36686_INT_FLAG_SHIFT 8 + +/* Max proximity thresholds */ +#define CM36686_MAX_PS_VALUE (BIT(12) - 1) + +#define CM36686_DEVICE_ID 0x86 + +enum { + CM36686, + CM36672P, +}; + +enum cm36686_distance { + CM36686_AWAY =3D 1, + CM36686_CLOSE, + CM36686_BOTH +}; + +enum { + CM36686_PS_CONF1, + CM36686_PS_CONF3, + CM36686_PS_CONF_NUM +}; + +enum { + CM36686_SUPPLY_VDD, + CM36686_SUPPLY_VDDIO, + CM36686_SUPPLY_VLED, + CM36686_SUPPLY_NUM, +}; + +static const int cm36686_als_it_times[][2] =3D { + {0, 80000}, + {0, 160000}, + {0, 320000}, + {0, 640000} +}; + +static const int cm36686_ps_it_times[][2] =3D { + {0, 320}, + {0, 480}, + {0, 640}, + {0, 800}, + {0, 960}, + {0, 1120}, + {0, 1280}, + {0, 2560} +}; + +static const int cm36686_ps_led_current[] =3D { + 50, + 75, + 100, + 120, + 140, + 160, + 180, + 200 +}; + +struct cm36686_data { + struct mutex lock; + struct i2c_client *client; + struct regulator_bulk_data supplies[CM36686_SUPPLY_NUM]; + int als_conf; + int ps_conf[CM36686_PS_CONF_NUM]; + int ps_close; + int ps_away; +}; + +struct cm36686_chip_info { + const char *name; + const struct iio_chan_spec *channels; + const int num_channels; +}; + +static int cm36686_current_to_index(int led_current) +{ + int i; + + for (i =3D 0; i < ARRAY_SIZE(cm36686_ps_led_current); i++) + if (led_current < cm36686_ps_led_current[i]) + break; + + return i > 0 ? i - 1 : -EINVAL; +} + +static ssize_t cm36686_read_near_level(struct iio_dev *indio_dev, + uintptr_t priv, + const struct iio_chan_spec *chan, + char *buf) +{ + struct cm36686_data *chip =3D iio_priv(indio_dev); + + return sysfs_emit(buf, "%u\n", chip->ps_close); +} + +static const struct iio_chan_spec_ext_info cm36686_ext_info[] =3D { + { + .name =3D "nearlevel", + .shared =3D IIO_SEPARATE, + .read =3D cm36686_read_near_level, + }, + {} +}; + +static const struct iio_event_spec cm36686_proximity_event_spec[] =3D { + { + .type =3D IIO_EV_TYPE_THRESH, + .dir =3D IIO_EV_DIR_FALLING, + .mask_separate =3D BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + }, + { + .type =3D IIO_EV_TYPE_THRESH, + .dir =3D IIO_EV_DIR_RISING, + .mask_separate =3D BIT(IIO_EV_INFO_VALUE) | + BIT(IIO_EV_INFO_ENABLE), + } +}; + +static const struct iio_chan_spec cm36686_channels[] =3D { + { + .type =3D IIO_LIGHT, + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_separate_available =3D BIT(IIO_CHAN_INFO_INT_TIME), + .address =3D CM36686_REG_ALS_DATA, + }, + { + .type =3D IIO_PROXIMITY, + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_separate_available =3D BIT(IIO_CHAN_INFO_INT_TIME), + .address =3D CM36686_REG_PS_DATA, + .event_spec =3D cm36686_proximity_event_spec, + .num_event_specs =3D ARRAY_SIZE(cm36686_proximity_event_spec), + .ext_info =3D cm36686_ext_info + } +}; + +static const struct iio_chan_spec cm36672p_channels[] =3D { + { + .type =3D IIO_PROXIMITY, + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_separate_available =3D BIT(IIO_CHAN_INFO_INT_TIME), + .address =3D CM36686_REG_PS_DATA, + .event_spec =3D cm36686_proximity_event_spec, + .num_event_specs =3D ARRAY_SIZE(cm36686_proximity_event_spec), + .ext_info =3D cm36686_ext_info + } +}; + +static int cm36686_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + if (mask !=3D IIO_CHAN_INFO_INT_TIME) + return -EINVAL; + + switch (chan->type) { + case IIO_LIGHT: + *vals =3D (int *)(cm36686_als_it_times); + *length =3D 2 * ARRAY_SIZE(cm36686_als_it_times); + *type =3D IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + case IIO_PROXIMITY: + *vals =3D (int *)(cm36686_ps_it_times); + *length =3D 2 * ARRAY_SIZE(cm36686_ps_it_times); + *type =3D IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int cm36686_read_channel(struct cm36686_data *chip, + struct iio_chan_spec const *chan, int *val) +{ + struct i2c_client *client =3D chip->client; + int ret =3D IIO_VAL_INT; + + int data =3D i2c_smbus_read_word_data(client, chan->address); + + if (data < 0) { + dev_err(&client->dev, "Failed to read register: %pe", ERR_PTR(data)); + ret =3D -EIO; + } else { + *val =3D data; + } + return ret; +} + +static int cm36686_read_int_time(struct cm36686_data *chip, + struct iio_chan_spec const *chan, int *val, + int *val2) +{ + int als_it_index, ps_it_index; + + switch (chan->type) { + case IIO_LIGHT: + als_it_index =3D FIELD_GET(CM36686_ALS_IT, chip->als_conf); + *val =3D cm36686_als_it_times[als_it_index][0]; + *val2 =3D cm36686_als_it_times[als_it_index][1]; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_PROXIMITY: + ps_it_index =3D FIELD_GET(CM36686_PS_IT, + chip->ps_conf[CM36686_PS_CONF1]); + *val =3D cm36686_ps_it_times[ps_it_index][0]; + *val2 =3D cm36686_ps_it_times[ps_it_index][1]; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int cm36686_write_light_int_time(struct cm36686_data *chip, int val= 2) +{ + struct i2c_client *client =3D chip->client; + int index =3D -1, ret, new_it_time; + + for (int i =3D 0; i < ARRAY_SIZE(cm36686_als_it_times); i++) { + if (cm36686_als_it_times[i][1] =3D=3D val2) { + index =3D i; + break; + } + } + + if (index =3D=3D -1) + return -EINVAL; + + new_it_time =3D chip->als_conf & ~CM36686_ALS_IT; + new_it_time |=3D FIELD_PREP(CM36686_ALS_IT, index); + + ret =3D i2c_smbus_write_word_data(chip->client, CM36686_REG_ALS_CONF, + new_it_time); + if (ret < 0) + dev_err(&client->dev, + "Failed to set ALS integration time: %pe", ERR_PTR(ret)); + else + chip->als_conf =3D new_it_time; + + return ret; +} + +static int cm36686_write_prox_int_time(struct cm36686_data *chip, int val2) +{ + struct i2c_client *client =3D chip->client; + int index =3D -1, ret, new_it_time; + + for (int i =3D 0; i < ARRAY_SIZE(cm36686_ps_it_times); i++) { + if (cm36686_ps_it_times[i][1] =3D=3D val2) { + index =3D i; + break; + } + } + + if (index =3D=3D -1) + return -EINVAL; + + new_it_time =3D chip->ps_conf[CM36686_PS_CONF1] & ~CM36686_PS_IT; + new_it_time |=3D FIELD_PREP(CM36686_PS_IT, index); + + ret =3D i2c_smbus_write_word_data(chip->client, CM36686_REG_PS_CONF1, + new_it_time); + if (ret < 0) + dev_err(&client->dev, "Failed to set PS integration time: %pe", + ERR_PTR(ret)); + else + chip->ps_conf[CM36686_PS_CONF1] =3D new_it_time; + + return ret; +} + +static int cm36686_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct cm36686_data *chip =3D iio_priv(indio_dev); + int ret; + + mutex_lock(&chip->lock); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret =3D cm36686_read_channel(chip, chan, val); + break; + case IIO_CHAN_INFO_INT_TIME: + ret =3D cm36686_read_int_time(chip, chan, val, val2); + break; + default: + ret =3D -EINVAL; + } + + mutex_unlock(&chip->lock); + return ret; +} + +static int cm36686_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct cm36686_data *chip =3D iio_priv(indio_dev); + int ret; + + if (val) /* Integration time more than 1s is not supported */ + return -EINVAL; + + if (mask !=3D IIO_CHAN_INFO_INT_TIME) + return -EINVAL; + + mutex_lock(&chip->lock); + + switch (chan->type) { + case IIO_LIGHT: + ret =3D cm36686_write_light_int_time(chip, val2); + break; + case IIO_PROXIMITY: + ret =3D cm36686_write_prox_int_time(chip, val2); + break; + default: + ret =3D -EINVAL; + } + + mutex_unlock(&chip->lock); + return ret; +} + +static int cm36686_read_prox_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int *val, + int *val2) +{ + struct cm36686_data *chip =3D iio_priv(indio_dev); + + if (chan->type !=3D IIO_PROXIMITY) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_RISING: + *val =3D chip->ps_close; + break; + case IIO_EV_DIR_FALLING: + *val =3D chip->ps_away; + break; + default: + return -EINVAL; + } + + return IIO_VAL_INT; +} + +static int cm36686_write_prox_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, int val, + int val2) +{ + struct cm36686_data *chip =3D iio_priv(indio_dev); + struct i2c_client *client =3D chip->client; + int ret =3D 0, address, *thresh; + + if (chan->type !=3D IIO_PROXIMITY) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_FALLING: + if (val > chip->ps_close || val < 0) + return -EINVAL; + + address =3D CM36686_REG_PS_THDL; + thresh =3D &chip->ps_away; + break; + case IIO_EV_DIR_RISING: + if (val < chip->ps_away || val > CM36686_MAX_PS_VALUE) + return -EINVAL; + + address =3D CM36686_REG_PS_THDH; + thresh =3D &chip->ps_close; + break; + default: + return -EINVAL; + } + + mutex_lock(&chip->lock); + + ret =3D i2c_smbus_write_word_data(client, address, val); + if (ret < 0) + dev_err(&client->dev, + "Failed to set PS threshold value: %pe", ERR_PTR(ret)); + else + *thresh =3D val; + + mutex_unlock(&chip->lock); + return ret; +} + +static int cm36686_read_prox_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct cm36686_data *chip =3D iio_priv(indio_dev); + + if (chan->type !=3D IIO_PROXIMITY) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_FALLING: + return FIELD_GET(CM36686_PS_INT_OUT, chip->ps_conf[CM36686_PS_CONF1]); + case IIO_EV_DIR_RISING: + return FIELD_GET(CM36686_PS_INT_IN, chip->ps_conf[CM36686_PS_CONF1]); + default: + return -EINVAL; + } +} + +static int cm36686_write_prox_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct cm36686_data *chip =3D iio_priv(indio_dev); + struct i2c_client *client =3D chip->client; + int ret =3D 0, new_ps_conf; + + if (chan->type !=3D IIO_PROXIMITY) + return -EINVAL; + + switch (dir) { + case IIO_EV_DIR_FALLING: + new_ps_conf =3D chip->ps_conf[CM36686_PS_CONF1] & ~CM36686_PS_INT_OUT; + new_ps_conf |=3D FIELD_PREP(CM36686_PS_INT_OUT, state); + break; + case IIO_EV_DIR_RISING: + new_ps_conf =3D chip->ps_conf[CM36686_PS_CONF1] & ~CM36686_PS_INT_IN; + new_ps_conf |=3D FIELD_PREP(CM36686_PS_INT_IN, state); + break; + default: + return -EINVAL; + } + + mutex_lock(&chip->lock); + + ret =3D i2c_smbus_write_word_data(chip->client, CM36686_REG_PS_CONF1, new= _ps_conf); + if (ret < 0) + dev_err(&client->dev, + "Failed to set proximity event interrupt config: %pe", ERR_PTR(ret)); + else + chip->ps_conf[CM36686_PS_CONF1] =3D new_ps_conf; + + mutex_unlock(&chip->lock); + + return ret; +} + +static int cm36686_fallback_read_ps(struct iio_dev *indio_dev) +{ + struct cm36686_data *chip =3D iio_priv(indio_dev); + struct i2c_client *client =3D chip->client; + int data =3D i2c_smbus_read_word_data(client, CM36686_REG_PS_DATA); + + if (data < 0) + return data; + + if (data < chip->ps_away) + return IIO_EV_DIR_FALLING; + else if (data > chip->ps_close) + return IIO_EV_DIR_RISING; + else + return IIO_EV_DIR_EITHER; +} + +static irqreturn_t cm36686_irq_handler(int irq, void *data) +{ + struct iio_dev *indio_dev =3D data; + struct cm36686_data *chip =3D iio_priv(indio_dev); + struct i2c_client *client =3D chip->client; + int ev_dir, ret; + u64 ev_code; + + /* Reading the interrupt flag acknowledges the interrupt */ + ret =3D i2c_smbus_read_word_data(client, CM36686_REG_INT_FLAG); + if (ret < 0) { + dev_err(&client->dev, + "Interrupt flag register read failed: %pe", ERR_PTR(ret)); + return IRQ_HANDLED; + } + + ret >>=3D CM36686_INT_FLAG_SHIFT; + switch (ret) { + case CM36686_CLOSE: + ev_dir =3D IIO_EV_DIR_RISING; + break; + case CM36686_AWAY: + ev_dir =3D IIO_EV_DIR_FALLING; + break; + case CM36686_BOTH: + ev_dir =3D cm36686_fallback_read_ps(indio_dev); + if (ev_dir < 0) { + dev_err(&client->dev, "Failed to settle interrupt state: %pe", + ERR_PTR(ret)); + return IRQ_HANDLED; + } + break; + default: + dev_err(&client->dev, "Unknown interrupt state: %x", ret); + return IRQ_HANDLED; + } + ev_code =3D IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, IIO_EV_INFO_VALUE, + IIO_EV_TYPE_THRESH, ev_dir); + + iio_push_event(indio_dev, ev_code, iio_get_time_ns(indio_dev)); + return IRQ_HANDLED; +} + +static const struct iio_info cm36686_info =3D { + .read_avail =3D cm36686_read_avail, + .read_raw =3D cm36686_read_raw, + .write_raw =3D cm36686_write_raw, + .read_event_value =3D cm36686_read_prox_thresh, + .write_event_value =3D cm36686_write_prox_thresh, + .read_event_config =3D cm36686_read_prox_event_config, + .write_event_config =3D cm36686_write_prox_event_config, +}; + +static struct cm36686_chip_info cm36686_chip_info_tbl[] =3D { + [CM36686] =3D { + .name =3D "cm36686", + .channels =3D cm36686_channels, + .num_channels =3D ARRAY_SIZE(cm36686_channels), + }, + [CM36672P] =3D { + .name =3D "cm36672p", + .channels =3D cm36672p_channels, + .num_channels =3D ARRAY_SIZE(cm36672p_channels), + }, +}; + +static int cm36686_setup(struct cm36686_data *chip) +{ + struct i2c_client *client =3D chip->client; + int ret, led_current, led_index; + + chip->als_conf =3D CM36686_ALS_ENABLE; + + ret =3D i2c_smbus_write_word_data(client, CM36686_REG_ALS_CONF, + chip->als_conf); + if (ret < 0) { + dev_err(&client->dev, + "Failed to enable ambient light sensor: %pe", ERR_PTR(ret)); + return ret; + } + + chip->ps_conf[CM36686_PS_CONF1] =3D CM36686_PS_INT_IN | + CM36686_PS_INT_OUT | CM36686_PS_DR_1_320 | + CM36686_PS_PERS_2 | CM36686_PS_IT_2_5T; + + ret =3D i2c_smbus_write_word_data(client, CM36686_REG_PS_CONF1, + chip->ps_conf[CM36686_PS_CONF1]); + if (ret < 0) { + dev_err(&client->dev, + "Failed to enable proximity sensor: %pe", ERR_PTR(ret)); + return ret; + } + + chip->ps_conf[CM36686_PS_CONF3] =3D CM36686_PS_SMART_PERS_ENABLE; + + ret =3D device_property_read_u32(&client->dev, + "capella,proximity-led-current", &led_current); + if (!ret) { + led_index =3D cm36686_current_to_index(led_current); + if (led_index < 0) { + dev_err(&client->dev, "No appropriate current for IR LED found."); + return led_index; + } + + chip->ps_conf[CM36686_PS_CONF3] &=3D ~CM36686_LED_I; + chip->ps_conf[CM36686_PS_CONF3] |=3D FIELD_PREP(CM36686_LED_I, led_index= ); + } + + ret =3D i2c_smbus_write_word_data(client, CM36686_REG_PS_CONF3, + chip->ps_conf[CM36686_PS_CONF3]); + if (ret < 0) { + dev_err(&client->dev, + "Failed to enable proximity sensor: %pe", ERR_PTR(ret)); + return ret; + } + + ret =3D device_property_read_u32(&client->dev, "proximity-near-level", + &chip->ps_close); + if (ret < 0) + chip->ps_close =3D 0; + + ret =3D i2c_smbus_write_word_data(client, CM36686_REG_PS_THDH, + chip->ps_close); + if (ret < 0) { + dev_err(&client->dev, + "Failed to set close proximity threshold: %pe", ERR_PTR(ret)); + return ret; + } + + chip->ps_away =3D chip->ps_close; + + ret =3D i2c_smbus_write_word_data(client, CM36686_REG_PS_THDL, + chip->ps_away); + if (ret < 0) { + dev_err(&client->dev, + "Failed to set away proximity threshold: %pe", ERR_PTR(ret)); + return ret; + } + + return 0; +} + +static void cm36686_shutdown(void *data) +{ + struct cm36686_data *chip =3D data; + struct i2c_client *client =3D chip->client; + int ret, als_shutdown, ps_shutdown; + + als_shutdown =3D chip->als_conf | CM36686_ALS_SD; + + ret =3D i2c_smbus_write_word_data(client, CM36686_REG_ALS_CONF, + als_shutdown); + if (ret < 0) + dev_err(&client->dev, "Failed to shutdown ALS"); + + ps_shutdown =3D chip->ps_conf[CM36686_PS_CONF1] | CM36686_PS_SD; + + ret =3D i2c_smbus_write_word_data(client, CM36686_REG_PS_CONF1, + ps_shutdown); + if (ret < 0) + dev_err(&client->dev, "Failed to shutdown PS"); + + ret =3D regulator_bulk_disable(ARRAY_SIZE(chip->supplies), chip->supplies= ); + if (ret < 0) + dev_err(&client->dev, "Failed to disable regulators"); +} + +static int cm36686_probe(struct i2c_client *client) +{ + struct iio_dev *indio_dev; + struct cm36686_data *chip; + int ret; + + indio_dev =3D devm_iio_device_alloc(&client->dev, + sizeof(struct cm36686_data)); + if (!indio_dev) + return -ENOMEM; + + chip =3D iio_priv(indio_dev); + + const struct i2c_device_id *id =3D i2c_client_get_device_id(client); + + const struct cm36686_chip_info *info =3D + &cm36686_chip_info_tbl[id->driver_data]; + + ret =3D i2c_smbus_read_byte_data(client, CM36686_REG_ID_FLAG); + if (ret < 0) + return dev_err_probe(&client->dev, ret, "Failed to read device ID"); + + if (ret !=3D CM36686_DEVICE_ID) + return dev_err_probe(&client->dev, -ENODEV, "Device not recognized!"); + + i2c_set_clientdata(client, indio_dev); + chip->client =3D client; + ret =3D devm_mutex_init(&client->dev, &chip->lock); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to initialize mutex"); + + chip->supplies[CM36686_SUPPLY_VDD].supply =3D "vdd"; + chip->supplies[CM36686_SUPPLY_VDDIO].supply =3D "vddio"; + chip->supplies[CM36686_SUPPLY_VLED].supply =3D "vled"; + + ret =3D devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(chip->supplies), chip->supplies); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to enable regulators"); + + ret =3D devm_add_action_or_reset(&client->dev, cm36686_shutdown, chip); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to set shutdown action"); + + ret =3D cm36686_setup(chip); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to set up registers"); + + indio_dev->name =3D info->name; + indio_dev->channels =3D info->channels; + indio_dev->num_channels =3D info->num_channels; + indio_dev->info =3D &cm36686_info; + indio_dev->modes =3D INDIO_DIRECT_MODE; + + ret =3D devm_request_threaded_irq(&client->dev, client->irq, NULL, + cm36686_irq_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to request irq"); + + ret =3D devm_iio_device_register(&client->dev, indio_dev); + if (ret) + return dev_err_probe(&client->dev, ret, + "Failed to register iio device"); + + return 0; +} + +static const struct i2c_device_id cm36686_id[] =3D { + { "cm36686", CM36686 }, + { "cm36672p", CM36672P }, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cm36686_id); + +static const struct of_device_id cm36686_of_match[] =3D { + { .compatible =3D "capella,cm36686" }, + { .compatible =3D "capella,cm36672p" }, + {} +}; +MODULE_DEVICE_TABLE(of, cm36686_of_match); + +static struct i2c_driver cm36686_driver =3D { + .driver =3D { + .name =3D "cm36686", + .of_match_table =3D cm36686_of_match, + }, + .probe =3D cm36686_probe, + .id_table =3D cm36686_id +}; + +module_i2c_driver(cm36686_driver); + +MODULE_AUTHOR("Erikas Bitovtas "); +MODULE_DESCRIPTION("CM36686 ambient light and proximity sensor driver"); +MODULE_LICENSE("GPL"); --=20 2.52.0