linux/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright (C) 2016 NextThing Co
   4 * Copyright (C) 2016-2019 Bootlin
   5 *
   6 * Author: Maxime Ripard <maxime.ripard@bootlin.com>
   7 */
   8
   9#include <linux/clk.h>
  10#include <linux/dma-mapping.h>
  11#include <linux/interrupt.h>
  12#include <linux/module.h>
  13#include <linux/mutex.h>
  14#include <linux/of.h>
  15#include <linux/of_device.h>
  16#include <linux/of_graph.h>
  17#include <linux/platform_device.h>
  18#include <linux/pm_runtime.h>
  19#include <linux/reset.h>
  20#include <linux/videodev2.h>
  21
  22#include <media/v4l2-dev.h>
  23#include <media/v4l2-device.h>
  24#include <media/v4l2-fwnode.h>
  25#include <media/v4l2-ioctl.h>
  26#include <media/v4l2-mediabus.h>
  27
  28#include <media/videobuf2-core.h>
  29#include <media/videobuf2-dma-contig.h>
  30
  31#include "sun4i_csi.h"
  32
  33struct sun4i_csi_traits {
  34        unsigned int channels;
  35        unsigned int max_width;
  36        bool has_isp;
  37};
  38
  39static const struct media_entity_operations sun4i_csi_video_entity_ops = {
  40        .link_validate = v4l2_subdev_link_validate,
  41};
  42
  43static int sun4i_csi_notify_bound(struct v4l2_async_notifier *notifier,
  44                                  struct v4l2_subdev *subdev,
  45                                  struct v4l2_async_subdev *asd)
  46{
  47        struct sun4i_csi *csi = container_of(notifier, struct sun4i_csi,
  48                                             notifier);
  49
  50        csi->src_subdev = subdev;
  51        csi->src_pad = media_entity_get_fwnode_pad(&subdev->entity,
  52                                                   subdev->fwnode,
  53                                                   MEDIA_PAD_FL_SOURCE);
  54        if (csi->src_pad < 0) {
  55                dev_err(csi->dev, "Couldn't find output pad for subdev %s\n",
  56                        subdev->name);
  57                return csi->src_pad;
  58        }
  59
  60        dev_dbg(csi->dev, "Bound %s pad: %d\n", subdev->name, csi->src_pad);
  61        return 0;
  62}
  63
  64static int sun4i_csi_notify_complete(struct v4l2_async_notifier *notifier)
  65{
  66        struct sun4i_csi *csi = container_of(notifier, struct sun4i_csi,
  67                                             notifier);
  68        struct v4l2_subdev *subdev = &csi->subdev;
  69        struct video_device *vdev = &csi->vdev;
  70        int ret;
  71
  72        ret = v4l2_device_register_subdev(&csi->v4l, subdev);
  73        if (ret < 0)
  74                return ret;
  75
  76        ret = sun4i_csi_v4l2_register(csi);
  77        if (ret < 0)
  78                return ret;
  79
  80        ret = media_device_register(&csi->mdev);
  81        if (ret)
  82                return ret;
  83
  84        /* Create link from subdev to main device */
  85        ret = media_create_pad_link(&subdev->entity, CSI_SUBDEV_SOURCE,
  86                                    &vdev->entity, 0,
  87                                    MEDIA_LNK_FL_ENABLED |
  88                                    MEDIA_LNK_FL_IMMUTABLE);
  89        if (ret)
  90                goto err_clean_media;
  91
  92        ret = media_create_pad_link(&csi->src_subdev->entity, csi->src_pad,
  93                                    &subdev->entity, CSI_SUBDEV_SINK,
  94                                    MEDIA_LNK_FL_ENABLED |
  95                                    MEDIA_LNK_FL_IMMUTABLE);
  96        if (ret)
  97                goto err_clean_media;
  98
  99        ret = v4l2_device_register_subdev_nodes(&csi->v4l);
 100        if (ret < 0)
 101                goto err_clean_media;
 102
 103        return 0;
 104
 105err_clean_media:
 106        media_device_unregister(&csi->mdev);
 107
 108        return ret;
 109}
 110
 111static const struct v4l2_async_notifier_operations sun4i_csi_notify_ops = {
 112        .bound          = sun4i_csi_notify_bound,
 113        .complete       = sun4i_csi_notify_complete,
 114};
 115
 116static int sun4i_csi_notifier_init(struct sun4i_csi *csi)
 117{
 118        struct v4l2_fwnode_endpoint vep = {
 119                .bus_type = V4L2_MBUS_PARALLEL,
 120        };
 121        struct fwnode_handle *ep;
 122        int ret;
 123
 124        v4l2_async_notifier_init(&csi->notifier);
 125
 126        ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0,
 127                                             FWNODE_GRAPH_ENDPOINT_NEXT);
 128        if (!ep)
 129                return -EINVAL;
 130
 131        ret = v4l2_fwnode_endpoint_parse(ep, &vep);
 132        if (ret)
 133                goto out;
 134
 135        csi->bus = vep.bus.parallel;
 136
 137        ret = v4l2_async_notifier_add_fwnode_remote_subdev(&csi->notifier,
 138                                                           ep, &csi->asd);
 139        if (ret)
 140                goto out;
 141
 142        csi->notifier.ops = &sun4i_csi_notify_ops;
 143
 144out:
 145        fwnode_handle_put(ep);
 146        return ret;
 147}
 148
 149static int sun4i_csi_probe(struct platform_device *pdev)
 150{
 151        struct v4l2_subdev *subdev;
 152        struct video_device *vdev;
 153        struct sun4i_csi *csi;
 154        struct resource *res;
 155        int ret;
 156        int irq;
 157
 158        csi = devm_kzalloc(&pdev->dev, sizeof(*csi), GFP_KERNEL);
 159        if (!csi)
 160                return -ENOMEM;
 161        platform_set_drvdata(pdev, csi);
 162        csi->dev = &pdev->dev;
 163        subdev = &csi->subdev;
 164        vdev = &csi->vdev;
 165
 166        csi->traits = of_device_get_match_data(&pdev->dev);
 167        if (!csi->traits)
 168                return -EINVAL;
 169
 170        csi->mdev.dev = csi->dev;
 171        strscpy(csi->mdev.model, "Allwinner Video Capture Device",
 172                sizeof(csi->mdev.model));
 173        csi->mdev.hw_revision = 0;
 174        snprintf(csi->mdev.bus_info, sizeof(csi->mdev.bus_info), "platform:%s",
 175                 dev_name(csi->dev));
 176        media_device_init(&csi->mdev);
 177        csi->v4l.mdev = &csi->mdev;
 178
 179        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 180        csi->regs = devm_ioremap_resource(&pdev->dev, res);
 181        if (IS_ERR(csi->regs))
 182                return PTR_ERR(csi->regs);
 183
 184        irq = platform_get_irq(pdev, 0);
 185        if (irq < 0)
 186                return irq;
 187
 188        csi->bus_clk = devm_clk_get(&pdev->dev, "bus");
 189        if (IS_ERR(csi->bus_clk)) {
 190                dev_err(&pdev->dev, "Couldn't get our bus clock\n");
 191                return PTR_ERR(csi->bus_clk);
 192        }
 193
 194        if (csi->traits->has_isp) {
 195                csi->isp_clk = devm_clk_get(&pdev->dev, "isp");
 196                if (IS_ERR(csi->isp_clk)) {
 197                        dev_err(&pdev->dev, "Couldn't get our ISP clock\n");
 198                        return PTR_ERR(csi->isp_clk);
 199                }
 200        }
 201
 202        csi->ram_clk = devm_clk_get(&pdev->dev, "ram");
 203        if (IS_ERR(csi->ram_clk)) {
 204                dev_err(&pdev->dev, "Couldn't get our ram clock\n");
 205                return PTR_ERR(csi->ram_clk);
 206        }
 207
 208        csi->rst = devm_reset_control_get(&pdev->dev, NULL);
 209        if (IS_ERR(csi->rst)) {
 210                dev_err(&pdev->dev, "Couldn't get our reset line\n");
 211                return PTR_ERR(csi->rst);
 212        }
 213
 214        /* Initialize subdev */
 215        v4l2_subdev_init(subdev, &sun4i_csi_subdev_ops);
 216        subdev->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
 217        subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
 218        subdev->owner = THIS_MODULE;
 219        snprintf(subdev->name, sizeof(subdev->name), "sun4i-csi-0");
 220        v4l2_set_subdevdata(subdev, csi);
 221
 222        csi->subdev_pads[CSI_SUBDEV_SINK].flags =
 223                MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
 224        csi->subdev_pads[CSI_SUBDEV_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
 225        ret = media_entity_pads_init(&subdev->entity, CSI_SUBDEV_PADS,
 226                                     csi->subdev_pads);
 227        if (ret < 0)
 228                return ret;
 229
 230        csi->vdev_pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
 231        vdev->entity.ops = &sun4i_csi_video_entity_ops;
 232        ret = media_entity_pads_init(&vdev->entity, 1, &csi->vdev_pad);
 233        if (ret < 0)
 234                return ret;
 235
 236        ret = sun4i_csi_dma_register(csi, irq);
 237        if (ret)
 238                goto err_clean_pad;
 239
 240        ret = sun4i_csi_notifier_init(csi);
 241        if (ret)
 242                goto err_unregister_media;
 243
 244        ret = v4l2_async_notifier_register(&csi->v4l, &csi->notifier);
 245        if (ret) {
 246                dev_err(csi->dev, "Couldn't register our notifier.\n");
 247                goto err_unregister_media;
 248        }
 249
 250        pm_runtime_enable(&pdev->dev);
 251
 252        return 0;
 253
 254err_unregister_media:
 255        media_device_unregister(&csi->mdev);
 256        sun4i_csi_dma_unregister(csi);
 257
 258err_clean_pad:
 259        media_device_cleanup(&csi->mdev);
 260
 261        return ret;
 262}
 263
 264static int sun4i_csi_remove(struct platform_device *pdev)
 265{
 266        struct sun4i_csi *csi = platform_get_drvdata(pdev);
 267
 268        v4l2_async_notifier_unregister(&csi->notifier);
 269        v4l2_async_notifier_cleanup(&csi->notifier);
 270        vb2_video_unregister_device(&csi->vdev);
 271        media_device_unregister(&csi->mdev);
 272        sun4i_csi_dma_unregister(csi);
 273        media_device_cleanup(&csi->mdev);
 274
 275        return 0;
 276}
 277
 278static const struct sun4i_csi_traits sun4i_a10_csi1_traits = {
 279        .channels = 1,
 280        .max_width = 24,
 281        .has_isp = false,
 282};
 283
 284static const struct sun4i_csi_traits sun7i_a20_csi0_traits = {
 285        .channels = 4,
 286        .max_width = 16,
 287        .has_isp = true,
 288};
 289
 290static const struct of_device_id sun4i_csi_of_match[] = {
 291        { .compatible = "allwinner,sun4i-a10-csi1", .data = &sun4i_a10_csi1_traits },
 292        { .compatible = "allwinner,sun7i-a20-csi0", .data = &sun7i_a20_csi0_traits },
 293        { /* Sentinel */ }
 294};
 295MODULE_DEVICE_TABLE(of, sun4i_csi_of_match);
 296
 297static int __maybe_unused sun4i_csi_runtime_resume(struct device *dev)
 298{
 299        struct sun4i_csi *csi = dev_get_drvdata(dev);
 300
 301        reset_control_deassert(csi->rst);
 302        clk_prepare_enable(csi->bus_clk);
 303        clk_prepare_enable(csi->ram_clk);
 304        clk_set_rate(csi->isp_clk, 80000000);
 305        clk_prepare_enable(csi->isp_clk);
 306
 307        writel(1, csi->regs + CSI_EN_REG);
 308
 309        return 0;
 310}
 311
 312static int __maybe_unused sun4i_csi_runtime_suspend(struct device *dev)
 313{
 314        struct sun4i_csi *csi = dev_get_drvdata(dev);
 315
 316        clk_disable_unprepare(csi->isp_clk);
 317        clk_disable_unprepare(csi->ram_clk);
 318        clk_disable_unprepare(csi->bus_clk);
 319
 320        reset_control_assert(csi->rst);
 321
 322        return 0;
 323}
 324
 325static const struct dev_pm_ops sun4i_csi_pm_ops = {
 326        SET_RUNTIME_PM_OPS(sun4i_csi_runtime_suspend,
 327                           sun4i_csi_runtime_resume,
 328                           NULL)
 329};
 330
 331static struct platform_driver sun4i_csi_driver = {
 332        .probe  = sun4i_csi_probe,
 333        .remove = sun4i_csi_remove,
 334        .driver = {
 335                .name           = "sun4i-csi",
 336                .of_match_table = sun4i_csi_of_match,
 337                .pm             = &sun4i_csi_pm_ops,
 338        },
 339};
 340module_platform_driver(sun4i_csi_driver);
 341
 342MODULE_DESCRIPTION("Allwinner A10 Camera Sensor Interface driver");
 343MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>");
 344MODULE_LICENSE("GPL");
 345