From nobody Sun Oct 5 01:51:58 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 758292E336F for ; Mon, 11 Aug 2025 21:31:41 +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=1754947903; cv=none; b=HRX5ms1IZLEO3MJ5isva2AgWESVZVCqpMiUJjtg6Er1R+ObdH3Dd27CRtkDkKWugsiU1MMhhh0wFFF56Xqi4stcUpOeDgm7jLBJ1+FZJ5mTUUAIbttvlX59UNIfQcREMUcucJwDySmPIkkYOAShOTGofZsXdkc8p8K2bB2r7QnM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1754947903; c=relaxed/simple; bh=ffr9SzJa7cXbTumlucNTIkWUH5ssDXBeVH00KWVYk54=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=GFZSZx1G9XQxy1DTUJzsJeoJYrG0o9Nf0/8BtjWkESlIsBM0/JzKR1Gk2vvqatPGxZWlHIsGDV18/cMayP4EHWG4J2Iiiw8yk4aCQFqbw2+FvSwoTLbzUJv4ZPUO7qpxvK+lYypdnIOVHvQ5vxryTkRZriDjkokQbF1MZlpgez0= 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=cRkwjqFi; 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="cRkwjqFi" Received: by mail-wr1-f50.google.com with SMTP id ffacd0b85a97d-3b8d0f1fb49so2883632f8f.2 for ; Mon, 11 Aug 2025 14:31:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pinefeat.co.uk; s=google; t=1754947900; x=1755552700; 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=v2fhfRn8Bvae5tiQX9dls8GG8o2lN99or21sd4P+jR4=; b=cRkwjqFiMbPsgKx1L+mNQsie2JBllnbyISsx5q+cBbCWDy3peeeDXyBeGqiCWbJy2h SbMcUBIJUl076AxX23GszliSEGSS27KCsZPU46nhQ9uEklsbsYkKkCg28SPlEccpD6TC zmrPK8f1rMRths6vwera18OACTIPt001m+evHJsSlBCylyMS4gNM/rue7HcbQ7AVvzOa wkpaC6KLZK5uphr/S1/ROLRD7kFX0X6uFSNPzXr1QjR7Aht0rruRgQvPnz3b2Q1ovznk pdLF2HEZKGHHRXd+hgLyrsPyaZyr7ypCz4PeMs2/PfLjNx/vEmLEGUM973TJ7TTMod8O lShg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1754947900; x=1755552700; 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=v2fhfRn8Bvae5tiQX9dls8GG8o2lN99or21sd4P+jR4=; b=jSNV0/gLu5maGXsUWLIAujIptTDOLeg/Ou+R8jIcZuKNis+CBycl+rwVGO8/Wd/4Zf JIzgGB23L/Ouh8x87rEY9JcOrylDraN4BjLo6/4UD6wIyW1lsA5sWwo++59R//4GMfOM Mo5pGjYsV++I3IxeyMM4GfoODtUFnFnXJDmWucbBHgiCbfbNKdXomrGFcz68fL9rVSBi 53Z3bbYkqen5UbgNwEge88D+l69RAK6ggRqE2rApIffR6Ls8HWK8Z8IuW+pkfbLmvg3f x+dQiUAVaSAAAiAycBrZ1EAHfBkFdR9i/nXXcGn45OtWcI7zLl9dTxIkxDh9rjijgthg zSGw== X-Forwarded-Encrypted: i=1; AJvYcCXEb74qx4tTAr9iotZ/tY78Z8fpXzTDhxQQ5cRAA5f8WGXs+cw1L64yPDy/WcFruf4wHyZ6GB6XwrFn8Ak=@vger.kernel.org X-Gm-Message-State: AOJu0YyvS8yBZztxGpRIIjxhcR1rFDhYXAGphQhqylH+mVfT0JdaLYPP gfX9//JjKbAWDKPCo2lOBbWaHuHXAxVIwv0EHmTfgigOX+kmPdOpv53y+Y8vps15KQ8= X-Gm-Gg: ASbGncu6RlI5quIK7XGTVpSPq77yLj5e+fgLSpiXwDNbKF9b/rGqCaJAZpv/brEvroR kc1tzkk0vNHgl0xH1mHug0b5iOSV8EWFhIzRkn7GIbccIHvskeDcg41z+BAHurV14zLbgULLNV4 DCyiCEqnjIrZcIR3vKJzhsnmykypXX6Y8Q7Ii0yimBARHSEs2z6AenL0fzHdQb/hf7KTRrIMUul DbFBWuLRue3JlyS7mFGF//J6VfMeyAWHtfuiC5bCqRchEg6PqWOGQAPZFQRAAPPnh9hvD98Aa5q ac46MuDufPYYSx6W3Ul5dUjGR8t2hR/B1hGcqa6veO/MxabCn10knEnraNP3L9QwTiwFAoRBQsT vN+VqL3f05UksKUQs0tNKRFGx/AHQdB9f3b8ctnxY72iDz7a9 X-Google-Smtp-Source: AGHT+IG+iqBMbGzEpeHof0rjboPnFgFM0XgBYxPcKIogUeosDobZmZLrTnnogTdoV+nZLZHtgKFrEA== X-Received: by 2002:a05:6000:2911:b0:3b7:8d70:ed05 with SMTP id ffacd0b85a97d-3b910fdb8fcmr1083173f8f.5.1754947899632; Mon, 11 Aug 2025 14:31:39 -0700 (PDT) Received: from asmirnov-G751JM.Home ([2a02:c7c:b28c:1f00:f8cb:3493:2eed:8d11]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3b79c485444sm42397387f8f.66.2025.08.11.14.31.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 11 Aug 2025 14:31:39 -0700 (PDT) From: Alexander Smirnov X-Google-Original-From: Alexander Smirnov 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 v2 2/2] media/i2c: Pinefeat cef168 lens control board driver Date: Mon, 11 Aug 2025 22:31:02 +0100 Message-Id: <20250811213102.15703-3-aliaksandr.smirnou@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250811213102.15703-1-aliaksandr.smirnou@gmail.com> References: <20250811213102.15703-1-aliaksandr.smirnou@gmail.com> 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" From: Aliaksandr Smirnou 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 | 337 +++++++++++++++++++++++++++++++++++++ drivers/media/i2c/cef168.h | 51 ++++++ 5 files changed, 399 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..db3f97e7e9dc --- /dev/null +++ b/drivers/media/i2c/cef168.c @@ -0,0 +1,337 @@ +// 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; + + val =3D cpu_to_le16(val); + char tx_data[4] =3D { cmd, (val & 0xff), (val >> 8) }; + + 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_cpu(rx_data->moving_time); + rx_data->focus_position_min =3D le16_to_cpu(rx_data->focus_position_min); + rx_data->focus_position_max =3D le16_to_cpu(rx_data->focus_position_max); + rx_data->focus_position_cur =3D le16_to_cpu(rx_data->focus_position_cur); + rx_data->focus_distance_min =3D le16_to_cpu(rx_data->focus_distance_min); + rx_data->focus_distance_max =3D le16_to_cpu(rx_data->focus_distance_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 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)le32_to_cpu(((u32)data.focus_position_min << 16) | + 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 =3D=3D NULL) + 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