| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (C) 2026 Intel Corporation | |
| * CVS driver - CSI/V4L2 subdev support | |
| */ | |
| #include <linux/cleanup.h> | |
| #include <linux/device.h> | |
| #include <linux/module.h> | |
| #include <linux/pm_runtime.h> | |
| #include <linux/property.h> | |
| #include <media/v4l2-async.h> | |
| #include <media/v4l2-common.h> | |
| #include <media/v4l2-ctrls.h> | |
| #include <media/v4l2-event.h> | |
| #include <media/v4l2-fwnode.h> | |
| #include <media/v4l2-mc.h> | |
| #include <media/v4l2-subdev.h> | |
| #include "icvs.h" | |
| /* | |
| * Helpers | |
| */ | |
| static inline struct icvs *notifier_to_csi(struct v4l2_async_notifier *n) | |
| { | |
| return container_of(n, struct icvs, notifier); | |
| } | |
| static inline struct icvs *sd_to_csi(struct v4l2_subdev *sd) | |
| { | |
| return container_of(sd, struct icvs, subdev); | |
| } | |
| /* | |
| * Default formats | |
| */ | |
| static const struct v4l2_mbus_framefmt cvs_csi_format_mbus_default = { | |
| .width = 1, | |
| .height = 1, | |
| .code = MEDIA_BUS_FMT_Y8_1X8, | |
| .field = V4L2_FIELD_NONE, | |
| }; | |
| /** | |
| * csi_set_link_cfg - Program default CSI-2 link parameters | |
| * @ctx: CVS device context | |
| * @link_freq: Link frequency (Hz) | |
| * | |
| * Populates a HOST_SET_MIPI_CONFIG command using current lane count and | |
| * link frequency, then submits it to the device. | |
| * Rest of the link parameters are left at firmware defaults. | |
| * | |
| * Return: 0 on success or negative errno. | |
| */ | |
| static int csi_set_link_cfg(struct icvs *ctx, u64 link_freq) | |
| { | |
| struct icvs_cmd cmd = { | |
| .cmd_id = cpu_to_be16(ICVS_HOST_SET_MIPI_CONFIG), | |
| .param.conf.nr_of_lanes = ctx->nr_of_lanes, | |
| .param.conf.link_freq = link_freq, | |
| }; | |
| size_t cmd_size = sizeof(cmd.cmd_id) + sizeof(cmd.param.conf); | |
| guard(mutex)(&ctx->lock); | |
| return cvs_send(ctx, &cmd, cmd_size); | |
| } | |
| /* | |
| * Streaming | |
| */ | |
| /** | |
| * cvs_csi_enable_streams - Start streaming through the bridge | |
| * @sd: Sub-device pointer | |
| * @state: Active state | |
| * @pad: Pad identifier (must be ICVS_CSI_PAD_SOURCE) | |
| * @streams_mask: Streams to enable (bit 0 supported) | |
| * | |
| * Runtime-resumes the bridge (triggering cvs_runtime_resume() to claim CSI-2 | |
| * link ownership), fetches the link frequency, programs the MIPI configuration, | |
| * and forwards the enable request downstream. | |
| * | |
| * Return: 0 on success or negative errno. | |
| */ | |
| static int cvs_csi_enable_streams(struct v4l2_subdev *sd, | |
| struct v4l2_subdev_state *state, | |
| u32 pad, u64 streams_mask) | |
| { | |
| struct icvs *ctx = sd_to_csi(sd); | |
| struct v4l2_subdev *remote_sd = | |
| media_entity_to_v4l2_subdev(ctx->remote->entity); | |
| struct device *dev = cvs_dev(ctx); | |
| s64 link_freq; | |
| int ret; | |
| /* cvs_set_link_owner(ICVS_CSI_LINK_HOST) */ | |
| ret = pm_runtime_resume_and_get(dev); | |
| if (ret < 0) | |
| return ret; | |
| link_freq = v4l2_get_link_freq(ctx->remote, 0, 0); | |
| if (link_freq < 0) { | |
| ret = link_freq; | |
| goto err_rpm_put; | |
| } | |
| if (ctx->i2c_client) { | |
| ret = csi_set_link_cfg(ctx, link_freq); | |
| if (ret < 0) | |
| goto err_rpm_put_sync; | |
| } | |
| ret = v4l2_subdev_enable_streams(remote_sd, | |
| ctx->remote->index, | |
| streams_mask); | |
| if (ret) | |
| goto err_rpm_put_sync; | |
| return 0; | |
| err_rpm_put_sync: | |
| /* Bypass autosuspend to immediately release ownership on error */ | |
| pm_runtime_put_sync(dev); | |
| return ret; | |
| err_rpm_put: | |
| pm_runtime_put_autosuspend(dev); | |
| return ret; | |
| } | |
| /** | |
| * cvs_csi_disable_streams - Stop streaming through the bridge | |
| * @sd: Sub-device pointer | |
| * @state: Active state | |
| * @pad: Pad identifier (must be ICVS_CSI_PAD_SOURCE) | |
| * @streams_mask: Streams to disable (bit 0 supported) | |
| * | |
| * Disables the remote sensor stream then drops the PM reference acquired | |
| * during enable. After the autosuspend delay, cvs_runtime_suspend() will | |
| * return CSI-2 link ownership to CVS firmware. | |
| * | |
| * Return: 0 on success or negative errno. | |
| */ | |
| static int cvs_csi_disable_streams(struct v4l2_subdev *sd, | |
| struct v4l2_subdev_state *state, | |
| u32 pad, u64 streams_mask) | |
| { | |
| struct icvs *ctx = sd_to_csi(sd); | |
| struct v4l2_subdev *remote_sd = | |
| media_entity_to_v4l2_subdev(ctx->remote->entity); | |
| struct device *dev = cvs_dev(ctx); | |
| int ret; | |
| ret = v4l2_subdev_disable_streams(remote_sd, | |
| ctx->remote->index, | |
| streams_mask); | |
| if (ret) | |
| dev_err(dev, "disable streams failed: %d\n", ret); | |
| /* cvs_set_link_owner(ICVS_CSI_LINK_CVS) */ | |
| pm_runtime_put_autosuspend(dev); | |
| return ret; | |
| } | |
| /* | |
| * Pad operations / formats | |
| */ | |
| /** | |
| * cvs_csi_init_state - Initialize pad formats in subdev state | |
| * @sd: Sub-device | |
| * @state: State container | |
| * | |
| * Sets all pad formats to a minimal 1x1 default. | |
| * | |
| * Return: 0. | |
| */ | |
| static int cvs_csi_init_state(struct v4l2_subdev *sd, | |
| struct v4l2_subdev_state *state) | |
| { | |
| for (unsigned int i = 0; i < sd->entity.num_pads; i++) | |
| *v4l2_subdev_state_get_format(state, i) = | |
| cvs_csi_format_mbus_default; | |
| return 0; | |
| } | |
| /** | |
| * cvs_csi_set_fmt - Negotiate pad format | |
| * @sd: Sub-device | |
| * @state: State | |
| * @format: Desired / returned format | |
| * | |
| * Mirrors sink format onto source pad. Accepts many media bus codes, falling | |
| * back to Y8 if unsupported. Normalizes field setting. | |
| * | |
| * Return: 0. | |
| */ | |
| static int cvs_csi_set_fmt(struct v4l2_subdev *sd, | |
| struct v4l2_subdev_state *state, | |
| struct v4l2_subdev_format *format) | |
| { | |
| struct v4l2_mbus_framefmt *src = | |
| v4l2_subdev_state_get_format(state, ICVS_CSI_PAD_SOURCE); | |
| struct v4l2_mbus_framefmt *sink = | |
| v4l2_subdev_state_get_format(state, ICVS_CSI_PAD_SINK); | |
| if (format->pad == ICVS_CSI_PAD_SOURCE) { /* source pad mirrors sink */ | |
| *src = *sink; | |
| return 0; | |
| } | |
| v4l_bound_align_image(&format->format.width, 1, 65536, 0, | |
| &format->format.height, 1, 65536, 0, 0); | |
| switch (format->format.code) { | |
| /* Accept a large list; default fallback to Y8 */ | |
| case MEDIA_BUS_FMT_RGB444_1X12: | |
| case MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE: | |
| case MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE: | |
| case MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE: | |
| case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE: | |
| case MEDIA_BUS_FMT_RGB565_1X16: | |
| case MEDIA_BUS_FMT_BGR565_2X8_BE: | |
| case MEDIA_BUS_FMT_BGR565_2X8_LE: | |
| case MEDIA_BUS_FMT_RGB565_2X8_BE: | |
| case MEDIA_BUS_FMT_RGB565_2X8_LE: | |
| case MEDIA_BUS_FMT_RGB666_1X18: | |
| case MEDIA_BUS_FMT_RBG888_1X24: | |
| case MEDIA_BUS_FMT_RGB666_1X24_CPADHI: | |
| case MEDIA_BUS_FMT_BGR888_1X24: | |
| case MEDIA_BUS_FMT_GBR888_1X24: | |
| case MEDIA_BUS_FMT_RGB888_1X24: | |
| case MEDIA_BUS_FMT_RGB888_2X12_BE: | |
| case MEDIA_BUS_FMT_RGB888_2X12_LE: | |
| case MEDIA_BUS_FMT_ARGB8888_1X32: | |
| case MEDIA_BUS_FMT_RGB888_1X32_PADHI: | |
| case MEDIA_BUS_FMT_RGB101010_1X30: | |
| case MEDIA_BUS_FMT_RGB121212_1X36: | |
| case MEDIA_BUS_FMT_RGB161616_1X48: | |
| case MEDIA_BUS_FMT_Y8_1X8: | |
| case MEDIA_BUS_FMT_UV8_1X8: | |
| case MEDIA_BUS_FMT_UYVY8_1_5X8: | |
| case MEDIA_BUS_FMT_VYUY8_1_5X8: | |
| case MEDIA_BUS_FMT_YUYV8_1_5X8: | |
| case MEDIA_BUS_FMT_YVYU8_1_5X8: | |
| case MEDIA_BUS_FMT_UYVY8_2X8: | |
| case MEDIA_BUS_FMT_VYUY8_2X8: | |
| case MEDIA_BUS_FMT_YUYV8_2X8: | |
| case MEDIA_BUS_FMT_YVYU8_2X8: | |
| case MEDIA_BUS_FMT_Y10_1X10: | |
| case MEDIA_BUS_FMT_UYVY10_2X10: | |
| case MEDIA_BUS_FMT_VYUY10_2X10: | |
| case MEDIA_BUS_FMT_YUYV10_2X10: | |
| case MEDIA_BUS_FMT_YVYU10_2X10: | |
| case MEDIA_BUS_FMT_Y12_1X12: | |
| case MEDIA_BUS_FMT_UYVY12_2X12: | |
| case MEDIA_BUS_FMT_VYUY12_2X12: | |
| case MEDIA_BUS_FMT_YUYV12_2X12: | |
| case MEDIA_BUS_FMT_YVYU12_2X12: | |
| case MEDIA_BUS_FMT_UYVY8_1X16: | |
| case MEDIA_BUS_FMT_VYUY8_1X16: | |
| case MEDIA_BUS_FMT_YUYV8_1X16: | |
| case MEDIA_BUS_FMT_YVYU8_1X16: | |
| case MEDIA_BUS_FMT_YDYUYDYV8_1X16: | |
| case MEDIA_BUS_FMT_UYVY10_1X20: | |
| case MEDIA_BUS_FMT_VYUY10_1X20: | |
| case MEDIA_BUS_FMT_YUYV10_1X20: | |
| case MEDIA_BUS_FMT_YVYU10_1X20: | |
| case MEDIA_BUS_FMT_VUY8_1X24: | |
| case MEDIA_BUS_FMT_YUV8_1X24: | |
| case MEDIA_BUS_FMT_UYYVYY8_0_5X24: | |
| case MEDIA_BUS_FMT_UYVY12_1X24: | |
| case MEDIA_BUS_FMT_VYUY12_1X24: | |
| case MEDIA_BUS_FMT_YUYV12_1X24: | |
| case MEDIA_BUS_FMT_YVYU12_1X24: | |
| case MEDIA_BUS_FMT_YUV10_1X30: | |
| case MEDIA_BUS_FMT_UYYVYY10_0_5X30: | |
| case MEDIA_BUS_FMT_AYUV8_1X32: | |
| case MEDIA_BUS_FMT_UYYVYY12_0_5X36: | |
| case MEDIA_BUS_FMT_YUV12_1X36: | |
| case MEDIA_BUS_FMT_YUV16_1X48: | |
| case MEDIA_BUS_FMT_UYYVYY16_0_5X48: | |
| case MEDIA_BUS_FMT_JPEG_1X8: | |
| case MEDIA_BUS_FMT_AHSV8888_1X32: | |
| case MEDIA_BUS_FMT_SBGGR8_1X8: | |
| case MEDIA_BUS_FMT_SGBRG8_1X8: | |
| case MEDIA_BUS_FMT_SGRBG8_1X8: | |
| case MEDIA_BUS_FMT_SRGGB8_1X8: | |
| case MEDIA_BUS_FMT_SBGGR10_1X10: | |
| case MEDIA_BUS_FMT_SGBRG10_1X10: | |
| case MEDIA_BUS_FMT_SGRBG10_1X10: | |
| case MEDIA_BUS_FMT_SRGGB10_1X10: | |
| case MEDIA_BUS_FMT_SBGGR12_1X12: | |
| case MEDIA_BUS_FMT_SGBRG12_1X12: | |
| case MEDIA_BUS_FMT_SGRBG12_1X12: | |
| case MEDIA_BUS_FMT_SRGGB12_1X12: | |
| case MEDIA_BUS_FMT_SBGGR14_1X14: | |
| case MEDIA_BUS_FMT_SGBRG14_1X14: | |
| case MEDIA_BUS_FMT_SGRBG14_1X14: | |
| case MEDIA_BUS_FMT_SRGGB14_1X14: | |
| case MEDIA_BUS_FMT_SBGGR16_1X16: | |
| case MEDIA_BUS_FMT_SGBRG16_1X16: | |
| case MEDIA_BUS_FMT_SGRBG16_1X16: | |
| case MEDIA_BUS_FMT_SRGGB16_1X16: | |
| break; | |
| default: | |
| format->format.code = MEDIA_BUS_FMT_Y8_1X8; | |
| break; | |
| } | |
| if (format->format.field == V4L2_FIELD_ANY) | |
| format->format.field = V4L2_FIELD_NONE; | |
| *sink = format->format; | |
| *src = *sink; | |
| return 0; | |
| } | |
| /** | |
| * cvs_csi_get_mbus_config - Provide current CSI-2 bus configuration | |
| * @sd: Sub-device | |
| * @pad: Pad index | |
| * @cfg: Returned bus config | |
| * | |
| * Fills lane ordering and number of lanes; retrieves link frequency from | |
| * remote entity. | |
| * | |
| * Return: 0 on success or negative errno. | |
| */ | |
| static int cvs_csi_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad, | |
| struct v4l2_mbus_config *cfg) | |
| { | |
| struct icvs *ctx = sd_to_csi(sd); | |
| s64 freq; | |
| cfg->type = V4L2_MBUS_CSI2_DPHY; | |
| for (unsigned int i = 0; i < V4L2_MBUS_CSI2_MAX_DATA_LANES; i++) | |
| cfg->bus.mipi_csi2.data_lanes[i] = i + 1; | |
| cfg->bus.mipi_csi2.num_data_lanes = ctx->nr_of_lanes; | |
| freq = v4l2_get_link_freq(ctx->remote, 0, 0); | |
| if (freq < 0) | |
| return -EINVAL; | |
| cfg->link_freq = freq; | |
| return 0; | |
| } | |
| static const struct v4l2_subdev_core_ops cvs_csi_subdev_core_ops = { | |
| .subscribe_event = v4l2_ctrl_subdev_subscribe_event, | |
| .unsubscribe_event = v4l2_event_subdev_unsubscribe, | |
| }; | |
| static const struct v4l2_subdev_video_ops cvs_csi_video_ops = { | |
| .s_stream = v4l2_subdev_s_stream_helper, | |
| }; | |
| static const struct v4l2_subdev_pad_ops cvs_csi_pad_ops = { | |
| .get_fmt = v4l2_subdev_get_fmt, | |
| .set_fmt = cvs_csi_set_fmt, | |
| .get_mbus_config = cvs_csi_get_mbus_config, | |
| .enable_streams = cvs_csi_enable_streams, | |
| .disable_streams = cvs_csi_disable_streams, | |
| }; | |
| static const struct v4l2_subdev_ops cvs_csi_subdev_ops = { | |
| .core = &cvs_csi_subdev_core_ops, | |
| .video = &cvs_csi_video_ops, | |
| .pad = &cvs_csi_pad_ops, | |
| }; | |
| static const struct v4l2_subdev_internal_ops cvs_csi_internal_ops = { | |
| .init_state = cvs_csi_init_state, | |
| }; | |
| static const struct media_entity_operations cvs_csi_entity_ops = { | |
| .link_validate = v4l2_subdev_link_validate, | |
| }; | |
| /* | |
| * Async notifier | |
| */ | |
| /** | |
| * cvs_csi_notify_bound - Remote sensor bound callback | |
| * @notifier: Async notifier | |
| * @sd: Remote subdev | |
| * @asc: Async match connection | |
| * | |
| * Locates the source pad of the remote sensor and creates a media link to | |
| * the CVS bridge sink pad enabling it by default. | |
| * | |
| * Return: 0 on success or negative errno. | |
| */ | |
| static int cvs_csi_notify_bound(struct v4l2_async_notifier *notifier, | |
| struct v4l2_subdev *sd, | |
| struct v4l2_async_connection *asc) | |
| { | |
| struct icvs *ctx = notifier_to_csi(notifier); | |
| int pad; | |
| pad = media_entity_get_fwnode_pad(&sd->entity, asc->match.fwnode, | |
| MEDIA_PAD_FL_SOURCE); | |
| if (pad < 0) | |
| return pad; | |
| ctx->remote = &sd->entity.pads[pad]; | |
| return media_create_pad_link(&sd->entity, pad, &ctx->subdev.entity, | |
| ICVS_CSI_PAD_SINK, MEDIA_LNK_FL_ENABLED | | |
| MEDIA_LNK_FL_IMMUTABLE); | |
| } | |
| /** | |
| * cvs_csi_notify_unbind - Remote sensor unbind callback | |
| * @notifier: Notifier | |
| * @sd: Remote subdev | |
| * @asc: Connection | |
| */ | |
| static void cvs_csi_notify_unbind(struct v4l2_async_notifier *notifier, | |
| struct v4l2_subdev *sd, | |
| struct v4l2_async_connection *asc) | |
| { | |
| struct icvs *ctx = notifier_to_csi(notifier); | |
| ctx->remote = NULL; | |
| } | |
| static const struct v4l2_async_notifier_operations cvs_csi_notify_ops = { | |
| .bound = cvs_csi_notify_bound, | |
| .unbind = cvs_csi_notify_unbind, | |
| }; | |
| /* | |
| * Controls | |
| */ | |
| /** | |
| * cvs_csi_init_controls - Initialize V4L2 controls | |
| * @ctx: CVS context | |
| * | |
| * Currently sets up a read-only privacy control placeholder. | |
| * | |
| * Return: 0 on success or negative errno. | |
| */ | |
| static int cvs_csi_init_controls(struct icvs *ctx) | |
| { | |
| struct v4l2_ctrl *privacy_ctrl; | |
| v4l2_ctrl_handler_init(&ctx->ctrl_handler, 1); | |
| privacy_ctrl = v4l2_ctrl_new_std(&ctx->ctrl_handler, NULL, | |
| V4L2_CID_PRIVACY, 0, 1, 1, 0); | |
| if (privacy_ctrl) | |
| privacy_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; | |
| if (ctx->ctrl_handler.error) { | |
| v4l2_ctrl_handler_free(&ctx->ctrl_handler); | |
| return ctx->ctrl_handler.error; | |
| } | |
| ctx->subdev.ctrl_handler = &ctx->ctrl_handler; | |
| return 0; | |
| } | |
| /* | |
| * Firmware (graph) parsing | |
| */ | |
| /** | |
| * cvs_csi_parse_firmware - Parse firmware (ACPI graph) endpoints | |
| * @ctx: CVS context | |
| * | |
| * Discovers sink and remote source endpoints, validates lane counts and | |
| * registers an async notifier for the remote sensor. | |
| * | |
| * Return: 0 on success or negative errno. | |
| */ | |
| static int cvs_csi_parse_firmware(struct icvs *ctx) | |
| { | |
| struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY }; | |
| struct device *dev = cvs_dev(ctx); | |
| struct fwnode_handle *sink_ep, *source_ep; | |
| struct v4l2_async_connection *asc; | |
| int ret; | |
| sink_ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); | |
| v4l2_async_subdev_nf_init(&ctx->notifier, &ctx->subdev); | |
| ctx->notifier.ops = &cvs_csi_notify_ops; | |
| ret = v4l2_fwnode_endpoint_parse(sink_ep, &ep); | |
| if (ret) | |
| goto err_nf_cleanup; | |
| ctx->nr_of_lanes = ep.bus.mipi_csi2.num_data_lanes; | |
| source_ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 1, 0, 0); | |
| ret = v4l2_fwnode_endpoint_parse(source_ep, &ep); | |
| fwnode_handle_put(source_ep); | |
| if (ret) | |
| goto err_nf_cleanup; | |
| if (ctx->nr_of_lanes != ep.bus.mipi_csi2.num_data_lanes) { | |
| ret = -EINVAL; | |
| goto err_nf_cleanup; | |
| } | |
| asc = v4l2_async_nf_add_fwnode_remote(&ctx->notifier, sink_ep, | |
| struct v4l2_async_connection); | |
| if (IS_ERR(asc)) { | |
| ret = PTR_ERR(asc); | |
| goto err_nf_cleanup; | |
| } | |
| ret = v4l2_async_nf_register(&ctx->notifier); | |
| if (ret) | |
| goto err_nf_cleanup; | |
| fwnode_handle_put(sink_ep); | |
| return 0; | |
| err_nf_cleanup: | |
| v4l2_async_nf_cleanup(&ctx->notifier); | |
| fwnode_handle_put(sink_ep); | |
| return ret; | |
| } | |
| /* | |
| * Public CSI init/cleanup used by core probe/remove | |
| */ | |
| /** | |
| * cvs_csi_init - Initialize CSI/V4L2 sub-device side of bridge | |
| * @ctx: CVS context | |
| * @dev: Parent device | |
| * @i2c: I2C client (may be NULL for platform mode) | |
| * | |
| * Sets up sub-device, media entity pads, async notifier, controls and | |
| * registers with the V4L2 framework. | |
| * | |
| * Return: 0 on success or negative errno. | |
| */ | |
| int cvs_csi_init(struct icvs *ctx, struct device *dev, struct i2c_client *i2c) | |
| { | |
| int ret; | |
| if (i2c) { | |
| v4l2_i2c_subdev_init(&ctx->subdev, i2c, &cvs_csi_subdev_ops); | |
| } else { | |
| v4l2_subdev_init(&ctx->subdev, &cvs_csi_subdev_ops); | |
| ctx->subdev.dev = dev; | |
| } | |
| ctx->subdev.internal_ops = &cvs_csi_internal_ops; | |
| v4l2_set_subdevdata(&ctx->subdev, ctx); | |
| ctx->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; | |
| ctx->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; | |
| ctx->subdev.entity.ops = &cvs_csi_entity_ops; | |
| snprintf(ctx->subdev.name, sizeof(ctx->subdev.name), "Intel CVS"); | |
| ret = cvs_csi_parse_firmware(ctx); | |
| if (ret) | |
| return ret; | |
| ret = cvs_csi_init_controls(ctx); | |
| if (ret) | |
| goto err_nf_unreg; | |
| ctx->pads[ICVS_CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; | |
| ctx->pads[ICVS_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK; | |
| ret = media_entity_pads_init(&ctx->subdev.entity, ICVS_CSI_NUM_PADS, | |
| ctx->pads); | |
| if (ret) | |
| goto err_ctrl_cleanup; | |
| ctx->subdev.state_lock = ctx->ctrl_handler.lock; | |
| ret = v4l2_subdev_init_finalize(&ctx->subdev); | |
| if (ret) | |
| goto err_entity_cleanup; | |
| ret = v4l2_async_register_subdev(&ctx->subdev); | |
| if (ret) | |
| goto err_entity_cleanup; | |
| return 0; | |
| err_entity_cleanup: | |
| media_entity_cleanup(&ctx->subdev.entity); | |
| err_ctrl_cleanup: | |
| v4l2_ctrl_handler_free(&ctx->ctrl_handler); | |
| err_nf_unreg: | |
| v4l2_async_nf_unregister(&ctx->notifier); | |
| v4l2_async_nf_cleanup(&ctx->notifier); | |
| return ret; | |
| } | |
| /** | |
| * cvs_csi_remove - Cleanup CSI/V4L2 sub-device | |
| * @ctx: CVS context | |
| */ | |
| void cvs_csi_remove(struct icvs *ctx) | |
| { | |
| v4l2_async_nf_unregister(&ctx->notifier); | |
| v4l2_async_nf_cleanup(&ctx->notifier); | |
| v4l2_ctrl_handler_free(&ctx->ctrl_handler); | |
| v4l2_async_unregister_subdev(&ctx->subdev); | |
| v4l2_subdev_cleanup(&ctx->subdev); | |
| media_entity_cleanup(&ctx->subdev.entity); | |
| } | |
| MODULE_AUTHOR("Miguel Vadillo <miguel.vadillo@intel.com>"); | |
| MODULE_DESCRIPTION("CSI/V4L2 support for Intel Vision Sensing Controller"); | |
| MODULE_LICENSE("GPL"); |