* [PATCH v3 0/2] Pinefeat cef168 lens control board driver @ 2025-08-17 13:05 Aliaksandr Smirnou 2025-08-17 13:05 ` [PATCH v3 1/2] dt-bindings: Pinefeat cef168 lens control board Aliaksandr Smirnou 2025-08-17 13:05 ` [PATCH v3 2/2] media: i2c: Pinefeat cef168 lens control board driver Aliaksandr Smirnou 0 siblings, 2 replies; 10+ messages in thread From: Aliaksandr Smirnou @ 2025-08-17 13:05 UTC (permalink / raw) To: mchehab, robh, krzk+dt, conor+dt Cc: devicetree, linux-media, linux-kernel, Aliaksandr Smirnou This patch series adds support for the Pinefeat adapter, which interfaces Canon EF and EF-S lenses to non-Canon camera bodies. The cef168 circuit control board provides an I2C interface for electronic focus and aperture control. The driver integrates with the V4L2 sub-device API. For more information about the product, see: https://github.com/pinefeat/cef168 Changes in v3: - removed vcc-supply property and example - fixed incorrect type in assignment - fixed cast to restricted - removed unreachable code - changed comparison to NULL - fixed indent in commit message Link to v2: https://lore.kernel.org/all/20250811213102.15703-1-aliaksandr.smirnou@gmail.com/ Patches: dt-bindings: Pinefeat cef168 lens control board media: i2c: Pinefeat cef168 lens control board driver .../bindings/media/i2c/pinefeat,cef168.yaml | 48 +++ .../devicetree/bindings/vendor-prefixes.yaml | 2 + MAINTAINERS | 8 + drivers/media/i2c/Kconfig | 8 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/cef168.c | 335 ++++++++++++++++++ drivers/media/i2c/cef168.h | 51 +++ 7 files changed, 453 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/pinefeat,cef168.yaml create mode 100644 drivers/media/i2c/cef168.c create mode 100644 drivers/media/i2c/cef168.h base-commit: 2b38afce25c4e1b8f943ff4f0a2b51d6c40f2ed2 -- 2.34.1 ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 1/2] dt-bindings: Pinefeat cef168 lens control board 2025-08-17 13:05 [PATCH v3 0/2] Pinefeat cef168 lens control board driver Aliaksandr Smirnou @ 2025-08-17 13:05 ` Aliaksandr Smirnou 2025-08-18 17:36 ` Conor Dooley 2025-08-17 13:05 ` [PATCH v3 2/2] media: i2c: Pinefeat cef168 lens control board driver Aliaksandr Smirnou 1 sibling, 1 reply; 10+ messages in thread From: Aliaksandr Smirnou @ 2025-08-17 13:05 UTC (permalink / raw) To: mchehab, robh, krzk+dt, conor+dt Cc: devicetree, linux-media, linux-kernel, Aliaksandr Smirnou 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 <support@pinefeat.co.uk> --- .../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,cef168.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/pinefeat,cef168.yaml 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 <support@pinefeat.co.uk> + +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 = <1>; + #size-cells = <0>; + + camera-lens@d { + compatible = "pinefeat,cef168"; + reg = <0x0d>; + }; + }; + +... diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/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 +PINEFEAT CEF168 LENS DRIVER +M: Aliaksandr Smirnou <support@pinefeat.co.uk> +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 <tduszyns@gmail.com> S: Maintained -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: Pinefeat cef168 lens control board 2025-08-17 13:05 ` [PATCH v3 1/2] dt-bindings: Pinefeat cef168 lens control board Aliaksandr Smirnou @ 2025-08-18 17:36 ` Conor Dooley 2025-08-18 19:29 ` Aliaksandr Smirnou 2025-08-19 8:21 ` Krzysztof Kozlowski 0 siblings, 2 replies; 10+ messages in thread From: Conor Dooley @ 2025-08-18 17:36 UTC (permalink / raw) To: Aliaksandr Smirnou Cc: mchehab, robh, krzk+dt, conor+dt, devicetree, linux-media, linux-kernel [-- Attachment #1: Type: text/plain, Size: 2511 bytes --] On Sun, Aug 17, 2025 at 02:05:48PM +0100, Aliaksandr Smirnou wrote: > 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. Wut? This doesn't make sense, you have supplies so they should be documented. The fact that they're shared with a bunch of other things on the SBC you're aiming the product at doesn't matter. What if someone doesn't use this sensor with an RPi and there is a dedicated regulator? > Signed-off-by: Aliaksandr Smirnou <support@pinefeat.co.uk> Is that actually your email address? > --- > .../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,cef168.yaml > > diff --git a/Documentation/devicetree/bindings/media/i2c/pinefeat,cef168.yaml 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 <support@pinefeat.co.uk> > + > +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. Don't mention drivers in bindings, how linux handles things is not relevant to a hardware description. [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 228 bytes --] ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: Pinefeat cef168 lens control board 2025-08-18 17:36 ` Conor Dooley @ 2025-08-18 19:29 ` Aliaksandr Smirnou 2025-08-19 8:21 ` Krzysztof Kozlowski 1 sibling, 0 replies; 10+ messages in thread From: Aliaksandr Smirnou @ 2025-08-18 19:29 UTC (permalink / raw) To: conor Cc: conor+dt, devicetree, krzk+dt, linux-kernel, linux-media, mchehab, robh, support [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #1: Type: text/plain; charset=yes, Size: 871 bytes --] On Mon, 18 Aug 2025 18:36:50 +0100, Conor Dooley wrote: > Wut? This doesn't make sense, you have supplies so they should be > documented. The fact that they're shared with a bunch of other things on > the SBC you're aiming the product at doesn't matter. What if someone > doesn't use this sensor with an RPi and there is a dedicated regulator? The supply was introduced in the second patch version but later removed following feedback from another maintainer. I’m fine with adding it back if needed - I just want to make sure we’re consistent. https://lore.kernel.org/all/c1b848b9-b1da-4976-9838-d474394a0992@kernel.org/ Would you prefer it described as before? > Is that actually your email address? will change to a named one > Don't mention drivers in bindings, how linux handles things is not relevant to a hardware description. will remove driver mention ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 1/2] dt-bindings: Pinefeat cef168 lens control board 2025-08-18 17:36 ` Conor Dooley 2025-08-18 19:29 ` Aliaksandr Smirnou @ 2025-08-19 8:21 ` Krzysztof Kozlowski 1 sibling, 0 replies; 10+ messages in thread From: Krzysztof Kozlowski @ 2025-08-19 8:21 UTC (permalink / raw) To: Conor Dooley Cc: Aliaksandr Smirnou, mchehab, robh, krzk+dt, conor+dt, devicetree, linux-media, linux-kernel On Mon, Aug 18, 2025 at 06:36:50PM +0100, Conor Dooley wrote: > On Sun, Aug 17, 2025 at 02:05:48PM +0100, Aliaksandr Smirnou wrote: > > 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. > > Wut? This doesn't make sense, you have supplies so they should be > documented. The fact that they're shared with a bunch of other things on > the SBC you're aiming the product at doesn't matter. What if someone There is also some explanation at v2 discussion. I asked for that because there is no known design (neither RPi or other boards having compatible hat/connectors) which would have these supplies controllable. Adding them now would mean you should make them also required (because in fact they are), but since these are non-controllable there would be just regulator-fixed with voltage and that's it. It's just bloat. > doesn't use this sensor with an RPi and there is a dedicated regulator? Unlikely but if ever such hardware appears, we can always add regulators. Best regards, Krzysztof ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 2/2] media: i2c: Pinefeat cef168 lens control board driver 2025-08-17 13:05 [PATCH v3 0/2] Pinefeat cef168 lens control board driver Aliaksandr Smirnou 2025-08-17 13:05 ` [PATCH v3 1/2] dt-bindings: Pinefeat cef168 lens control board Aliaksandr Smirnou @ 2025-08-17 13:05 ` Aliaksandr Smirnou 2025-08-19 13:47 ` Jacopo Mondi 1 sibling, 1 reply; 10+ messages in thread From: Aliaksandr Smirnou @ 2025-08-17 13:05 UTC (permalink / raw) To: mchehab, robh, krzk+dt, conor+dt Cc: devicetree, linux-media, linux-kernel, 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 <support@pinefeat.co.uk> --- 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 <support@pinefeat.co.uk> 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 PLANTOWER PMS7003 AIR POLLUTION SENSOR DRIVER M: Tomasz Duszynski <tduszyns@gmail.com> 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. +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) += bt856.o obj-$(CONFIG_VIDEO_BT866) += bt866.o obj-$(CONFIG_VIDEO_CCS) += ccs/ obj-$(CONFIG_VIDEO_CCS_PLL) += ccs-pll.o +obj-$(CONFIG_VIDEO_CEF168) += cef168.o obj-$(CONFIG_VIDEO_CS3308) += cs3308.o obj-$(CONFIG_VIDEO_CS5345) += cs5345.o obj-$(CONFIG_VIDEO_CS53L32A) += 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 <linux/crc8.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-event.h> +#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 *subdev) +{ + 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 = v4l2_get_subdevdata(&cef168_dev->sd); + int retry, ret; + + __le16 le_data = cpu_to_le16(val); + char tx_data[4] = { cmd, ((u8 *)&le_data)[0], ((u8 *)&le_data)[1] }; + + tx_data[3] = crc8(cef168_crc8_table, tx_data, 3, CRC8_INIT_VALUE); + + for (retry = 0; retry < 3; retry++) { + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); + if (ret == sizeof(tx_data)) + return 0; + else if (ret != -EIO && ret != -EREMOTEIO) + break; + } + + dev_err(&client->dev, "I2C write fail after %d retries, ret=%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 = v4l2_get_subdevdata(&cef168_dev->sd); + + int ret = i2c_master_recv(client, (char *)rx_data, + sizeof(struct cef168_data)); + if (ret != sizeof(struct cef168_data)) { + dev_err(&client->dev, "I2C read fail, ret=%d\n", ret); + return -EIO; + } + + u8 computed_crc = crc8(cef168_crc8_table, (const u8 *)rx_data, + sizeof(struct cef168_data) - 1, CRC8_INIT_VALUE); + if (computed_crc != rx_data->crc8) { + dev_err(&client->dev, + "CRC mismatch calculated=0x%02X read=0x%02X\n", + computed_crc, rx_data->crc8); + return -EIO; + } + + rx_data->moving_time = le16_to_cpup((__le16 *)&rx_data->moving_time); + rx_data->focus_position_min = le16_to_cpup((__le16 *)&rx_data->focus_position_min); + rx_data->focus_position_max = le16_to_cpup((__le16 *)&rx_data->focus_position_max); + rx_data->focus_position_cur = le16_to_cpup((__le16 *)&rx_data->focus_position_cur); + rx_data->focus_distance_min = le16_to_cpup((__le16 *)&rx_data->focus_distance_min); + rx_data->focus_distance_max = le16_to_cpup((__le16 *)&rx_data->focus_distance_max); + + return 0; +} + +static int cef168_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct cef168_device *dev = 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 = 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 = 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 = to_cef168(ctrl); + int rval; + + if (ctrl->id != V4L2_CID_FOCUS_ABSOLUTE && + ctrl->id != CEF168_V4L2_CID_CUSTOM(data) && + ctrl->id != CEF168_V4L2_CID_CUSTOM(focus_range) && + ctrl->id != CEF168_V4L2_CID_CUSTOM(lens_id)) + return -EINVAL; + + struct cef168_data data; + + rval = cef168_i2c_read(dev, &data); + if (rval < 0) + return rval; + + switch (ctrl->id) { + case V4L2_CID_FOCUS_ABSOLUTE: + ctrl->val = data.focus_position_cur; + return 0; + case CEF168_V4L2_CID_CUSTOM(focus_range): + ctrl->p_new.p_u32[0] = ((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] = 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 = { + .g_volatile_ctrl = cef168_get_ctrl, + .s_ctrl = cef168_set_ctrl, +}; + +static const struct v4l2_ctrl_config cef168_lens_id_ctrl = { + .ops = &cef168_ctrl_ops, + .id = CEF168_V4L2_CID_CUSTOM(lens_id), + .type = V4L2_CTRL_TYPE_U8, + .name = "Lens ID", + .min = 0, + .max = U8_MAX, + .step = 1, + .def = 0, + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, +}; + +static const struct v4l2_ctrl_config cef168_focus_range_ctrl = { + .ops = &cef168_ctrl_ops, + .id = CEF168_V4L2_CID_CUSTOM(focus_range), + .type = V4L2_CTRL_TYPE_U32, + .name = "Focus Range", + .min = 0, + .max = U32_MAX, + .step = 1, + .def = 0, + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, +}; + +static const struct v4l2_ctrl_config cef168_data_ctrl = { + .ops = &cef168_ctrl_ops, + .id = CEF168_V4L2_CID_CUSTOM(data), + .type = V4L2_CTRL_TYPE_U8, + .name = "Data", + .min = 0, + .max = U8_MAX, + .step = 1, + .def = 0, + .dims = { sizeof(struct cef168_data) / sizeof(u8) }, + .elem_size = sizeof(u8), + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, +}; + +static const struct v4l2_ctrl_config cef168_calibrate_ctrl = { + .ops = &cef168_ctrl_ops, + .id = CEF168_V4L2_CID_CUSTOM(calibrate), + .type = V4L2_CTRL_TYPE_BUTTON, + .name = "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 = { + .open = cef168_open, + .close = cef168_close, +}; + +static const struct v4l2_subdev_core_ops cef168_core_ops = { + .log_status = v4l2_ctrl_subdev_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops cef168_ops = { + .core = &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 = &dev->ctrls; + const struct v4l2_ctrl_ops *ops = &cef168_ctrl_ops; + + v4l2_ctrl_handler_init(hdl, 8); + + ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, 0, S16_MAX, + 1, 0); + if (ctrl) + ctrl->flags |= 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 = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_IRIS_ABSOLUTE, 0, S16_MAX, + 1, 0); + if (ctrl) + ctrl->flags |= 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 = hdl; + return hdl->error; +} + +static int cef168_probe(struct i2c_client *client) +{ + struct cef168_device *cef168_dev; + int rval; + + cef168_dev = 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 |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + cef168_dev->sd.internal_ops = &cef168_int_ops; + + rval = cef168_init_controls(cef168_dev); + if (rval) + goto err_cleanup; + + rval = media_entity_pads_init(&cef168_dev->sd.entity, 0, NULL); + if (rval < 0) + goto err_cleanup; + + cef168_dev->sd.entity.function = MEDIA_ENT_F_LENS; + + rval = 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 = i2c_get_clientdata(client); + struct cef168_device *cef168_dev = 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[] = { + { .compatible = "pinefeat,cef168" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cef168_of_table); + +static struct i2c_driver cef168_i2c_driver = { + .driver = { + .name = CEF168_NAME, + .of_match_table = cef168_of_table, + }, + .probe = cef168_probe, + .remove = 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 -- 2.34.1 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/2] media: i2c: Pinefeat cef168 lens control board driver 2025-08-17 13:05 ` [PATCH v3 2/2] media: i2c: Pinefeat cef168 lens control board driver Aliaksandr Smirnou @ 2025-08-19 13:47 ` Jacopo Mondi 2025-08-19 21:32 ` Aliaksandr Smirnou 0 siblings, 1 reply; 10+ messages in thread From: Jacopo Mondi @ 2025-08-19 13:47 UTC (permalink / raw) To: Aliaksandr Smirnou Cc: mchehab, robh, krzk+dt, conor+dt, devicetree, linux-media, linux-kernel Hi Aliaksandr thanks for the patch just a quick review, I'm not a v4l2-control expert so you might want to wait for more reviews on that part.. On Sun, Aug 17, 2025 at 02:05:49PM +0100, Aliaksandr Smirnou wrote: > 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 <support@pinefeat.co.uk> > --- > 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 <support@pinefeat.co.uk> > 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 > > PLANTOWER PMS7003 AIR POLLUTION SENSOR DRIVER > M: Tomasz Duszynski <tduszyns@gmail.com> > 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. > > +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) += bt856.o > obj-$(CONFIG_VIDEO_BT866) += bt866.o > obj-$(CONFIG_VIDEO_CCS) += ccs/ > obj-$(CONFIG_VIDEO_CCS_PLL) += ccs-pll.o > +obj-$(CONFIG_VIDEO_CEF168) += cef168.o > obj-$(CONFIG_VIDEO_CS3308) += cs3308.o > obj-$(CONFIG_VIDEO_CS5345) += cs5345.o > obj-$(CONFIG_VIDEO_CS53L32A) += 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 <linux/crc8.h> Do you need to select CRC8 in Kconfig then ? > +#include <linux/delay.h> > +#include <linux/i2c.h> > +#include <linux/module.h> > +#include <linux/pm_runtime.h> > +#include <media/v4l2-ctrls.h> > +#include <media/v4l2-device.h> > +#include <media/v4l2-event.h> > +#include "cef168.h" Why an header file ? This isn't going to be included by any other .c file, right ? > + > +/* > + * 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 *subdev) > +{ > + 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 = v4l2_get_subdevdata(&cef168_dev->sd); > + int retry, ret; > + > + __le16 le_data = cpu_to_le16(val); > + char tx_data[4] = { cmd, ((u8 *)&le_data)[0], ((u8 *)&le_data)[1] }; > + > + tx_data[3] = crc8(cef168_crc8_table, tx_data, 3, CRC8_INIT_VALUE); > + > + for (retry = 0; retry < 3; retry++) { This seems a bit random, why do you need to retry three times ? > + ret = i2c_master_send(client, tx_data, sizeof(tx_data)); > + if (ret == sizeof(tx_data)) > + return 0; > + else if (ret != -EIO && ret != -EREMOTEIO) > + break; > + } > + > + dev_err(&client->dev, "I2C write fail after %d retries, ret=%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 = v4l2_get_subdevdata(&cef168_dev->sd); > + > + int ret = i2c_master_recv(client, (char *)rx_data, > + sizeof(struct cef168_data)); > + if (ret != sizeof(struct cef168_data)) { > + dev_err(&client->dev, "I2C read fail, ret=%d\n", ret); > + return -EIO; > + } > + > + u8 computed_crc = crc8(cef168_crc8_table, (const u8 *)rx_data, > + sizeof(struct cef168_data) - 1, CRC8_INIT_VALUE); > + if (computed_crc != rx_data->crc8) { > + dev_err(&client->dev, > + "CRC mismatch calculated=0x%02X read=0x%02X\n", > + computed_crc, rx_data->crc8); > + return -EIO; > + } > + > + rx_data->moving_time = le16_to_cpup((__le16 *)&rx_data->moving_time); > + rx_data->focus_position_min = le16_to_cpup((__le16 *)&rx_data->focus_position_min); > + rx_data->focus_position_max = le16_to_cpup((__le16 *)&rx_data->focus_position_max); > + rx_data->focus_position_cur = le16_to_cpup((__le16 *)&rx_data->focus_position_cur); > + rx_data->focus_distance_min = le16_to_cpup((__le16 *)&rx_data->focus_distance_min); > + rx_data->focus_distance_max = le16_to_cpup((__le16 *)&rx_data->focus_distance_max); > + > + return 0; > +} > + > +static int cef168_set_ctrl(struct v4l2_ctrl *ctrl) > +{ > + struct cef168_device *dev = 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 = 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 = 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 = to_cef168(ctrl); > + int rval; > + > + if (ctrl->id != V4L2_CID_FOCUS_ABSOLUTE && > + ctrl->id != CEF168_V4L2_CID_CUSTOM(data) && > + ctrl->id != CEF168_V4L2_CID_CUSTOM(focus_range) && > + ctrl->id != CEF168_V4L2_CID_CUSTOM(lens_id)) > + return -EINVAL; If you mark them WRITE_ONLY wouldn't the core take care of this ? > + > + struct cef168_data data; I thought the compiler would complain for variables declared not at the beginning of the function > + > + rval = cef168_i2c_read(dev, &data); > + if (rval < 0) > + return rval; > + > + switch (ctrl->id) { > + case V4L2_CID_FOCUS_ABSOLUTE: > + ctrl->val = data.focus_position_cur; > + return 0; > + case CEF168_V4L2_CID_CUSTOM(focus_range): > + ctrl->p_new.p_u32[0] = ((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] = 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 = { > + .g_volatile_ctrl = cef168_get_ctrl, > + .s_ctrl = cef168_set_ctrl, > +}; > + > +static const struct v4l2_ctrl_config cef168_lens_id_ctrl = { > + .ops = &cef168_ctrl_ops, > + .id = CEF168_V4L2_CID_CUSTOM(lens_id), > + .type = V4L2_CTRL_TYPE_U8, > + .name = "Lens ID", > + .min = 0, > + .max = U8_MAX, > + .step = 1, > + .def = 0, > + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, > +}; > + > +static const struct v4l2_ctrl_config cef168_focus_range_ctrl = { > + .ops = &cef168_ctrl_ops, > + .id = CEF168_V4L2_CID_CUSTOM(focus_range), > + .type = V4L2_CTRL_TYPE_U32, > + .name = "Focus Range", > + .min = 0, > + .max = U32_MAX, > + .step = 1, > + .def = 0, > + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, > +}; > + > +static const struct v4l2_ctrl_config cef168_data_ctrl = { > + .ops = &cef168_ctrl_ops, > + .id = CEF168_V4L2_CID_CUSTOM(data), > + .type = V4L2_CTRL_TYPE_U8, > + .name = "Data", > + .min = 0, > + .max = U8_MAX, > + .step = 1, > + .def = 0, > + .dims = { sizeof(struct cef168_data) / sizeof(u8) }, > + .elem_size = sizeof(u8), > + .flags = V4L2_CTRL_FLAG_VOLATILE | V4L2_CTRL_FLAG_READ_ONLY, > +}; > + > +static const struct v4l2_ctrl_config cef168_calibrate_ctrl = { > + .ops = &cef168_ctrl_ops, > + .id = CEF168_V4L2_CID_CUSTOM(calibrate), > + .type = V4L2_CTRL_TYPE_BUTTON, > + .name = "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 = { > + .open = cef168_open, > + .close = cef168_close, > +}; > + > +static const struct v4l2_subdev_core_ops cef168_core_ops = { > + .log_status = v4l2_ctrl_subdev_log_status, > + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, > + .unsubscribe_event = v4l2_event_subdev_unsubscribe, > +}; > + > +static const struct v4l2_subdev_ops cef168_ops = { > + .core = &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 = &dev->ctrls; > + const struct v4l2_ctrl_ops *ops = &cef168_ctrl_ops; > + > + v4l2_ctrl_handler_init(hdl, 8); > + > + ctrl = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE, 0, S16_MAX, > + 1, 0); > + if (ctrl) > + ctrl->flags |= 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 = v4l2_ctrl_new_std(hdl, ops, V4L2_CID_IRIS_ABSOLUTE, 0, S16_MAX, > + 1, 0); > + if (ctrl) > + ctrl->flags |= 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 = hdl; > + return hdl->error; > +} > + > +static int cef168_probe(struct i2c_client *client) > +{ > + struct cef168_device *cef168_dev; > + int rval; > + > + cef168_dev = 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 |= V4L2_SUBDEV_FL_HAS_DEVNODE | > + V4L2_SUBDEV_FL_HAS_EVENTS; > + cef168_dev->sd.internal_ops = &cef168_int_ops; > + > + rval = cef168_init_controls(cef168_dev); > + if (rval) > + goto err_cleanup; > + > + rval = media_entity_pads_init(&cef168_dev->sd.entity, 0, NULL); > + if (rval < 0) > + goto err_cleanup; > + > + cef168_dev->sd.entity.function = MEDIA_ENT_F_LENS; > + > + rval = 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); Is the device powered up at this point ? > + pm_runtime_enable(&client->dev); > + pm_runtime_idle(&client->dev); If you depend on the pm_runtime_resume_and_get() call in open() to power the device up, then you need to depend on PM in KConfig ? > + > + 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 = i2c_get_clientdata(client); > + struct cef168_device *cef168_dev = 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[] = { > + { .compatible = "pinefeat,cef168" }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(of, cef168_of_table); > + > +static struct i2c_driver cef168_i2c_driver = { > + .driver = { > + .name = CEF168_NAME, > + .of_match_table = cef168_of_table, > + }, > + .probe = cef168_probe, > + .remove = 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) I think you need to reserve space for your controls in include/uapi/linux/v4l2-controls.h otherwise this will never be visible to applications ? > + > +enum { custom_lens_id, custom_data, custom_focus_range, custom_calibrate }; > + > +/** No need to kerneldoc unless you properly document all fields and include the file in some of the Documentation/ > + * 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 > -- > 2.34.1 > > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/2] media: i2c: Pinefeat cef168 lens control board driver 2025-08-19 13:47 ` Jacopo Mondi @ 2025-08-19 21:32 ` Aliaksandr Smirnou 2025-08-20 12:56 ` Jacopo Mondi 0 siblings, 1 reply; 10+ messages in thread From: Aliaksandr Smirnou @ 2025-08-19 21:32 UTC (permalink / raw) To: jacopo.mondi Cc: conor+dt, devicetree, krzk+dt, linux-kernel, linux-media, mchehab, robh, support Hi Jacopo, Thank you for the review. Your remarks are very helpful. While I'll apply most of them, could you clarify the one regarding pm_runtime_ for me? On Tue, 19 Aug 2025 15:47:54 +0200, Jacopo Mondi wrote: > > +#include <linux/crc8.h> > > Do you need to "select CRC8" in Kconfig then ? Yes, I'll include it. > > +#include "cef168.h" > > Why an header file ? Ok, I'll remove the header moving everying in the .c file. > > + for (retry = 0; retry < 3; retry++) { > > This seems a bit random, why do you need to retry three times ? The driver retries writes to work around an issue in the Raspberry Pi's I2C hardware, where the BCM2835 mishandles clock stretching. When the slave stretches the clock, the Pi can misread the SCL line or sample data too early, making it think the write failed. To improve reliability, the kernel driver automatically retries the write, effectively compensating for the hardware's timing bug. > > + ctrl->id != CEF168_V4L2_CID_CUSTOM(data) && > > + ctrl->id != CEF168_V4L2_CID_CUSTOM(focus_range) && > > + ctrl->id != CEF168_V4L2_CID_CUSTOM(lens_id)) > > + return -EINVAL; > > If you mark them WRITE_ONLY wouldn't the core take care of this ? These controls are read-only. The data they return depens on the lens. > > + struct cef168_data data; > > I thought the compiler would complain for variables declared not at > the beginning of the function Ok, I'll move the variable at the beginning. > > + pm_runtime_set_active(&client->dev); > > Is the device powered up at this point ? > If you depend on the pm_runtime_resume_and_get() call in open() to > power the device up, then you need to depend on PM in KConfig ? Yes, the device powers from 3v3 rail of a SBC, which makes it powered up as soon as the SBC is up. Given that, should I remove all code around Power Management Runtime (pm_runtime_*) as redundant? > > +#define CEF168_V4L2_CID_CUSTOM(ctrl) \ > > + ((V4L2_CID_USER_BASE | 168) + custom_##ctrl) > > I think you need to reserve space for your controls in > include/uapi/linux/v4l2-controls.h > > otherwise this will never be visible to applications ? I found there is no need for that. Custom control become available automatically by name via the v4l2-ctl utility. For example, the focus range can be read directly in the terminal as follows: v4l2-ctl -d $DEV_LENS -C focus_range > > +/** > > + * cef168 data structure > > No need to kerneldoc unless you properly document all fields and > include the file in some of the Documentation/ OK, I'll remove the comment above the structure. Kind regards, Aliaksandr ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/2] media: i2c: Pinefeat cef168 lens control board driver 2025-08-19 21:32 ` Aliaksandr Smirnou @ 2025-08-20 12:56 ` Jacopo Mondi 2025-08-22 17:04 ` Aliaksandr Smirnou 0 siblings, 1 reply; 10+ messages in thread From: Jacopo Mondi @ 2025-08-20 12:56 UTC (permalink / raw) To: Aliaksandr Smirnou, Hans Verkuil Cc: jacopo.mondi, conor+dt, devicetree, krzk+dt, linux-kernel, linux-media, mchehab, robh Hello +cc Hans On Tue, Aug 19, 2025 at 10:32:34PM +0100, Aliaksandr Smirnou wrote: > Hi Jacopo, > > Thank you for the review. Your remarks are very helpful. While I'll apply > most of them, could you clarify the one regarding pm_runtime_ for me? > > On Tue, 19 Aug 2025 15:47:54 +0200, Jacopo Mondi wrote: > > > +#include <linux/crc8.h> > > > > Do you need to "select CRC8" in Kconfig then ? > > Yes, I'll include it. > > > > +#include "cef168.h" > > > > Why an header file ? > > Ok, I'll remove the header moving everying in the .c file. > > > > + for (retry = 0; retry < 3; retry++) { > > > > This seems a bit random, why do you need to retry three times ? > > The driver retries writes to work around an issue in the Raspberry > Pi's I2C hardware, where the BCM2835 mishandles clock stretching. > When the slave stretches the clock, the Pi can misread the SCL line > or sample data too early, making it think the write failed. To > improve reliability, the kernel driver automatically retries the > write, effectively compensating for the hardware's timing bug. > mmm, usually a mainline driver is not the right place for introducing in workarounds for a specific host/soc, as the assumption is that the driver should work on all platforms equally. Your case is certainly different as this product is pi-specific... With a comment I wouldn't mind too much. Maybe others do :) > > > + ctrl->id != CEF168_V4L2_CID_CUSTOM(data) && > > > + ctrl->id != CEF168_V4L2_CID_CUSTOM(focus_range) && > > > + ctrl->id != CEF168_V4L2_CID_CUSTOM(lens_id)) > > > + return -EINVAL; > > > > If you mark them WRITE_ONLY wouldn't the core take care of this ? > > These controls are read-only. The data they return depens on the lens. > Sorry, I wasn't clear. If you mark as WO the controls you don't accept here, will the core handle this for you ? > > > + struct cef168_data data; > > > > I thought the compiler would complain for variables declared not at > > the beginning of the function > > Ok, I'll move the variable at the beginning. > > > > + pm_runtime_set_active(&client->dev); > > > > Is the device powered up at this point ? > > If you depend on the pm_runtime_resume_and_get() call in open() to > > power the device up, then you need to depend on PM in KConfig ? > > Yes, the device powers from 3v3 rail of a SBC, which makes it powered > up as soon as the SBC is up. Given that, should I remove all code > around Power Management Runtime (pm_runtime_*) as redundant? > Yes, I overlooked the fact you don't regiser any resume/suspend routine, so your calls to pm_runtime_set_active/pm_runtime_put are nops (they do at least provide usage counting, but I would anyway remove them). > > > +#define CEF168_V4L2_CID_CUSTOM(ctrl) \ > > > + ((V4L2_CID_USER_BASE | 168) + custom_##ctrl) > > > > I think you need to reserve space for your controls in > > include/uapi/linux/v4l2-controls.h > > > > otherwise this will never be visible to applications ? > > I found there is no need for that. Custom control become available > automatically by name via the v4l2-ctl utility. For example, the focus > range can be read directly in the terminal as follows: > > v4l2-ctl -d $DEV_LENS -C focus_range > Yes the driver enuemrates them, but you need to add them to the main header, otherwise USER_BASE | 168 will be take by someone else. See in example 7c8c957ef12c41968adb66d785ce1dd5fb2f96e7 which is the latest commit that adds a custom control space. Hans, do we need a uapi header file with the definition of the custom controls registered by this driver ? Thanks j > > > +/** > > > + * cef168 data structure > > > > No need to kerneldoc unless you properly document all fields and > > include the file in some of the Documentation/ > > OK, I'll remove the comment above the structure. > > Kind regards, > Aliaksandr ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/2] media: i2c: Pinefeat cef168 lens control board driver 2025-08-20 12:56 ` Jacopo Mondi @ 2025-08-22 17:04 ` Aliaksandr Smirnou 0 siblings, 0 replies; 10+ messages in thread From: Aliaksandr Smirnou @ 2025-08-22 17:04 UTC (permalink / raw) To: jacopo.mondi Cc: conor+dt, devicetree, hverkuil, krzk+dt, linux-kernel, linux-media, mchehab, robh, support On Wed, 20 Aug 2025 14:56:38 +0200, Jacopo Mondi wrote: > > > > + ctrl->id != CEF168_V4L2_CID_CUSTOM(data) && > > > > + ctrl->id != CEF168_V4L2_CID_CUSTOM(focus_range) && > > > > + ctrl->id != CEF168_V4L2_CID_CUSTOM(lens_id)) > > > > + return -EINVAL; > > > > > > If you mark them WRITE_ONLY wouldn't the core take care of this ? > > > > These controls are read-only. The data they return depens on the lens. > > > > Sorry, I wasn't clear. > > If you mark as WO the controls you don't accept here, will the core > handle this for you ? I see what you mean now. Indeed, the other controls are alredy WO, so the core will not let them pass here. I'll remove this check as redundant. > > > > +#define CEF168_V4L2_CID_CUSTOM(ctrl) \ > > > > + ((V4L2_CID_USER_BASE | 168) + custom_##ctrl) > > > > > > I think you need to reserve space for your controls in > > > include/uapi/linux/v4l2-controls.h > > > > > > otherwise this will never be visible to applications ? > > > > I found there is no need for that. Custom control become available > > automatically by name via the v4l2-ctl utility. For example, the focus > > range can be read directly in the terminal as follows: > > > > v4l2-ctl -d $DEV_LENS -C focus_range > > > > Yes the driver enuemrates them, but you need to add them to the main > header, otherwise USER_BASE | 168 will be take by someone else. > I see, ok, I'll reserve 16 controls for this driver in v4l2-controls.h. Thank you for your help. Kind regards, Aliaksandr ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2025-08-22 17:04 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-08-17 13:05 [PATCH v3 0/2] Pinefeat cef168 lens control board driver Aliaksandr Smirnou 2025-08-17 13:05 ` [PATCH v3 1/2] dt-bindings: Pinefeat cef168 lens control board Aliaksandr Smirnou 2025-08-18 17:36 ` Conor Dooley 2025-08-18 19:29 ` Aliaksandr Smirnou 2025-08-19 8:21 ` Krzysztof Kozlowski 2025-08-17 13:05 ` [PATCH v3 2/2] media: i2c: Pinefeat cef168 lens control board driver Aliaksandr Smirnou 2025-08-19 13:47 ` Jacopo Mondi 2025-08-19 21:32 ` Aliaksandr Smirnou 2025-08-20 12:56 ` Jacopo Mondi 2025-08-22 17:04 ` Aliaksandr Smirnou
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).