linux/drivers/media/platform/rcar-vin/rcar-core.c
<<
>>
Prefs
   1/*
   2 * Driver for Renesas R-Car VIN
   3 *
   4 * Copyright (C) 2016 Renesas Electronics Corp.
   5 * Copyright (C) 2011-2013 Renesas Solutions Corp.
   6 * Copyright (C) 2013 Cogent Embedded, Inc., <source@cogentembedded.com>
   7 * Copyright (C) 2008 Magnus Damm
   8 *
   9 * Based on the soc-camera rcar_vin driver
  10 *
  11 * This program is free software; you can redistribute  it and/or modify it
  12 * under  the terms of  the GNU General  Public License as published by the
  13 * Free Software Foundation;  either version 2 of the  License, or (at your
  14 * option) any later version.
  15 */
  16
  17#include <linux/module.h>
  18#include <linux/of.h>
  19#include <linux/of_device.h>
  20#include <linux/of_graph.h>
  21#include <linux/platform_device.h>
  22#include <linux/pm_runtime.h>
  23
  24#include <media/v4l2-async.h>
  25#include <media/v4l2-fwnode.h>
  26
  27#include "rcar-vin.h"
  28
  29/* -----------------------------------------------------------------------------
  30 * Async notifier
  31 */
  32
  33#define notifier_to_vin(n) container_of(n, struct rvin_dev, notifier)
  34
  35static int rvin_find_pad(struct v4l2_subdev *sd, int direction)
  36{
  37        unsigned int pad;
  38
  39        if (sd->entity.num_pads <= 1)
  40                return 0;
  41
  42        for (pad = 0; pad < sd->entity.num_pads; pad++)
  43                if (sd->entity.pads[pad].flags & direction)
  44                        return pad;
  45
  46        return -EINVAL;
  47}
  48
  49static bool rvin_mbus_supported(struct rvin_graph_entity *entity)
  50{
  51        struct v4l2_subdev *sd = entity->subdev;
  52        struct v4l2_subdev_mbus_code_enum code = {
  53                .which = V4L2_SUBDEV_FORMAT_ACTIVE,
  54        };
  55
  56        code.index = 0;
  57        code.pad = entity->source_pad;
  58        while (!v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code)) {
  59                code.index++;
  60                switch (code.code) {
  61                case MEDIA_BUS_FMT_YUYV8_1X16:
  62                case MEDIA_BUS_FMT_UYVY8_2X8:
  63                case MEDIA_BUS_FMT_UYVY10_2X10:
  64                case MEDIA_BUS_FMT_RGB888_1X24:
  65                        entity->code = code.code;
  66                        return true;
  67                default:
  68                        break;
  69                }
  70        }
  71
  72        return false;
  73}
  74
  75static int rvin_digital_notify_complete(struct v4l2_async_notifier *notifier)
  76{
  77        struct rvin_dev *vin = notifier_to_vin(notifier);
  78        int ret;
  79
  80        /* Verify subdevices mbus format */
  81        if (!rvin_mbus_supported(vin->digital)) {
  82                vin_err(vin, "Unsupported media bus format for %s\n",
  83                        vin->digital->subdev->name);
  84                return -EINVAL;
  85        }
  86
  87        vin_dbg(vin, "Found media bus format for %s: %d\n",
  88                vin->digital->subdev->name, vin->digital->code);
  89
  90        ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev);
  91        if (ret < 0) {
  92                vin_err(vin, "Failed to register subdev nodes\n");
  93                return ret;
  94        }
  95
  96        return rvin_v4l2_probe(vin);
  97}
  98
  99static void rvin_digital_notify_unbind(struct v4l2_async_notifier *notifier,
 100                                       struct v4l2_subdev *subdev,
 101                                       struct v4l2_async_subdev *asd)
 102{
 103        struct rvin_dev *vin = notifier_to_vin(notifier);
 104
 105        vin_dbg(vin, "unbind digital subdev %s\n", subdev->name);
 106        rvin_v4l2_remove(vin);
 107        vin->digital->subdev = NULL;
 108}
 109
 110static int rvin_digital_notify_bound(struct v4l2_async_notifier *notifier,
 111                                     struct v4l2_subdev *subdev,
 112                                     struct v4l2_async_subdev *asd)
 113{
 114        struct rvin_dev *vin = notifier_to_vin(notifier);
 115        int ret;
 116
 117        v4l2_set_subdev_hostdata(subdev, vin);
 118
 119        /* Find source and sink pad of remote subdevice */
 120
 121        ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SOURCE);
 122        if (ret < 0)
 123                return ret;
 124        vin->digital->source_pad = ret;
 125
 126        ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SINK);
 127        vin->digital->sink_pad = ret < 0 ? 0 : ret;
 128
 129        vin->digital->subdev = subdev;
 130
 131        vin_dbg(vin, "bound subdev %s source pad: %u sink pad: %u\n",
 132                subdev->name, vin->digital->source_pad,
 133                vin->digital->sink_pad);
 134
 135        return 0;
 136}
 137static const struct v4l2_async_notifier_operations rvin_digital_notify_ops = {
 138        .bound = rvin_digital_notify_bound,
 139        .unbind = rvin_digital_notify_unbind,
 140        .complete = rvin_digital_notify_complete,
 141};
 142
 143
 144static int rvin_digital_parse_v4l2(struct device *dev,
 145                                   struct v4l2_fwnode_endpoint *vep,
 146                                   struct v4l2_async_subdev *asd)
 147{
 148        struct rvin_dev *vin = dev_get_drvdata(dev);
 149        struct rvin_graph_entity *rvge =
 150                container_of(asd, struct rvin_graph_entity, asd);
 151
 152        if (vep->base.port || vep->base.id)
 153                return -ENOTCONN;
 154
 155        rvge->mbus_cfg.type = vep->bus_type;
 156
 157        switch (rvge->mbus_cfg.type) {
 158        case V4L2_MBUS_PARALLEL:
 159                vin_dbg(vin, "Found PARALLEL media bus\n");
 160                rvge->mbus_cfg.flags = vep->bus.parallel.flags;
 161                break;
 162        case V4L2_MBUS_BT656:
 163                vin_dbg(vin, "Found BT656 media bus\n");
 164                rvge->mbus_cfg.flags = 0;
 165                break;
 166        default:
 167                vin_err(vin, "Unknown media bus type\n");
 168                return -EINVAL;
 169        }
 170
 171        vin->digital = rvge;
 172
 173        return 0;
 174}
 175
 176static int rvin_digital_graph_init(struct rvin_dev *vin)
 177{
 178        int ret;
 179
 180        ret = v4l2_async_notifier_parse_fwnode_endpoints(
 181                vin->dev, &vin->notifier,
 182                sizeof(struct rvin_graph_entity), rvin_digital_parse_v4l2);
 183        if (ret)
 184                return ret;
 185
 186        if (!vin->digital)
 187                return -ENODEV;
 188
 189        vin_dbg(vin, "Found digital subdevice %pOF\n",
 190                to_of_node(vin->digital->asd.match.fwnode));
 191
 192        vin->notifier.ops = &rvin_digital_notify_ops;
 193        ret = v4l2_async_notifier_register(&vin->v4l2_dev, &vin->notifier);
 194        if (ret < 0) {
 195                vin_err(vin, "Notifier registration failed\n");
 196                return ret;
 197        }
 198
 199        return 0;
 200}
 201
 202/* -----------------------------------------------------------------------------
 203 * Platform Device Driver
 204 */
 205
 206static const struct of_device_id rvin_of_id_table[] = {
 207        { .compatible = "renesas,vin-r8a7794", .data = (void *)RCAR_GEN2 },
 208        { .compatible = "renesas,vin-r8a7793", .data = (void *)RCAR_GEN2 },
 209        { .compatible = "renesas,vin-r8a7791", .data = (void *)RCAR_GEN2 },
 210        { .compatible = "renesas,vin-r8a7790", .data = (void *)RCAR_GEN2 },
 211        { .compatible = "renesas,vin-r8a7779", .data = (void *)RCAR_H1 },
 212        { .compatible = "renesas,vin-r8a7778", .data = (void *)RCAR_M1 },
 213        { .compatible = "renesas,rcar-gen2-vin", .data = (void *)RCAR_GEN2 },
 214        { },
 215};
 216MODULE_DEVICE_TABLE(of, rvin_of_id_table);
 217
 218static int rcar_vin_probe(struct platform_device *pdev)
 219{
 220        const struct of_device_id *match;
 221        struct rvin_dev *vin;
 222        struct resource *mem;
 223        int irq, ret;
 224
 225        vin = devm_kzalloc(&pdev->dev, sizeof(*vin), GFP_KERNEL);
 226        if (!vin)
 227                return -ENOMEM;
 228
 229        match = of_match_device(of_match_ptr(rvin_of_id_table), &pdev->dev);
 230        if (!match)
 231                return -ENODEV;
 232
 233        vin->dev = &pdev->dev;
 234        vin->chip = (enum chip_id)match->data;
 235
 236        mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 237        if (mem == NULL)
 238                return -EINVAL;
 239
 240        vin->base = devm_ioremap_resource(vin->dev, mem);
 241        if (IS_ERR(vin->base))
 242                return PTR_ERR(vin->base);
 243
 244        irq = platform_get_irq(pdev, 0);
 245        if (irq < 0)
 246                return irq;
 247
 248        ret = rvin_dma_probe(vin, irq);
 249        if (ret)
 250                return ret;
 251
 252        platform_set_drvdata(pdev, vin);
 253
 254        ret = rvin_digital_graph_init(vin);
 255        if (ret < 0)
 256                goto error;
 257
 258        pm_suspend_ignore_children(&pdev->dev, true);
 259        pm_runtime_enable(&pdev->dev);
 260
 261        return 0;
 262error:
 263        rvin_dma_remove(vin);
 264        v4l2_async_notifier_cleanup(&vin->notifier);
 265
 266        return ret;
 267}
 268
 269static int rcar_vin_remove(struct platform_device *pdev)
 270{
 271        struct rvin_dev *vin = platform_get_drvdata(pdev);
 272
 273        pm_runtime_disable(&pdev->dev);
 274
 275        v4l2_async_notifier_unregister(&vin->notifier);
 276        v4l2_async_notifier_cleanup(&vin->notifier);
 277
 278        rvin_dma_remove(vin);
 279
 280        return 0;
 281}
 282
 283static struct platform_driver rcar_vin_driver = {
 284        .driver = {
 285                .name = "rcar-vin",
 286                .of_match_table = rvin_of_id_table,
 287        },
 288        .probe = rcar_vin_probe,
 289        .remove = rcar_vin_remove,
 290};
 291
 292module_platform_driver(rcar_vin_driver);
 293
 294MODULE_AUTHOR("Niklas Söderlund <niklas.soderlund@ragnatech.se>");
 295MODULE_DESCRIPTION("Renesas R-Car VIN camera host driver");
 296MODULE_LICENSE("GPL v2");
 297