LCOV - code coverage report
Current view: top level - ipu4 - virt-sensor.c (source / functions) Coverage Total Hit
Test: ipu4.info Lines: 60.7 % 122 74
Test Date: 2026-05-12 04:57:36 Functions: 50.0 % 8 4

            Line data    Source code
       1              : // SPDX-License-Identifier: GPL-2.0-only
       2              : /*
       3              :  * Synthetic CSI2 sensor subdev for the IPU4 QEMU harness.
       4              :  *
       5              :  * Under CONFIG_VIDEO_IPU4_VIRT_SENSOR, `ipu4_virt_sensor_install()`
       6              :  * replaces the ambu_ipu_bridge_init() call in the IPU4 probe path. It
       7              :  * does exactly two things that matter to ipu6-isys:
       8              :  *
       9              :  *   1. Builds a software_node graph on `pdev->dev.fwnode->secondary`
      10              :  *      shaped like the ambu bridge's: an IPU HID node with port@0 /
      11              :  *      endpoint@0, linked to a sensor node with its own port@0 /
      12              :  *      endpoint@0. This is what `fwnode_graph_get_endpoint_by_id()`
      13              :  *      in isys_notifier_init() (ipu6-isys.c:824) walks.
      14              :  *
      15              :  *   2. Registers a v4l2_subdev with a SOURCE pad advertising
      16              :  *      RGB888_1X24 at 800x800, with its fwnode set to the sensor HID
      17              :  *      node's fwnode so the async matcher can bind it.
      18              :  *
      19              :  * `.s_stream()` is a no-op — frame generation is M5's QEMU work. What
      20              :  * this file delivers is: probe completes, `/dev/video*` nodes appear.
      21              :  */
      22              : 
      23              : #include <linux/module.h>
      24              : #include <linux/property.h>
      25              : #include <linux/slab.h>
      26              : #include <linux/types.h>
      27              : 
      28              : #include <media/media-entity.h>
      29              : #include <media/v4l2-ctrls.h>
      30              : #include <media/v4l2-fwnode.h>
      31              : #include <media/v4l2-subdev.h>
      32              : 
      33              : #include "ipu4-compat.h"
      34              : #include "virt-sensor.h"
      35              : 
      36              : #define VIRT_SENSOR_WIDTH               800
      37              : #define VIRT_SENSOR_HEIGHT              800
      38              : #define VIRT_SENSOR_MBUS_CODE           MEDIA_BUS_FMT_RGB888_1X24
      39              : #define VIRT_SENSOR_LANES               4
      40              : #define VIRT_SENSOR_LINK_FREQ           450000000ULL
      41              : 
      42              : /* Match ambu-bridge's IPU HID; ipu6-isys does not care about the string,
      43              :  * but using the same ACPI HID keeps the register/cleanup symmetric with
      44              :  * the downstream bridge code the harness is replacing. */
      45              : #define IPU_HID                         "INT343E"
      46              : #define SENSOR_NAME                     "ipu4-virt-sensor"
      47              : 
      48              : struct virt_sensor {
      49              :         struct v4l2_subdev sd;
      50              :         struct media_pad pad;
      51              :         struct v4l2_ctrl_handler ctrl_handler;
      52              : 
      53              :         /* Nodes are in the order expected by software_node_register_node_group. */
      54              :         struct software_node ipu_node;
      55              :         struct software_node ipu_port;
      56              :         struct software_node ipu_endpoint;
      57              :         struct software_node sensor_node;
      58              :         struct software_node sensor_port;
      59              :         struct software_node sensor_endpoint;
      60              :         const struct software_node *group[7];           /* 6 nodes + NULL */
      61              : 
      62              :         u32 data_lanes[VIRT_SENSOR_LANES];
      63              :         u64 link_freqs[1];
      64              : 
      65              :         struct property_entry ipu_ep_props[3];          /* data-lanes, remote-ep, NULL */
      66              :         struct property_entry sensor_ep_props[5];       /* bus-type, data-lanes, remote-ep, link-freqs, NULL */
      67              : 
      68              :         /* PROPERTY_ENTRY_REF_ARRAY uses ARRAY_SIZE internally, so these
      69              :          * must be actual arrays (size 1) rather than plain structs. */
      70              :         struct software_node_ref_args ipu_remote_ref[1];
      71              :         struct software_node_ref_args sensor_remote_ref[1];
      72              : 
      73              :         bool fwnode_secondary_set;
      74              : };
      75              : 
      76              : /* Only one virt-sensor per system; matches the one-IPU-device reality. */
      77              : static struct virt_sensor *g_virt_sensor;
      78              : 
      79              : static const struct v4l2_mbus_framefmt virt_sensor_default_fmt = {
      80              :         .width          = VIRT_SENSOR_WIDTH,
      81              :         .height         = VIRT_SENSOR_HEIGHT,
      82              :         .code           = VIRT_SENSOR_MBUS_CODE,
      83              :         .field          = V4L2_FIELD_NONE,
      84              :         .colorspace     = V4L2_COLORSPACE_SRGB,
      85              : };
      86              : 
      87            0 : static int virt_sensor_enum_mbus_code(struct v4l2_subdev *sd,
      88              :                                       struct v4l2_subdev_state *state,
      89              :                                       struct v4l2_subdev_mbus_code_enum *code)
      90              : {
      91            0 :         if (code->index != 0)
      92              :                 return -EINVAL;
      93            0 :         code->code = VIRT_SENSOR_MBUS_CODE;
      94            0 :         return 0;
      95              : }
      96              : 
      97            0 : static int virt_sensor_enum_frame_size(struct v4l2_subdev *sd,
      98              :                                        struct v4l2_subdev_state *state,
      99              :                                        struct v4l2_subdev_frame_size_enum *fse)
     100              : {
     101            0 :         if (fse->index != 0 || fse->code != VIRT_SENSOR_MBUS_CODE)
     102              :                 return -EINVAL;
     103            0 :         fse->min_width = fse->max_width = VIRT_SENSOR_WIDTH;
     104            0 :         fse->min_height = fse->max_height = VIRT_SENSOR_HEIGHT;
     105            0 :         return 0;
     106              : }
     107              : 
     108            1 : static int virt_sensor_get_fmt(struct v4l2_subdev *sd,
     109              :                                struct v4l2_subdev_state *state,
     110              :                                struct v4l2_subdev_format *fmt)
     111              : {
     112            1 :         fmt->format = virt_sensor_default_fmt;
     113            1 :         return 0;
     114              : }
     115              : 
     116            0 : static int virt_sensor_set_fmt(struct v4l2_subdev *sd,
     117              :                                struct v4l2_subdev_state *state,
     118              :                                struct v4l2_subdev_format *fmt)
     119              : {
     120              :         /* Single format — negotiation is a no-op. */
     121            0 :         fmt->format = virt_sensor_default_fmt;
     122            0 :         return 0;
     123              : }
     124              : 
     125            2 : static int virt_sensor_s_stream(struct v4l2_subdev *sd, int enable)
     126              : {
     127            3 :         dev_info(sd->dev, "virt-sensor s_stream %s (no-op, M5 delivers frames)\n",
     128              :                  enable ? "on" : "off");
     129            2 :         return 0;
     130              : }
     131              : 
     132              : static const struct v4l2_subdev_pad_ops virt_sensor_pad_ops = {
     133              :         .enum_mbus_code         = virt_sensor_enum_mbus_code,
     134              :         .enum_frame_size        = virt_sensor_enum_frame_size,
     135              :         .get_fmt                = virt_sensor_get_fmt,
     136              :         .set_fmt                = virt_sensor_set_fmt,
     137              : };
     138              : 
     139              : static const struct v4l2_subdev_video_ops virt_sensor_video_ops = {
     140              :         .s_stream = virt_sensor_s_stream,
     141              : };
     142              : 
     143              : static const struct v4l2_subdev_ops virt_sensor_subdev_ops = {
     144              :         .pad    = &virt_sensor_pad_ops,
     145              :         .video  = &virt_sensor_video_ops,
     146              : };
     147              : 
     148            1 : static void virt_sensor_build_swnodes(struct virt_sensor *vs)
     149              : {
     150              :         unsigned int i;
     151              : 
     152            5 :         for (i = 0; i < VIRT_SENSOR_LANES; i++)
     153            4 :                 vs->data_lanes[i] = i + 1;
     154            1 :         vs->link_freqs[0] = VIRT_SENSOR_LINK_FREQ;
     155              : 
     156            1 :         vs->ipu_node.name            = IPU_HID;
     157            1 :         vs->ipu_port.name            = "port@0";
     158            1 :         vs->ipu_port.parent          = &vs->ipu_node;
     159            1 :         vs->ipu_endpoint.name                = "endpoint@0";
     160            1 :         vs->ipu_endpoint.parent              = &vs->ipu_port;
     161              : 
     162            1 :         vs->sensor_node.name         = SENSOR_NAME;
     163            1 :         vs->sensor_port.name         = "port@0";
     164            1 :         vs->sensor_port.parent               = &vs->sensor_node;
     165            1 :         vs->sensor_endpoint.name     = "endpoint@0";
     166            1 :         vs->sensor_endpoint.parent   = &vs->sensor_port;
     167              : 
     168            1 :         vs->ipu_remote_ref[0] = SOFTWARE_NODE_REFERENCE(&vs->sensor_endpoint);
     169            1 :         vs->sensor_remote_ref[0] = SOFTWARE_NODE_REFERENCE(&vs->ipu_endpoint);
     170              : 
     171            1 :         vs->ipu_ep_props[0] = PROPERTY_ENTRY_U32_ARRAY_LEN("data-lanes",
     172              :                                                            vs->data_lanes,
     173              :                                                            VIRT_SENSOR_LANES);
     174            1 :         vs->ipu_ep_props[1] = PROPERTY_ENTRY_REF_ARRAY("remote-endpoint",
     175              :                                                        vs->ipu_remote_ref);
     176              :         /* ipu_ep_props[2] is the zero-init terminator. */
     177              : 
     178            1 :         vs->sensor_ep_props[0] = PROPERTY_ENTRY_U32("bus-type",
     179              :                                                     V4L2_FWNODE_BUS_TYPE_CSI2_DPHY);
     180            1 :         vs->sensor_ep_props[1] = PROPERTY_ENTRY_U32_ARRAY_LEN("data-lanes",
     181              :                                                               vs->data_lanes,
     182              :                                                               VIRT_SENSOR_LANES);
     183            1 :         vs->sensor_ep_props[2] = PROPERTY_ENTRY_REF_ARRAY("remote-endpoint",
     184              :                                                           vs->sensor_remote_ref);
     185            1 :         vs->sensor_ep_props[3] = PROPERTY_ENTRY_U64_ARRAY_LEN("link-frequencies",
     186              :                                                               vs->link_freqs, 1);
     187              :         /* sensor_ep_props[4] is the terminator. */
     188              : 
     189            1 :         vs->ipu_endpoint.properties  = vs->ipu_ep_props;
     190            1 :         vs->sensor_endpoint.properties       = vs->sensor_ep_props;
     191              : 
     192            1 :         vs->group[0] = &vs->ipu_node;
     193            1 :         vs->group[1] = &vs->ipu_port;
     194            1 :         vs->group[2] = &vs->ipu_endpoint;
     195            1 :         vs->group[3] = &vs->sensor_node;
     196            1 :         vs->group[4] = &vs->sensor_port;
     197            1 :         vs->group[5] = &vs->sensor_endpoint;
     198            1 :         vs->group[6] = NULL;
     199            1 : }
     200              : 
     201            1 : int ipu4_virt_sensor_install(struct pci_dev *pdev)
     202              : {
     203            1 :         struct device *dev = &pdev->dev;
     204              :         struct virt_sensor *vs;
     205              :         struct fwnode_handle *ipu_fwnode;
     206              :         int ret;
     207              : 
     208            1 :         if (g_virt_sensor)
     209              :                 return -EEXIST;
     210            1 :         if (!dev->fwnode) {
     211            0 :                 dev_err(dev, "virt-sensor: IPU device has no primary fwnode\n");
     212            0 :                 return -ENODEV;
     213              :         }
     214            1 :         if (dev->fwnode->secondary && !IS_ERR(dev->fwnode->secondary)) {
     215            0 :                 dev_err(dev, "virt-sensor: fwnode->secondary already set\n");
     216            0 :                 return -EBUSY;
     217              :         }
     218              : 
     219            1 :         vs = kzalloc(sizeof(*vs), GFP_KERNEL);
     220            1 :         if (!vs)
     221              :                 return -ENOMEM;
     222              : 
     223            1 :         virt_sensor_build_swnodes(vs);
     224              : 
     225            1 :         ret = software_node_register_node_group(vs->group);
     226            1 :         if (ret) {
     227            0 :                 dev_err(dev, "virt-sensor: sw-node register failed %d\n", ret);
     228            0 :                 goto err_free;
     229              :         }
     230              : 
     231            1 :         ipu_fwnode = software_node_fwnode(&vs->ipu_node);
     232            1 :         if (!ipu_fwnode) {
     233            0 :                 dev_err(dev, "virt-sensor: no fwnode for IPU swnode\n");
     234              :                 ret = -ENODEV;
     235            0 :                 goto err_nodes;
     236              :         }
     237              : 
     238              :         /* Mirror ambu_ipu_bridge_init: there is no public inverse of
     239              :          * set_secondary_fwnode(), so set it directly. */
     240            1 :         dev->fwnode->secondary = ipu_fwnode;
     241            1 :         vs->fwnode_secondary_set = true;
     242              : 
     243            1 :         v4l2_subdev_init(&vs->sd, &virt_sensor_subdev_ops);
     244            1 :         vs->sd.dev   = dev;
     245            1 :         vs->sd.fwnode        = software_node_fwnode(&vs->sensor_node);
     246            1 :         vs->sd.owner = THIS_MODULE;
     247            1 :         vs->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
     248            1 :         strscpy(vs->sd.name, SENSOR_NAME, sizeof(vs->sd.name));
     249              : 
     250              :         /*
     251              :          * Expose V4L2_CID_LINK_FREQ so ipu6_isys_csi2_get_link_freq()
     252              :          * (kernel/ipu4/ipu6-isys-csi2.c:108) can read a sane value from
     253              :          * `v4l2_get_link_freq(ext_sd->ctrl_handler, 0, 0)`. Without the
     254              :          * ctrl, that helper returns -ENOENT, ipu6_isys_csi2_calc_timing()
     255              :          * propagates the error back, and CSI2 enable_streams aborts
     256              :          * before set_stream() ever writes the per-port RX_ENABLE /
     257              :          * DLY_CNT_* / IRQ_* registers — exactly the silicon writes the
     258              :          * Step-2 mmio-trace coverage report wants to drop from
     259              :          * `unimplemented`.
     260              :          */
     261            1 :         ret = v4l2_ctrl_handler_init(&vs->ctrl_handler, 1);
     262            1 :         if (ret) {
     263            0 :                 dev_err(dev, "virt-sensor: ctrl handler init failed %d\n", ret);
     264            0 :                 goto err_fwnode;
     265              :         }
     266            1 :         v4l2_ctrl_new_int_menu(&vs->ctrl_handler, NULL, V4L2_CID_LINK_FREQ,
     267            1 :                                0, 0, vs->link_freqs);
     268            1 :         if (vs->ctrl_handler.error) {
     269              :                 ret = vs->ctrl_handler.error;
     270            0 :                 dev_err(dev, "virt-sensor: failed to add LINK_FREQ ctrl %d\n",
     271              :                         ret);
     272            0 :                 v4l2_ctrl_handler_free(&vs->ctrl_handler);
     273            0 :                 goto err_fwnode;
     274              :         }
     275            1 :         vs->sd.ctrl_handler = &vs->ctrl_handler;
     276              : 
     277            1 :         vs->pad.flags                 = MEDIA_PAD_FL_SOURCE;
     278            1 :         vs->sd.entity.function        = MEDIA_ENT_F_CAM_SENSOR;
     279            1 :         vs->sd.entity.name    = vs->sd.name;
     280              : 
     281            1 :         ret = media_entity_pads_init(&vs->sd.entity, 1, &vs->pad);
     282            1 :         if (ret) {
     283            0 :                 dev_err(dev, "virt-sensor: entity pads init failed %d\n", ret);
     284            0 :                 goto err_fwnode;
     285              :         }
     286              : 
     287            1 :         ret = v4l2_async_register_subdev(&vs->sd);
     288            1 :         if (ret) {
     289            0 :                 dev_err(dev, "virt-sensor: async register failed %d\n", ret);
     290            0 :                 goto err_entity;
     291              :         }
     292              : 
     293            1 :         g_virt_sensor = vs;
     294            1 :         dev_info(dev, "virt-sensor installed: %ux%u RGB888, %d lanes\n",
     295              :                  VIRT_SENSOR_WIDTH, VIRT_SENSOR_HEIGHT, VIRT_SENSOR_LANES);
     296            1 :         return 0;
     297              : 
     298              : err_entity:
     299              :         media_entity_cleanup(&vs->sd.entity);
     300            0 :         v4l2_ctrl_handler_free(&vs->ctrl_handler);
     301            0 : err_fwnode:
     302            0 :         dev->fwnode->secondary = NULL;
     303            0 :         vs->fwnode_secondary_set = false;
     304            0 : err_nodes:
     305            0 :         software_node_unregister_node_group(vs->group);
     306            0 : err_free:
     307            0 :         kfree(vs);
     308            0 :         return ret;
     309              : }
     310              : EXPORT_SYMBOL_NS_GPL(ipu4_virt_sensor_install, INTEL_IPU6);
     311              : 
     312            0 : void ipu4_virt_sensor_remove(struct pci_dev *pdev)
     313              : {
     314            0 :         struct virt_sensor *vs = g_virt_sensor;
     315              : 
     316            0 :         if (!vs)
     317              :                 return;
     318            0 :         v4l2_async_unregister_subdev(&vs->sd);
     319              :         media_entity_cleanup(&vs->sd.entity);
     320            0 :         v4l2_ctrl_handler_free(&vs->ctrl_handler);
     321            0 :         if (vs->fwnode_secondary_set)
     322            0 :                 pdev->dev.fwnode->secondary = NULL;
     323            0 :         software_node_unregister_node_group(vs->group);
     324            0 :         kfree(vs);
     325            0 :         g_virt_sensor = NULL;
     326              : }
     327              : EXPORT_SYMBOL_NS_GPL(ipu4_virt_sensor_remove, INTEL_IPU6);
        

Generated by: LCOV version 2.0-1