linux/drivers/staging/media/imx/imx-media-dev-common.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * V4L2 Media Controller Driver for Freescale common i.MX5/6/7 SOC
   4 *
   5 * Copyright (c) 2019 Linaro Ltd
   6 * Copyright (c) 2016 Mentor Graphics Inc.
   7 */
   8
   9#include <linux/of_graph.h>
  10#include <linux/of_platform.h>
  11#include <media/v4l2-ctrls.h>
  12#include <media/v4l2-event.h>
  13#include <media/v4l2-ioctl.h>
  14#include <media/v4l2-mc.h>
  15#include "imx-media.h"
  16
  17static inline struct imx_media_dev *notifier2dev(struct v4l2_async_notifier *n)
  18{
  19        return container_of(n, struct imx_media_dev, notifier);
  20}
  21
  22/* async subdev bound notifier */
  23static int imx_media_subdev_bound(struct v4l2_async_notifier *notifier,
  24                                  struct v4l2_subdev *sd,
  25                                  struct v4l2_async_subdev *asd)
  26{
  27        v4l2_info(sd->v4l2_dev, "subdev %s bound\n", sd->name);
  28
  29        return 0;
  30}
  31
  32/*
  33 * Create the media links for all subdevs that registered.
  34 * Called after all async subdevs have bound.
  35 */
  36static int imx_media_create_links(struct v4l2_async_notifier *notifier)
  37{
  38        struct imx_media_dev *imxmd = notifier2dev(notifier);
  39        struct v4l2_subdev *sd;
  40
  41        list_for_each_entry(sd, &imxmd->v4l2_dev.subdevs, list) {
  42                switch (sd->grp_id) {
  43                case IMX_MEDIA_GRP_ID_IPU_VDIC:
  44                case IMX_MEDIA_GRP_ID_IPU_IC_PRP:
  45                case IMX_MEDIA_GRP_ID_IPU_IC_PRPENC:
  46                case IMX_MEDIA_GRP_ID_IPU_IC_PRPVF:
  47                        /*
  48                         * links have already been created for the
  49                         * sync-registered subdevs.
  50                         */
  51                        break;
  52                case IMX_MEDIA_GRP_ID_IPU_CSI0:
  53                case IMX_MEDIA_GRP_ID_IPU_CSI1:
  54                case IMX_MEDIA_GRP_ID_CSI:
  55                        imx_media_create_csi_of_links(imxmd, sd);
  56                        break;
  57                default:
  58                        /*
  59                         * if this subdev has fwnode links, create media
  60                         * links for them.
  61                         */
  62                        imx_media_create_of_links(imxmd, sd);
  63                        break;
  64                }
  65        }
  66
  67        return 0;
  68}
  69
  70/*
  71 * adds given video device to given imx-media source pad vdev list.
  72 * Continues upstream from the pad entity's sink pads.
  73 */
  74static int imx_media_add_vdev_to_pad(struct imx_media_dev *imxmd,
  75                                     struct imx_media_video_dev *vdev,
  76                                     struct media_pad *srcpad)
  77{
  78        struct media_entity *entity = srcpad->entity;
  79        struct imx_media_pad_vdev *pad_vdev;
  80        struct list_head *pad_vdev_list;
  81        struct media_link *link;
  82        struct v4l2_subdev *sd;
  83        int i, ret;
  84
  85        /* skip this entity if not a v4l2_subdev */
  86        if (!is_media_entity_v4l2_subdev(entity))
  87                return 0;
  88
  89        sd = media_entity_to_v4l2_subdev(entity);
  90
  91        pad_vdev_list = to_pad_vdev_list(sd, srcpad->index);
  92        if (!pad_vdev_list) {
  93                v4l2_warn(&imxmd->v4l2_dev, "%s:%u has no vdev list!\n",
  94                          entity->name, srcpad->index);
  95                /*
  96                 * shouldn't happen, but no reason to fail driver load,
  97                 * just skip this entity.
  98                 */
  99                return 0;
 100        }
 101
 102        /* just return if we've been here before */
 103        list_for_each_entry(pad_vdev, pad_vdev_list, list) {
 104                if (pad_vdev->vdev == vdev)
 105                        return 0;
 106        }
 107
 108        dev_dbg(imxmd->md.dev, "adding %s to pad %s:%u\n",
 109                vdev->vfd->entity.name, entity->name, srcpad->index);
 110
 111        pad_vdev = devm_kzalloc(imxmd->md.dev, sizeof(*pad_vdev), GFP_KERNEL);
 112        if (!pad_vdev)
 113                return -ENOMEM;
 114
 115        /* attach this vdev to this pad */
 116        pad_vdev->vdev = vdev;
 117        list_add_tail(&pad_vdev->list, pad_vdev_list);
 118
 119        /* move upstream from this entity's sink pads */
 120        for (i = 0; i < entity->num_pads; i++) {
 121                struct media_pad *pad = &entity->pads[i];
 122
 123                if (!(pad->flags & MEDIA_PAD_FL_SINK))
 124                        continue;
 125
 126                list_for_each_entry(link, &entity->links, list) {
 127                        if (link->sink != pad)
 128                                continue;
 129                        ret = imx_media_add_vdev_to_pad(imxmd, vdev,
 130                                                        link->source);
 131                        if (ret)
 132                                return ret;
 133                }
 134        }
 135
 136        return 0;
 137}
 138
 139/*
 140 * For every subdevice, allocate an array of list_head's, one list_head
 141 * for each pad, to hold the list of video devices reachable from that
 142 * pad.
 143 */
 144static int imx_media_alloc_pad_vdev_lists(struct imx_media_dev *imxmd)
 145{
 146        struct list_head *vdev_lists;
 147        struct media_entity *entity;
 148        struct v4l2_subdev *sd;
 149        int i;
 150
 151        list_for_each_entry(sd, &imxmd->v4l2_dev.subdevs, list) {
 152                entity = &sd->entity;
 153                vdev_lists = devm_kcalloc(imxmd->md.dev,
 154                                          entity->num_pads, sizeof(*vdev_lists),
 155                                          GFP_KERNEL);
 156                if (!vdev_lists)
 157                        return -ENOMEM;
 158
 159                /* attach to the subdev's host private pointer */
 160                sd->host_priv = vdev_lists;
 161
 162                for (i = 0; i < entity->num_pads; i++)
 163                        INIT_LIST_HEAD(to_pad_vdev_list(sd, i));
 164        }
 165
 166        return 0;
 167}
 168
 169/* form the vdev lists in all imx-media source pads */
 170static int imx_media_create_pad_vdev_lists(struct imx_media_dev *imxmd)
 171{
 172        struct imx_media_video_dev *vdev;
 173        struct media_link *link;
 174        int ret;
 175
 176        ret = imx_media_alloc_pad_vdev_lists(imxmd);
 177        if (ret)
 178                return ret;
 179
 180        list_for_each_entry(vdev, &imxmd->vdev_list, list) {
 181                link = list_first_entry(&vdev->vfd->entity.links,
 182                                        struct media_link, list);
 183                ret = imx_media_add_vdev_to_pad(imxmd, vdev, link->source);
 184                if (ret)
 185                        return ret;
 186        }
 187
 188        return 0;
 189}
 190
 191/* async subdev complete notifier */
 192int imx_media_probe_complete(struct v4l2_async_notifier *notifier)
 193{
 194        struct imx_media_dev *imxmd = notifier2dev(notifier);
 195        int ret;
 196
 197        mutex_lock(&imxmd->mutex);
 198
 199        ret = imx_media_create_links(notifier);
 200        if (ret)
 201                goto unlock;
 202
 203        ret = imx_media_create_pad_vdev_lists(imxmd);
 204        if (ret)
 205                goto unlock;
 206
 207        ret = v4l2_device_register_subdev_nodes(&imxmd->v4l2_dev);
 208unlock:
 209        mutex_unlock(&imxmd->mutex);
 210        if (ret)
 211                return ret;
 212
 213        return media_device_register(&imxmd->md);
 214}
 215EXPORT_SYMBOL_GPL(imx_media_probe_complete);
 216
 217/*
 218 * adds controls to a video device from an entity subdevice.
 219 * Continues upstream from the entity's sink pads.
 220 */
 221static int imx_media_inherit_controls(struct imx_media_dev *imxmd,
 222                                      struct video_device *vfd,
 223                                      struct media_entity *entity)
 224{
 225        int i, ret = 0;
 226
 227        if (is_media_entity_v4l2_subdev(entity)) {
 228                struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
 229
 230                dev_dbg(imxmd->md.dev,
 231                        "adding controls to %s from %s\n",
 232                        vfd->entity.name, sd->entity.name);
 233
 234                ret = v4l2_ctrl_add_handler(vfd->ctrl_handler,
 235                                            sd->ctrl_handler,
 236                                            NULL, true);
 237                if (ret)
 238                        return ret;
 239        }
 240
 241        /* move upstream */
 242        for (i = 0; i < entity->num_pads; i++) {
 243                struct media_pad *pad, *spad = &entity->pads[i];
 244
 245                if (!(spad->flags & MEDIA_PAD_FL_SINK))
 246                        continue;
 247
 248                pad = media_entity_remote_pad(spad);
 249                if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
 250                        continue;
 251
 252                ret = imx_media_inherit_controls(imxmd, vfd, pad->entity);
 253                if (ret)
 254                        break;
 255        }
 256
 257        return ret;
 258}
 259
 260static int imx_media_link_notify(struct media_link *link, u32 flags,
 261                                 unsigned int notification)
 262{
 263        struct imx_media_dev *imxmd = container_of(link->graph_obj.mdev,
 264                                                   struct imx_media_dev, md);
 265        struct media_entity *source = link->source->entity;
 266        struct imx_media_pad_vdev *pad_vdev;
 267        struct list_head *pad_vdev_list;
 268        struct video_device *vfd;
 269        struct v4l2_subdev *sd;
 270        int pad_idx, ret;
 271
 272        ret = v4l2_pipeline_link_notify(link, flags, notification);
 273        if (ret)
 274                return ret;
 275
 276        /* don't bother if source is not a subdev */
 277        if (!is_media_entity_v4l2_subdev(source))
 278                return 0;
 279
 280        sd = media_entity_to_v4l2_subdev(source);
 281        pad_idx = link->source->index;
 282
 283        pad_vdev_list = to_pad_vdev_list(sd, pad_idx);
 284        if (!pad_vdev_list) {
 285                /* nothing to do if source sd has no pad vdev list */
 286                return 0;
 287        }
 288
 289        /*
 290         * Before disabling a link, reset controls for all video
 291         * devices reachable from this link.
 292         *
 293         * After enabling a link, refresh controls for all video
 294         * devices reachable from this link.
 295         */
 296        if (notification == MEDIA_DEV_NOTIFY_PRE_LINK_CH &&
 297            !(flags & MEDIA_LNK_FL_ENABLED)) {
 298                list_for_each_entry(pad_vdev, pad_vdev_list, list) {
 299                        vfd = pad_vdev->vdev->vfd;
 300                        dev_dbg(imxmd->md.dev,
 301                                "reset controls for %s\n",
 302                                vfd->entity.name);
 303                        v4l2_ctrl_handler_free(vfd->ctrl_handler);
 304                        v4l2_ctrl_handler_init(vfd->ctrl_handler, 0);
 305                }
 306        } else if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH &&
 307                   (link->flags & MEDIA_LNK_FL_ENABLED)) {
 308                list_for_each_entry(pad_vdev, pad_vdev_list, list) {
 309                        vfd = pad_vdev->vdev->vfd;
 310                        dev_dbg(imxmd->md.dev,
 311                                "refresh controls for %s\n",
 312                                vfd->entity.name);
 313                        ret = imx_media_inherit_controls(imxmd, vfd,
 314                                                         &vfd->entity);
 315                        if (ret)
 316                                break;
 317                }
 318        }
 319
 320        return ret;
 321}
 322
 323static void imx_media_notify(struct v4l2_subdev *sd, unsigned int notification,
 324                             void *arg)
 325{
 326        struct media_entity *entity = &sd->entity;
 327        int i;
 328
 329        if (notification != V4L2_DEVICE_NOTIFY_EVENT)
 330                return;
 331
 332        for (i = 0; i < entity->num_pads; i++) {
 333                struct media_pad *pad = &entity->pads[i];
 334                struct imx_media_pad_vdev *pad_vdev;
 335                struct list_head *pad_vdev_list;
 336
 337                pad_vdev_list = to_pad_vdev_list(sd, pad->index);
 338                if (!pad_vdev_list)
 339                        continue;
 340                list_for_each_entry(pad_vdev, pad_vdev_list, list)
 341                        v4l2_event_queue(pad_vdev->vdev->vfd, arg);
 342        }
 343}
 344
 345static const struct v4l2_async_notifier_operations imx_media_notifier_ops = {
 346        .bound = imx_media_subdev_bound,
 347        .complete = imx_media_probe_complete,
 348};
 349
 350static const struct media_device_ops imx_media_md_ops = {
 351        .link_notify = imx_media_link_notify,
 352};
 353
 354struct imx_media_dev *imx_media_dev_init(struct device *dev,
 355                                         const struct media_device_ops *ops)
 356{
 357        struct imx_media_dev *imxmd;
 358        int ret;
 359
 360        imxmd = devm_kzalloc(dev, sizeof(*imxmd), GFP_KERNEL);
 361        if (!imxmd)
 362                return ERR_PTR(-ENOMEM);
 363
 364        dev_set_drvdata(dev, imxmd);
 365
 366        strscpy(imxmd->md.model, "imx-media", sizeof(imxmd->md.model));
 367        imxmd->md.ops = ops ? ops : &imx_media_md_ops;
 368        imxmd->md.dev = dev;
 369
 370        mutex_init(&imxmd->mutex);
 371
 372        imxmd->v4l2_dev.mdev = &imxmd->md;
 373        imxmd->v4l2_dev.notify = imx_media_notify;
 374        strscpy(imxmd->v4l2_dev.name, "imx-media",
 375                sizeof(imxmd->v4l2_dev.name));
 376
 377        media_device_init(&imxmd->md);
 378
 379        ret = v4l2_device_register(dev, &imxmd->v4l2_dev);
 380        if (ret < 0) {
 381                v4l2_err(&imxmd->v4l2_dev,
 382                         "Failed to register v4l2_device: %d\n", ret);
 383                goto cleanup;
 384        }
 385
 386        INIT_LIST_HEAD(&imxmd->vdev_list);
 387
 388        v4l2_async_notifier_init(&imxmd->notifier);
 389
 390        return imxmd;
 391
 392cleanup:
 393        media_device_cleanup(&imxmd->md);
 394
 395        return ERR_PTR(ret);
 396}
 397EXPORT_SYMBOL_GPL(imx_media_dev_init);
 398
 399int imx_media_dev_notifier_register(struct imx_media_dev *imxmd,
 400                            const struct v4l2_async_notifier_operations *ops)
 401{
 402        int ret;
 403
 404        /* no subdevs? just bail */
 405        if (list_empty(&imxmd->notifier.asd_list)) {
 406                v4l2_err(&imxmd->v4l2_dev, "no subdevs\n");
 407                return -ENODEV;
 408        }
 409
 410        /* prepare the async subdev notifier and register it */
 411        imxmd->notifier.ops = ops ? ops : &imx_media_notifier_ops;
 412        ret = v4l2_async_notifier_register(&imxmd->v4l2_dev,
 413                                           &imxmd->notifier);
 414        if (ret) {
 415                v4l2_err(&imxmd->v4l2_dev,
 416                         "v4l2_async_notifier_register failed with %d\n", ret);
 417                return ret;
 418        }
 419
 420        return 0;
 421}
 422EXPORT_SYMBOL_GPL(imx_media_dev_notifier_register);
 423