From nobody Sat Oct 4 12:41:18 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