From nobody Sat Oct 4 11:10:00 2025 Received: from mail-wr1-f50.google.com (mail-wr1-f50.google.com [209.85.221.50]) (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 DAC063FE5 for ; Sun, 17 Aug 2025 13:06:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755435966; cv=none; b=bVGf9qM4oMvZAoISertlXVh21pbqy8oiqt5q8AwUO61vubdHSTCV4yExRDSH+7vIoup2wEtNnMe6OYi3MLWyiRCmu1HZ3gAKVovT7MFEXgutRUzb4qxmW0ZkCgxbHRTGCyUxbcyKmL7RV2QmVcX2iokzTCo6qDj3d+I3ZDxpsA0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755435966; c=relaxed/simple; bh=n+LG32lrqhbZEHPnRm0DKpY9KEFe6UgElqttoMw0ofY=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=DT6fbZm1OKYxKTJdt7dwMfAFJXHcxw8bgix/lOosP9S81ByzGFUNRmD9bfv2YDQ0sMsjTnTK6k1Bhi3jj96GEGjPjZqpV1DIJ8j3KZAt6+Idk18fEzomeWW1rY4QplZg2+Am5jRBt2Wh4lLgzwylVQLqHMbC5gp2ekUmTHnlX08= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pinefeat.co.uk; spf=pass smtp.mailfrom=pinefeat.co.uk; dkim=pass (2048-bit key) header.d=pinefeat.co.uk header.i=@pinefeat.co.uk header.b=PdWoNvLi; arc=none smtp.client-ip=209.85.221.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pinefeat.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pinefeat.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pinefeat.co.uk header.i=@pinefeat.co.uk header.b="PdWoNvLi" Received: by mail-wr1-f50.google.com with SMTP id ffacd0b85a97d-3b9edf0e4efso2383576f8f.2 for ; Sun, 17 Aug 2025 06:06:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pinefeat.co.uk; s=google; t=1755435962; x=1756040762; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=pOX3OLtfh155JLaNOfAZcUOGkcxsk7cjeCdGL/xz2xU=; b=PdWoNvLiDRcQrC2KKfY2Nzo/sGCY+RB4H7pvODl9azqe8nmX2sTNrgXZpH5lG0dWr0 A2IGkE1GJTHD1PqGqZ886JMSL5hX2zQWSWrcjRbUY6bx/5rBYBex8HtUC7tu2roojD7M hJNm0yRwi0NLavhRtINAMiftcJnt9BXB8XqEcA4tN33WSujNcU5NhzN8jncRH5PKdHJi NFM7ZVYhwfQuMTdLuqirhiVvh9aPbZZRuZKN+YfGvKYPvMhgM+6U/kGyYUqYUSE/xfiJ fcW2Sb6tEVvOuMSbPDt2QbVL8mGaQcD4ShP/J9r0c960hbLHjK886EtVmsi128WhZl0w 1adw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755435962; x=1756040762; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=pOX3OLtfh155JLaNOfAZcUOGkcxsk7cjeCdGL/xz2xU=; b=Cmq1jqwIuZ1vfQG0jjkNjDutSR6zdVR63wPct8LQ+qMGxX1gL274LctBX9Gljlih8l nF9pg1tsPqVk+jp5Qbvb5UIgIWScafHr+ZBAL7cvLttvpNDvOqjAyv2OiO1pJ6NFl9Yx 7OyCC7+stwstKRVZfZHpA3NcD2H3YESotUrU2sQqgnvkPoQ1LZnt2EWZnAYZXnKq10lm ZyvTxAa+8OccD0L//tSLjBMDZetVjyrt8hCc5SOs0auuMmZQzbdrv6j98I4y0rM0RbY/ oJ/lFEF5PK1adQ8vbQq4afkAc6URWFAwYJnPOlr7/gLjPK64+FVOEL5e2jf1OkG/XQLM 0zTg== X-Forwarded-Encrypted: i=1; AJvYcCUCSJWihzHNFdVRzFI5F4UFDK8mmOvfbWXOlw1KjWcZDb+ypDhf6h+nh5U9H7J2E+5EMYP97k6LdV7ky6I=@vger.kernel.org X-Gm-Message-State: AOJu0YwHCGTv7Lbo2dcLgcwFUYuvwy1/csNOYfBpRV420LCaSdc1jYE6 cFkOLbeE1uEWbrjQrj/tqk4OveIvr78Ja5ruW8Mk8/wGnDhCqYGRbtBari9kiiRLgfU= X-Gm-Gg: ASbGncuc5BaHVi4TFTldU/1kju9lT2MGaUkhqB6rcqLIMIMJ1fv/ab+VTV6PWKUIvLZ EnorAaZHZIDNjaTGZ6+3gaXt6E96d62g9VdqQ7Nm1Gzy2x3znO0WxZlhEOZrqc0ch0h7WRRQfLb giggwbw4BlyC4M2KQhzbdmozZ3AhloRH6ZRsqASkC9Mi7wH1jgltf88CsCvzwPqE/5RoqQS2jxV r6E8ZwFXG8jSJkI1YLxOchpRDoG0x8GA0IoaH3wv7UA1EyzTChVN1Cp+NKbjA0lj32FC5my8Zmq 5mNiYfs/VPrYK+64qI3TBIfhpQfpM7oCFYNFQlBNIvLnOjPcSfvZw9BmWoSfGJ7Ux1pE3Olm/Z7 m/+bi35v1wOAiyU/+TuzhGCjQHjnctCFMOZ+PRUrm X-Google-Smtp-Source: AGHT+IH4t+K+wdyKKjtu7C3WDETSgqotuEysxOHVXeMjX8vcWBncV2wJkzVWE9RHgsvRzMXRTAtVLA== X-Received: by 2002:a05:6000:400a:b0:3a6:d95e:f38c with SMTP id ffacd0b85a97d-3bb68a17232mr5769729f8f.33.1755435962122; Sun, 17 Aug 2025 06:06:02 -0700 (PDT) Received: from asmirnov-G751JM.Home ([2a02:c7c:b28c:1f00:b4c0:f0fd:db4c:31dd]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3bb93862fe7sm9235729f8f.64.2025.08.17.06.06.01 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 17 Aug 2025 06:06:01 -0700 (PDT) From: Aliaksandr Smirnou To: mchehab@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: devicetree@vger.kernel.org, linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, Aliaksandr Smirnou Subject: [PATCH v3 1/2] dt-bindings: Pinefeat cef168 lens control board Date: Sun, 17 Aug 2025 14:05:48 +0100 Message-Id: <20250817130549.7766-2-support@pinefeat.co.uk> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250817130549.7766-1-support@pinefeat.co.uk> References: <20250817130549.7766-1-support@pinefeat.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add the Device Tree schema and examples for the Pinefeat cef168 lens control board. This board interfaces Canon EF & EF-S lenses with non-Canon camera bodies, enabling electronic control of focus and aperture via V4L2. Power supply is derived from fixed supplies via connector or GPIO header. Therefore, the driver does not manage any regulator, so representing any supply in the binding is redundant. Signed-off-by: Aliaksandr Smirnou --- .../bindings/media/i2c/pinefeat,cef168.yaml | 48 +++++++++++++++++++ .../devicetree/bindings/vendor-prefixes.yaml | 2 + MAINTAINERS | 6 +++ 3 files changed, 56 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/pinefeat,ce= f168.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/pinefeat,cef168.ya= ml b/Documentation/devicetree/bindings/media/i2c/pinefeat,cef168.yaml new file mode 100644 index 000000000000..80cdff40d175 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/pinefeat,cef168.yaml @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (c) 2025 Pinefeat LLP +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/pinefeat,cef168.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Pinefeat cef168 lens driver + +maintainers: + - Aliaksandr Smirnou + +description: | + Pinefeat produces an adapter designed to interface between + Canon EF & EF-S lenses and non-Canon camera bodies, incorporating + features for electronic focus and aperture adjustment. The cef168 + circuit board, included with the adapter, provides a software + programming interface that allows control of lens focus and + aperture positions. This driver enables controlling the lens + focus and aperture via the V4L2 (Video4Linux2) API. + +properties: + compatible: + enum: + - pinefeat,cef168 + + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + camera-lens@d { + compatible =3D "pinefeat,cef168"; + reg =3D <0x0d>; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Docum= entation/devicetree/bindings/vendor-prefixes.yaml index 77160cd47f54..dab27f769b0a 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -1195,6 +1195,8 @@ patternProperties: description: Picochip Ltd "^pine64,.*": description: Pine64 + "^pinefeat,.*": + description: Pinefeat LLP "^pineriver,.*": description: Shenzhen PineRiver Designs Co., Ltd. "^pixcir,.*": diff --git a/MAINTAINERS b/MAINTAINERS index fe168477caa4..811c6a150029 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19985,6 +19985,12 @@ S: Supported F: Documentation/devicetree/bindings/input/pine64,pinephone-keyboard.yaml F: drivers/input/keyboard/pinephone-keyboard.c =20 +PINEFEAT CEF168 LENS DRIVER +M: Aliaksandr Smirnou +L: linux-media@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/media/i2c/pinefeat,cef168.yaml + PLANTOWER PMS7003 AIR POLLUTION SENSOR DRIVER M: Tomasz Duszynski S: Maintained --=20 2.34.1 From nobody Sat Oct 4 11:10:00 2025 Received: from mail-wm1-f46.google.com (mail-wm1-f46.google.com [209.85.128.46]) (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 03FF423C512 for ; Sun, 17 Aug 2025 13:06:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755435967; cv=none; b=MwwdZNxKU6A4LqwA5r/Qxiuqy1tiXHtkO8x3TARX9/yrqPtnpvz9WTf65fAhxgQL9JM189e9nELIcV1iAwGyOMkanAjZkV6Z4Q5PjibnM9brh8VNWPlyoqioXfSiqXWIkxF4jOuVATHalq78v+pFpJ4ARDXPwg26b0BeyAEXYy8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755435967; c=relaxed/simple; bh=VKw54mv5Clqown3IKY6Q41ggLRuSenr05wvAtBblemw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=K7z90eEZBJ9UdijzutLhnINnafftVPiDm16an6L/xemvtK7Is9AyJqr1X0wR2hgud1Ql83h2OW/kP/VxdN5WKQumteaQnty7liDsLq1OICOKrh4we7pwcjyxEOcTI0FxHHjPwMUsXMoExyyrIrkseMEjt5usR1qydWikR/WXd+Q= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pinefeat.co.uk; spf=pass smtp.mailfrom=pinefeat.co.uk; dkim=pass (2048-bit key) header.d=pinefeat.co.uk header.i=@pinefeat.co.uk header.b=ZrpPe42G; arc=none smtp.client-ip=209.85.128.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pinefeat.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pinefeat.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pinefeat.co.uk header.i=@pinefeat.co.uk header.b="ZrpPe42G" Received: by mail-wm1-f46.google.com with SMTP id 5b1f17b1804b1-45a1b0d231eso17094465e9.3 for ; Sun, 17 Aug 2025 06:06:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pinefeat.co.uk; s=google; t=1755435963; x=1756040763; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=jz6fstsCDsDBjL4jUd8VaaWUGhUyW4ehi4fYTsnqtJE=; b=ZrpPe42G8LT2iXNNQW4pcfv5kLyLpEatc4yM3UvFzv/n6I9i9AYcx+/UKE91VTDE7m x0JXlM7wU8FiDVZDpgDWKC21SQQ/tSfkmLHcnAW0xfCPLbZZkaVD+AitTMRJQ1pMwSuY CKCcgafmqy/QpAD78ilGeNgFpz7j+JTBLTRNCjul59G5j1p1N6jIwau4e50k5rsfnw9U 0TirHP6RhznBD8crzv/YdK0r1El5a1bkhyOBoL/ZCl8DccxS/sZr1iIp2NKlfE9KojuT RnCbAbrxM5AKURDPi/qGYq3DjPfp6/T/UtT69FKgkgpjnu45Uhhms0jIEiFapntnKZ5A 3oZg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755435963; x=1756040763; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=jz6fstsCDsDBjL4jUd8VaaWUGhUyW4ehi4fYTsnqtJE=; b=q4dTlCPgJehQhGhdYl7CH8pTZrrmrFJD02UJ01uQxmDztHQ2iTtxKG2iroANwznEgQ iPKmXyCR8H/8tTB4jcAW2GYkFkRXD+SXUY3Y+xPl1VXljkDpxi2GCufGy+UfkDe52iZC w2kUTHJQJmYZd2pn3io27ziI+Bm2lXstV7lDF6tx1ArOWU1bjxi+GGbLHhk9Wrntf8mp lEy9QOn2ONWZxjD5tYzwutSmL4arr8ui2NAqQ2yzoIa1uhpXCT2UjlJoqUSgSVzli+vl ePGN6TFuc3i/L8wPvtdWspaIIDQFQWa3QwsXfYu9qjsB2ZLbQ80sDwbwvkD3Zk32DoFj cDgw== X-Forwarded-Encrypted: i=1; AJvYcCUYIXTCOpEdSf8Kvm7Jil+Ureoljid9m+O3KEWB1k1iWjToRGMupfwydTLzE8a3OSqsavDKyy/uiqpu1UE=@vger.kernel.org X-Gm-Message-State: AOJu0YyxEGYHCESxlOKzqLdH/TYsIT43/3lFO8nugpDhfhqDgf51vPvh CzS/IhcZ87FVLGpApX+7ssmHSI7ITu8Oibm7cc4mugiqlG+7t0oUG3XxWnBUYlQwSd8= X-Gm-Gg: ASbGncvrmktFiiMo9XGWYWX3B4krdJtj798S1Wr1AbiGpNjP+1wNKKRPUm44kbDR9t2 wDHxwsMVsQO/RzuMCUfZEZSs8x8Z0JvgqWWLNidTlP+nd8QCEF2XEuSGpVeTKSG75Yik4/Wtu4D 7vgWKRxK/dKcF1FNThoyIbrVTNYGNdB7HK/yblByBIXi64n5FZ70Nr6il0c2YmLUuN9YaSz3Z2X XEEWkKBjcDow6+FdlJaSlF44jcRPZ1r7emTg/dj2coM4yJgX1hTuamUEcVyg5NmWHH0gAZ8CeTQ YnshuT35bpRODdUODi2rPLU6//X/9LFA8cpz8GNg8pKGEmOjuMKEv4yBXqZM8Pj/tacAT0lPhKt 0bxEGGjPaMICbuGplTtqx3kRWaHSHVUz4mcLwjT9l X-Google-Smtp-Source: AGHT+IGsC9v/Xr/ndSdBbWYN/HHdzSI/j5fjpRzgKIpfh4UOWP3dWxjdOj80vTrPeAhdnQKnzNlFjQ== X-Received: by 2002:a05:600c:8707:b0:453:5c30:a1fd with SMTP id 5b1f17b1804b1-45a25283959mr38362715e9.8.1755435963046; Sun, 17 Aug 2025 06:06:03 -0700 (PDT) Received: from asmirnov-G751JM.Home ([2a02:c7c:b28c:1f00:b4c0:f0fd:db4c:31dd]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3bb93862fe7sm9235729f8f.64.2025.08.17.06.06.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 17 Aug 2025 06:06:02 -0700 (PDT) From: Aliaksandr Smirnou To: mchehab@kernel.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: devicetree@vger.kernel.org, linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, Aliaksandr Smirnou Subject: [PATCH v3 2/2] media: i2c: Pinefeat cef168 lens control board driver Date: Sun, 17 Aug 2025 14:05:49 +0100 Message-Id: <20250817130549.7766-3-support@pinefeat.co.uk> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250817130549.7766-1-support@pinefeat.co.uk> References: <20250817130549.7766-1-support@pinefeat.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add support for the Pinefeat cef168 lens control board that provides electronic focus and aperture control for Canon EF & EF-S lenses on non-Canon camera bodies. Signed-off-by: Aliaksandr Smirnou --- MAINTAINERS | 2 + drivers/media/i2c/Kconfig | 8 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/cef168.c | 335 +++++++++++++++++++++++++++++++++++++ drivers/media/i2c/cef168.h | 51 ++++++ 5 files changed, 397 insertions(+) create mode 100644 drivers/media/i2c/cef168.c create mode 100644 drivers/media/i2c/cef168.h diff --git a/MAINTAINERS b/MAINTAINERS index 811c6a150029..922efc000722 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19990,6 +19990,8 @@ M: Aliaksandr Smirnou L: linux-media@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/media/i2c/pinefeat,cef168.yaml +F: drivers/media/i2c/cef168.c +F: drivers/media/i2c/cef168.h =20 PLANTOWER PMS7003 AIR POLLUTION SENSOR DRIVER M: Tomasz Duszynski diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 6237fe804a5c..c4c3b03a0b98 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -791,6 +791,14 @@ config VIDEO_AK7375 capability. This is designed for linear control of voice coil motors, controlled via I2C serial interface. =20 +config VIDEO_CEF168 + tristate "CEF168 lens control support" + help + This is a driver for the CEF168 lens control board. + The board provides an I2C interface for electronic focus + and aperture control of EF and EF-S lenses. The driver + integrates with the V4L2 sub-device API. + config VIDEO_DW9714 tristate "DW9714 lens voice coil support" depends on GPIOLIB diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 5873d29433ee..75a95f850f18 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_VIDEO_BT856) +=3D bt856.o obj-$(CONFIG_VIDEO_BT866) +=3D bt866.o obj-$(CONFIG_VIDEO_CCS) +=3D ccs/ obj-$(CONFIG_VIDEO_CCS_PLL) +=3D ccs-pll.o +obj-$(CONFIG_VIDEO_CEF168) +=3D cef168.o obj-$(CONFIG_VIDEO_CS3308) +=3D cs3308.o obj-$(CONFIG_VIDEO_CS5345) +=3D cs5345.o obj-$(CONFIG_VIDEO_CS53L32A) +=3D cs53l32a.o diff --git a/drivers/media/i2c/cef168.c b/drivers/media/i2c/cef168.c new file mode 100644 index 000000000000..563251a54835 --- /dev/null +++ b/drivers/media/i2c/cef168.c @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2025 Pinefeat LLP + +#include +#include +#include +#include +#include +#include +#include +#include +#include "cef168.h" + +/* + * cef168 device structure + */ +struct cef168_device { + struct v4l2_ctrl_handler ctrls; + struct v4l2_subdev sd; +}; + +static inline struct cef168_device *to_cef168(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct cef168_device, ctrls); +} + +static inline struct cef168_device *sd_to_cef168(struct v4l2_subdev *subde= v) +{ + return container_of(subdev, struct cef168_device, sd); +} + +static int cef168_i2c_write(struct cef168_device *cef168_dev, u8 cmd, u16 = val) +{ + struct i2c_client *client =3D v4l2_get_subdevdata(&cef168_dev->sd); + int retry, ret; + + __le16 le_data =3D cpu_to_le16(val); + char tx_data[4] =3D { cmd, ((u8 *)&le_data)[0], ((u8 *)&le_data)[1] }; + + tx_data[3] =3D crc8(cef168_crc8_table, tx_data, 3, CRC8_INIT_VALUE); + + for (retry =3D 0; retry < 3; retry++) { + ret =3D i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret =3D=3D sizeof(tx_data)) + return 0; + else if (ret !=3D -EIO && ret !=3D -EREMOTEIO) + break; + } + + dev_err(&client->dev, "I2C write fail after %d retries, ret=3D%d\n", + retry, ret); + return -EIO; +} + +static int cef168_i2c_read(struct cef168_device *cef168_dev, + struct cef168_data *rx_data) +{ + struct i2c_client *client =3D v4l2_get_subdevdata(&cef168_dev->sd); + + int ret =3D i2c_master_recv(client, (char *)rx_data, + sizeof(struct cef168_data)); + if (ret !=3D sizeof(struct cef168_data)) { + dev_err(&client->dev, "I2C read fail, ret=3D%d\n", ret); + return -EIO; + } + + u8 computed_crc =3D crc8(cef168_crc8_table, (const u8 *)rx_data, + sizeof(struct cef168_data) - 1, CRC8_INIT_VALUE); + if (computed_crc !=3D rx_data->crc8) { + dev_err(&client->dev, + "CRC mismatch calculated=3D0x%02X read=3D0x%02X\n", + computed_crc, rx_data->crc8); + return -EIO; + } + + rx_data->moving_time =3D le16_to_cpup((__le16 *)&rx_data->moving_time); + rx_data->focus_position_min =3D le16_to_cpup((__le16 *)&rx_data->focus_po= sition_min); + rx_data->focus_position_max =3D le16_to_cpup((__le16 *)&rx_data->focus_po= sition_max); + rx_data->focus_position_cur =3D le16_to_cpup((__le16 *)&rx_data->focus_po= sition_cur); + rx_data->focus_distance_min =3D le16_to_cpup((__le16 *)&rx_data->focus_di= stance_min); + rx_data->focus_distance_max =3D le16_to_cpup((__le16 *)&rx_data->focus_di= stance_max); + + return 0; +} + +static int cef168_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct cef168_device *dev =3D to_cef168(ctrl); + u8 cmd; + + switch (ctrl->id) { + case V4L2_CID_FOCUS_ABSOLUTE: + return cef168_i2c_write(dev, INP_SET_FOCUS, ctrl->val); + case V4L2_CID_FOCUS_RELATIVE: + cmd =3D ctrl->val < 0 ? INP_SET_FOCUS_N : INP_SET_FOCUS_P; + return cef168_i2c_write(dev, cmd, abs(ctrl->val)); + case V4L2_CID_IRIS_ABSOLUTE: + return cef168_i2c_write(dev, INP_SET_APERTURE, ctrl->val); + case V4L2_CID_IRIS_RELATIVE: + cmd =3D ctrl->val < 0 ? INP_SET_APERTURE_N : INP_SET_APERTURE_P; + return cef168_i2c_write(dev, cmd, abs(ctrl->val)); + case CEF168_V4L2_CID_CUSTOM(calibrate): + return cef168_i2c_write(dev, INP_CALIBRATE, 0); + } + + return -EINVAL; +} + +static int cef168_get_ctrl(struct v4l2_ctrl *ctrl) +{ + struct cef168_device *dev =3D to_cef168(ctrl); + int rval; + + if (ctrl->id !=3D V4L2_CID_FOCUS_ABSOLUTE && + ctrl->id !=3D CEF168_V4L2_CID_CUSTOM(data) && + ctrl->id !=3D CEF168_V4L2_CID_CUSTOM(focus_range) && + ctrl->id !=3D CEF168_V4L2_CID_CUSTOM(lens_id)) + return -EINVAL; + + struct cef168_data data; + + rval =3D cef168_i2c_read(dev, &data); + if (rval < 0) + return rval; + + switch (ctrl->id) { + case V4L2_CID_FOCUS_ABSOLUTE: + ctrl->val =3D data.focus_position_cur; + return 0; + case CEF168_V4L2_CID_CUSTOM(focus_range): + ctrl->p_new.p_u32[0] =3D ((u32)data.focus_position_min << 16) | + (u32)data.focus_position_max; + return 0; + case CEF168_V4L2_CID_CUSTOM(lens_id): + ctrl->p_new.p_u8[0] =3D data.lens_id; + return 0; + case CEF168_V4L2_CID_CUSTOM(data): + memcpy(ctrl->p_new.p_u8, &data, sizeof(data)); + return 0; + } + + return -EINVAL; +} + +static const struct v4l2_ctrl_ops cef168_ctrl_ops =3D { + .g_volatile_ctrl =3D cef168_get_ctrl, + .s_ctrl =3D cef168_set_ctrl, +}; + +static const struct v4l2_ctrl_config cef168_lens_id_ctrl =3D { + .ops =3D &cef168_ctrl_ops, + .id =3D CEF168_V4L2_CID_CUSTOM(lens_id), + .type =3D V4L2_CTRL_TYPE_U8, + .name =3D "Lens ID", + .min =3D 0, + .max =3D U8_MAX, + .step =3D 1, + .def =3D 0, + .flags =3D V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, +}; + +static const struct v4l2_ctrl_config cef168_focus_range_ctrl =3D { + .ops =3D &cef168_ctrl_ops, + .id =3D CEF168_V4L2_CID_CUSTOM(focus_range), + .type =3D V4L2_CTRL_TYPE_U32, + .name =3D "Focus Range", + .min =3D 0, + .max =3D U32_MAX, + .step =3D 1, + .def =3D 0, + .flags =3D V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, +}; + +static const struct v4l2_ctrl_config cef168_data_ctrl =3D { + .ops =3D &cef168_ctrl_ops, + .id =3D CEF168_V4L2_CID_CUSTOM(data), + .type =3D V4L2_CTRL_TYPE_U8, + .name =3D "Data", + .min =3D 0, + .max =3D U8_MAX, + .step =3D 1, + .def =3D 0, + .dims =3D { sizeof(struct cef168_data) / sizeof(u8) }, + .elem_size =3D sizeof(u8), + .flags =3D V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, +}; + +static const struct v4l2_ctrl_config cef168_calibrate_ctrl =3D { + .ops =3D &cef168_ctrl_ops, + .id =3D CEF168_V4L2_CID_CUSTOM(calibrate), + .type =3D V4L2_CTRL_TYPE_BUTTON, + .name =3D "Calibrate", +}; + +static int cef168_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + return pm_runtime_resume_and_get(sd->dev); +} + +static int cef168_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + pm_runtime_put(sd->dev); + return 0; +} + +static const struct v4l2_subdev_internal_ops cef168_int_ops =3D { + .open =3D cef168_open, + .close =3D cef168_close, +}; + +static const struct v4l2_subdev_core_ops cef168_core_ops =3D { + .log_status =3D v4l2_ctrl_subdev_log_status, + .subscribe_event =3D v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event =3D v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops cef168_ops =3D { + .core =3D &cef168_core_ops, +}; + +static void cef168_subdev_cleanup(struct cef168_device *cef168_dev) +{ + v4l2_async_unregister_subdev(&cef168_dev->sd); + v4l2_ctrl_handler_free(&cef168_dev->ctrls); + media_entity_cleanup(&cef168_dev->sd.entity); +} + +static int cef168_init_controls(struct cef168_device *dev) +{ + struct v4l2_ctrl *ctrl; + struct v4l2_ctrl_handler *hdl =3D &dev->ctrls; + const struct v4l2_ctrl_ops *ops =3D &cef168_ctrl_ops; + + v4l2_ctrl_handler_init(hdl, 8); + + ctrl =3D v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, 0, S16_MAX, + 1, 0); + if (ctrl) + ctrl->flags |=3D V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_EXECUTE_ON_WRITE; + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_RELATIVE, S16_MIN, S16_MAX, + 1, 0); + ctrl =3D v4l2_ctrl_new_std(hdl, ops, V4L2_CID_IRIS_ABSOLUTE, 0, S16_MAX, + 1, 0); + if (ctrl) + ctrl->flags |=3D V4L2_CTRL_FLAG_WRITE_ONLY | + V4L2_CTRL_FLAG_EXECUTE_ON_WRITE; + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_IRIS_RELATIVE, S16_MIN, S16_MAX, 1, + 0); + v4l2_ctrl_new_custom(hdl, &cef168_calibrate_ctrl, NULL); + v4l2_ctrl_new_custom(hdl, &cef168_focus_range_ctrl, NULL); + v4l2_ctrl_new_custom(hdl, &cef168_data_ctrl, NULL); + v4l2_ctrl_new_custom(hdl, &cef168_lens_id_ctrl, NULL); + + if (hdl->error) + dev_err(dev->sd.dev, "%s fail error: 0x%x\n", __func__, + hdl->error); + dev->sd.ctrl_handler =3D hdl; + return hdl->error; +} + +static int cef168_probe(struct i2c_client *client) +{ + struct cef168_device *cef168_dev; + int rval; + + cef168_dev =3D devm_kzalloc(&client->dev, sizeof(*cef168_dev), + GFP_KERNEL); + if (!cef168_dev) + return -ENOMEM; + + v4l2_i2c_subdev_init(&cef168_dev->sd, client, &cef168_ops); + cef168_dev->sd.flags |=3D V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + cef168_dev->sd.internal_ops =3D &cef168_int_ops; + + rval =3D cef168_init_controls(cef168_dev); + if (rval) + goto err_cleanup; + + rval =3D media_entity_pads_init(&cef168_dev->sd.entity, 0, NULL); + if (rval < 0) + goto err_cleanup; + + cef168_dev->sd.entity.function =3D MEDIA_ENT_F_LENS; + + rval =3D v4l2_async_register_subdev(&cef168_dev->sd); + if (rval < 0) + goto err_cleanup; + + crc8_populate_msb(cef168_crc8_table, CEF_CRC8_POLYNOMIAL); + + pm_runtime_set_active(&client->dev); + pm_runtime_enable(&client->dev); + pm_runtime_idle(&client->dev); + + return 0; + +err_cleanup: + v4l2_ctrl_handler_free(&cef168_dev->ctrls); + media_entity_cleanup(&cef168_dev->sd.entity); + + return rval; +} + +static void cef168_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd =3D i2c_get_clientdata(client); + struct cef168_device *cef168_dev =3D sd_to_cef168(sd); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + cef168_subdev_cleanup(cef168_dev); +} + +static const struct of_device_id cef168_of_table[] =3D { + { .compatible =3D "pinefeat,cef168" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cef168_of_table); + +static struct i2c_driver cef168_i2c_driver =3D { + .driver =3D { + .name =3D CEF168_NAME, + .of_match_table =3D cef168_of_table, + }, + .probe =3D cef168_probe, + .remove =3D cef168_remove, +}; + +module_i2c_driver(cef168_i2c_driver); + +MODULE_AUTHOR("support@pinefeat.co.uk>"); +MODULE_DESCRIPTION("CEF168 lens driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/i2c/cef168.h b/drivers/media/i2c/cef168.h new file mode 100644 index 000000000000..cdce1a19bda0 --- /dev/null +++ b/drivers/media/i2c/cef168.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Pinefeat cef168 lens driver + * + * Copyright (c) 2025 Pinefeat LLP + */ + +#ifndef CEF168_CEF168_H +#define CEF168_CEF168_H + +#define CEF168_NAME "cef168" + +#define CEF168_V4L2_CID_CUSTOM(ctrl) \ + ((V4L2_CID_USER_BASE | 168) + custom_##ctrl) + +enum { custom_lens_id, custom_data, custom_focus_range, custom_calibrate }; + +/** + * cef168 data structure + */ +struct cef168_data { + __u8 lens_id; + __u8 moving : 1; + __u8 calibrating : 2; + __u16 moving_time; + __u16 focus_position_min; + __u16 focus_position_max; + __u16 focus_position_cur; + __u16 focus_distance_min; + __u16 focus_distance_max; + __u8 crc8; +} __packed; + +/* + * cef168 I2C protocol commands + */ +#define INP_CALIBRATE 0x22 +#define INP_SET_FOCUS 0x80 +#define INP_SET_FOCUS_P 0x81 +#define INP_SET_FOCUS_N 0x82 +#define INP_SET_APERTURE 0x7A +#define INP_SET_APERTURE_P 0x7B +#define INP_SET_APERTURE_N 0x7C + +#define CEF_CRC8_POLYNOMIAL 168 + +#ifdef DECLARE_CRC8_TABLE +DECLARE_CRC8_TABLE(cef168_crc8_table); +#endif + +#endif //CEF168_CEF168_H --=20 2.34.1