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 v4l2_async_subdev *asd;
 122        struct fwnode_handle *ep;
 123        int ret;
 124
 125        v4l2_async_notifier_init(&csi->notifier);
 126
 127        ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0,
 128                                             FWNODE_GRAPH_ENDPOINT_NEXT);
 129        if (!ep)
 130                return -EINVAL;
 131
 132        ret = v4l2_fwnode_endpoint_parse(ep, &vep);
 133        if (ret)
 134                goto out;
 135
 136        csi->bus = vep.bus.parallel;
 137
 138        asd = v4l2_async_notifier_add_fwnode_remote_subdev(&csi->notifier, ep,
 139                                                           struct v4l2_async_subdev);
 140        if (IS_ERR(asd)) {
 141                ret = PTR_ERR(asd);
 142                goto out;
 143        }
 144
 145        csi->notifier.ops = &sun4i_csi_notify_ops;
 146
 147out:
 148        fwnode_handle_put(ep);
 149        return ret;
 150}
 151
 152static int sun4i_csi_probe(struct platform_device *pdev)
 153{
 154        struct v4l2_subdev *subdev;
 155        struct video_device *vdev;
 156        struct sun4i_csi *csi;
 157        struct resource *res;
 158        int ret;
 159        int irq;
 160
 161        csi = devm_kzalloc(&pdev->dev, sizeof(*csi), GFP_KERNEL);
 162        if (!csi)
 163                return -ENOMEM;
 164        platform_set_drvdata(pdev, csi);
 165        csi->dev = &pdev->dev;
 166        subdev = &csi->subdev;
 167        vdev = &csi->vdev;
 168
 169        csi->traits = of_device_get_match_data(&pdev->dev);
 170        if (!csi->traits)
 171                return -EINVAL;
 172
 173        csi->mdev.dev = csi->dev;
 174        strscpy(csi->mdev.model, "Allwinner Video Capture Device",
 175                sizeof(csi->mdev.model));
 176        csi->mdev.hw_revision = 0;
 177        snprintf(csi->mdev.bus_info, sizeof(csi->mdev.bus_info), "platform:%s",
 178                 dev_name(csi->dev));
 179        media_device_init(&csi->mdev);
 180        csi->v4l.mdev = &csi->mdev;
 181
 182        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 183        csi->regs = devm_ioremap_resource(&pdev->dev, res);
 184        if (IS_ERR(csi->regs))
 185                return PTR_ERR(csi->regs);
 186
 187        irq = platform_get_irq(pdev, 0);
 188        if (irq < 0)
 189                return irq;
 190
 191        csi->bus_clk = devm_clk_get(&pdev->dev, "bus");
 192        if (IS_ERR(csi->bus_clk)) {
 193                dev_err(&pdev->dev, "Couldn't get our bus clock\n");
 194                return PTR_ERR(csi->bus_clk);
 195        }
 196
 197        if (csi->traits->has_isp) {
 198                csi->isp_clk = devm_clk_get(&pdev->dev, "isp");
 199                if (IS_ERR(csi->isp_clk)) {
 200                        dev_err(&pdev->dev, "Couldn't get our ISP clock\n");
 201                        return PTR_ERR(csi->isp_clk);
 202                }
 203        }
 204
 205        csi->ram_clk = devm_clk_get(&pdev->dev, "ram");
 206        if (IS_ERR(csi->ram_clk)) {
 207                dev_err(&pdev->dev, "Couldn't get our ram clock\n");
 208                return PTR_ERR(csi->ram_clk);
 209        }
 210
 211        csi->rst = devm_reset_control_get(&pdev->dev, NULL);
 212        if (IS_ERR(csi->rst)) {
 213                dev_err(&pdev->dev, "Couldn't get our reset line\n");
 214                return PTR_ERR(csi->rst);
 215        }
 216
 217        /* Initialize subdev */
 218        v4l2_subdev_init(subdev, &sun4i_csi_subdev_ops);
 219        subdev->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
 220        subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
 221        subdev->owner = THIS_MODULE;
 222        snprintf(subdev->name, sizeof(subdev->name), "sun4i-csi-0");
 223        v4l2_set_subdevdata(subdev, csi);
 224
 225        csi->subdev_pads[CSI_SUBDEV_SINK].flags =
 226                MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
 227        csi->subdev_pads[CSI_SUBDEV_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
 228        ret = media_entity_pads_init(&subdev->entity, CSI_SUBDEV_PADS,
 229                                     csi->subdev_pads);
 230        if (ret < 0)
 231                return ret;
 232
 233        csi->vdev_pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
 234        vdev->entity.ops = &sun4i_csi_video_entity_ops;
 235        ret = media_entity_pads_init(&vdev->entity, 1, &csi->vdev_pad);
 236        if (ret < 0)
 237                return ret;
 238
 239        ret = sun4i_csi_dma_register(csi, irq);
 240        if (ret)
 241                goto err_clean_pad;
 242
 243        ret = sun4i_csi_notifier_init(csi);
 244        if (ret)
 245                goto err_unregister_media;
 246
 247        ret = v4l2_async_notifier_register(&csi->v4l, &csi->notifier);
 248        if (ret) {
 249                dev_err(csi->dev, "Couldn't register our notifier.\n");
 250                goto err_unregister_media;
 251        }
 252
 253        pm_runtime_enable(&pdev->dev);
 254
 255        return 0;
 256
 257err_unregister_media:
 258        media_device_unregister(&csi->mdev);
 259        sun4i_csi_dma_unregister(csi);
 260
 261err_clean_pad:
 262        media_device_cleanup(&csi->mdev);
 263
 264        return ret;
 265}
 266
 267static int sun4i_csi_remove(struct platform_device *pdev)
 268{
 269        struct sun4i_csi *csi = platform_get_drvdata(pdev);
 270
 271        v4l2_async_notifier_unregister(&csi->notifier);
 272        v4l2_async_notifier_cleanup(&csi->notifier);
 273        vb2_video_unregister_device(&csi->vdev);
 274        media_device_unregister(&csi->mdev);
 275        sun4i_csi_dma_unregister(csi);
 276        media_device_cleanup(&csi->mdev);
 277
 278        return 0;
 279}
 280
 281static const struct sun4i_csi_traits sun4i_a10_csi1_traits = {
 282        .channels = 1,
 283        .max_width = 24,
 284        .has_isp = false,
 285};
 286
 287static const struct sun4i_csi_traits sun7i_a20_csi0_traits = {
 288        .channels = 4,
 289        .max_width = 16,
 290        .has_isp = true,
 291};
 292
 293static const struct of_device_id sun4i_csi_of_match[] = {
 294        { .compatible = "allwinner,sun4i-a10-csi1", .data = &sun4i_a10_csi1_traits },
 295        { .compatible = "allwinner,sun7i-a20-csi0", .data = &sun7i_a20_csi0_traits },
 296        { /* Sentinel */ }
 297};
 298MODULE_DEVICE_TABLE(of, sun4i_csi_of_match);
 299
 300static int __maybe_unused sun4i_csi_runtime_resume(struct device *dev)
 301{
 302        struct sun4i_csi *csi = dev_get_drvdata(dev);
 303
 304        reset_control_deassert(csi->rst);
 305        clk_prepare_enable(csi->bus_clk);
 306        clk_prepare_enable(csi->ram_clk);
 307        clk_set_rate(csi->isp_clk, 80000000);
 308        clk_prepare_enable(csi->isp_clk);
 309
 310        writel(1, csi->regs + CSI_EN_REG);
 311
 312        return 0;
 313}
 314
 315static int __maybe_unused sun4i_csi_runtime_suspend(struct device *dev)
 316{
 317        struct sun4i_csi *csi = dev_get_drvdata(dev);
 318
 319        clk_disable_unprepare(csi->isp_clk);
 320        clk_disable_unprepare(csi->ram_clk);
 321        clk_disable_unprepare(csi->bus_clk);
 322
 323        reset_control_assert(csi->rst);
 324
 325        return 0;
 326}
 327
 328static const struct dev_pm_ops sun4i_csi_pm_ops = {
 329        SET_RUNTIME_PM_OPS(sun4i_csi_runtime_suspend,
 330                           sun4i_csi_runtime_resume,
 331                           NULL)
 332};
 333
 334static struct platform_driver sun4i_csi_driver = {
 335        .probe  = sun4i_csi_probe,
 336        .remove = sun4i_csi_remove,
 337        .driver = {
 338                .name           = "sun4i-csi",
 339                .of_match_table = sun4i_csi_of_match,
 340                .pm             = &sun4i_csi_pm_ops,
 341        },
 342};
 343module_platform_driver(sun4i_csi_driver);
 344
 345MODULE_DESCRIPTION("Allwinner A10 Camera Sensor Interface driver");
 346MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>");
 347MODULE_LICENSE("GPL");
 348