Commit 577bbf23 authored by Maxime Ripard's avatar Maxime Ripard Committed by Mauro Carvalho Chehab

media: sunxi: Add A10 CSI driver

The older CSI drivers have camera capture interface different from the one
in the newer ones.

This IP is pretty simple. Some variants (one controller out of two
instances on some SoCs) have an ISP embedded, but there's no code that make
use of it, so we ignored that part for now.

[Sakari Ailus: Wrapped a few long lines, set mbus code using a macro.]
Signed-off-by: default avatarMaxime Ripard <maxime.ripard@bootlin.com>
Signed-off-by: default avatarSakari Ailus <sakari.ailus@linux.intel.com>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab+samsung@kernel.org>
parent 3f0e6df6
......@@ -1425,6 +1425,7 @@ Allwinner A10 CSI driver
M: Maxime Ripard <mripard@kernel.org>
L: linux-media@vger.kernel.org
T: git git://linuxtv.org/media_tree.git
F: drivers/media/platform/sunxi/sun4i-csi/
F: Documentation/devicetree/bindings/media/allwinner,sun4i-a10-csi.yaml
S: Maintained
......
source "drivers/media/platform/sunxi/sun4i-csi/Kconfig"
source "drivers/media/platform/sunxi/sun6i-csi/Kconfig"
obj-y += sun4i-csi/
obj-y += sun6i-csi/
config VIDEO_SUN4I_CSI
tristate "Allwinner A10 CMOS Sensor Interface Support"
depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API && HAS_DMA
depends on ARCH_SUNXI || COMPILE_TEST
select VIDEOBUF2_DMA_CONTIG
select V4L2_FWNODE
help
This is a V4L2 driver for the Allwinner A10 CSI
To compile this driver as a module, choose M here: the module
will be called sun4i_csi.
sun4i-csi-y += sun4i_csi.o
sun4i-csi-y += sun4i_dma.o
sun4i-csi-y += sun4i_v4l2.o
obj-$(CONFIG_VIDEO_SUN4I_CSI) += sun4i-csi.o
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2016 NextThing Co
* Copyright (C) 2016-2019 Bootlin
*
* Author: Maxime Ripard <maxime.ripard@bootlin.com>
*/
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/videodev2.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mediabus.h>
#include <media/videobuf2-core.h>
#include <media/videobuf2-dma-contig.h>
#include "sun4i_csi.h"
static const struct media_entity_operations sun4i_csi_video_entity_ops = {
.link_validate = v4l2_subdev_link_validate,
};
static int sun4i_csi_notify_bound(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *subdev,
struct v4l2_async_subdev *asd)
{
struct sun4i_csi *csi = container_of(notifier, struct sun4i_csi,
notifier);
csi->src_subdev = subdev;
csi->src_pad = media_entity_get_fwnode_pad(&subdev->entity,
subdev->fwnode,
MEDIA_PAD_FL_SOURCE);
if (csi->src_pad < 0) {
dev_err(csi->dev, "Couldn't find output pad for subdev %s\n",
subdev->name);
return csi->src_pad;
}
dev_dbg(csi->dev, "Bound %s pad: %d\n", subdev->name, csi->src_pad);
return 0;
}
static int sun4i_csi_notify_complete(struct v4l2_async_notifier *notifier)
{
struct sun4i_csi *csi = container_of(notifier, struct sun4i_csi,
notifier);
struct v4l2_subdev *subdev = &csi->subdev;
struct video_device *vdev = &csi->vdev;
int ret;
ret = v4l2_device_register_subdev(&csi->v4l, subdev);
if (ret < 0)
return ret;
ret = sun4i_csi_v4l2_register(csi);
if (ret < 0)
return ret;
ret = media_device_register(&csi->mdev);
if (ret)
return ret;
/* Create link from subdev to main device */
ret = media_create_pad_link(&subdev->entity, CSI_SUBDEV_SOURCE,
&vdev->entity, 0,
MEDIA_LNK_FL_ENABLED |
MEDIA_LNK_FL_IMMUTABLE);
if (ret)
goto err_clean_media;
ret = media_create_pad_link(&csi->src_subdev->entity, csi->src_pad,
&subdev->entity, CSI_SUBDEV_SINK,
MEDIA_LNK_FL_ENABLED |
MEDIA_LNK_FL_IMMUTABLE);
if (ret)
goto err_clean_media;
ret = v4l2_device_register_subdev_nodes(&csi->v4l);
if (ret < 0)
goto err_clean_media;
return 0;
err_clean_media:
media_device_unregister(&csi->mdev);
return ret;
}
static const struct v4l2_async_notifier_operations sun4i_csi_notify_ops = {
.bound = sun4i_csi_notify_bound,
.complete = sun4i_csi_notify_complete,
};
static int sun4i_csi_notifier_init(struct sun4i_csi *csi)
{
struct v4l2_fwnode_endpoint vep = {
.bus_type = V4L2_MBUS_PARALLEL,
};
struct fwnode_handle *ep;
int ret;
v4l2_async_notifier_init(&csi->notifier);
ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0,
FWNODE_GRAPH_ENDPOINT_NEXT);
if (!ep)
return -EINVAL;
ret = v4l2_fwnode_endpoint_parse(ep, &vep);
if (ret)
goto out;
csi->bus = vep.bus.parallel;
ret = v4l2_async_notifier_add_fwnode_remote_subdev(&csi->notifier,
ep, &csi->asd);
if (ret)
goto out;
csi->notifier.ops = &sun4i_csi_notify_ops;
out:
fwnode_handle_put(ep);
return ret;
}
static int sun4i_csi_probe(struct platform_device *pdev)
{
struct v4l2_subdev *subdev;
struct video_device *vdev;
struct sun4i_csi *csi;
struct resource *res;
int ret;
int irq;
csi = devm_kzalloc(&pdev->dev, sizeof(*csi), GFP_KERNEL);
if (!csi)
return -ENOMEM;
platform_set_drvdata(pdev, csi);
csi->dev = &pdev->dev;
subdev = &csi->subdev;
vdev = &csi->vdev;
csi->mdev.dev = csi->dev;
strscpy(csi->mdev.model, "Allwinner Video Capture Device",
sizeof(csi->mdev.model));
csi->mdev.hw_revision = 0;
media_device_init(&csi->mdev);
csi->v4l.mdev = &csi->mdev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
csi->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(csi->regs))
return PTR_ERR(csi->regs);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
csi->bus_clk = devm_clk_get(&pdev->dev, "bus");
if (IS_ERR(csi->bus_clk)) {
dev_err(&pdev->dev, "Couldn't get our bus clock\n");
return PTR_ERR(csi->bus_clk);
}
csi->isp_clk = devm_clk_get(&pdev->dev, "isp");
if (IS_ERR(csi->isp_clk)) {
dev_err(&pdev->dev, "Couldn't get our ISP clock\n");
return PTR_ERR(csi->isp_clk);
}
csi->ram_clk = devm_clk_get(&pdev->dev, "ram");
if (IS_ERR(csi->ram_clk)) {
dev_err(&pdev->dev, "Couldn't get our ram clock\n");
return PTR_ERR(csi->ram_clk);
}
csi->rst = devm_reset_control_get(&pdev->dev, NULL);
if (IS_ERR(csi->rst)) {
dev_err(&pdev->dev, "Couldn't get our reset line\n");
return PTR_ERR(csi->rst);
}
/* Initialize subdev */
v4l2_subdev_init(subdev, &sun4i_csi_subdev_ops);
subdev->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
subdev->owner = THIS_MODULE;
snprintf(subdev->name, sizeof(subdev->name), "sun4i-csi-0");
v4l2_set_subdevdata(subdev, csi);
csi->subdev_pads[CSI_SUBDEV_SINK].flags =
MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
csi->subdev_pads[CSI_SUBDEV_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&subdev->entity, CSI_SUBDEV_PADS,
csi->subdev_pads);
if (ret < 0)
return ret;
csi->vdev_pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
vdev->entity.ops = &sun4i_csi_video_entity_ops;
ret = media_entity_pads_init(&vdev->entity, 1, &csi->vdev_pad);
if (ret < 0)
return ret;
ret = sun4i_csi_dma_register(csi, irq);
if (ret)
goto err_clean_pad;
ret = sun4i_csi_notifier_init(csi);
if (ret)
goto err_unregister_media;
ret = v4l2_async_notifier_register(&csi->v4l, &csi->notifier);
if (ret) {
dev_err(csi->dev, "Couldn't register our notifier.\n");
goto err_unregister_media;
}
pm_runtime_enable(&pdev->dev);
return 0;
err_unregister_media:
media_device_unregister(&csi->mdev);
sun4i_csi_dma_unregister(csi);
err_clean_pad:
media_device_cleanup(&csi->mdev);
return ret;
}
static int sun4i_csi_remove(struct platform_device *pdev)
{
struct sun4i_csi *csi = platform_get_drvdata(pdev);
v4l2_async_notifier_unregister(&csi->notifier);
v4l2_async_notifier_cleanup(&csi->notifier);
media_device_unregister(&csi->mdev);
sun4i_csi_dma_unregister(csi);
media_device_cleanup(&csi->mdev);
return 0;
}
static const struct of_device_id sun4i_csi_of_match[] = {
{ .compatible = "allwinner,sun7i-a20-csi0" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, sun4i_csi_of_match);
static int __maybe_unused sun4i_csi_runtime_resume(struct device *dev)
{
struct sun4i_csi *csi = dev_get_drvdata(dev);
reset_control_deassert(csi->rst);
clk_prepare_enable(csi->bus_clk);
clk_prepare_enable(csi->ram_clk);
clk_set_rate(csi->isp_clk, 80000000);
clk_prepare_enable(csi->isp_clk);
writel(1, csi->regs + CSI_EN_REG);
return 0;
}
static int __maybe_unused sun4i_csi_runtime_suspend(struct device *dev)
{
struct sun4i_csi *csi = dev_get_drvdata(dev);
clk_disable_unprepare(csi->isp_clk);
clk_disable_unprepare(csi->ram_clk);
clk_disable_unprepare(csi->bus_clk);
reset_control_assert(csi->rst);
return 0;
}
static const struct dev_pm_ops sun4i_csi_pm_ops = {
SET_RUNTIME_PM_OPS(sun4i_csi_runtime_suspend,
sun4i_csi_runtime_resume,
NULL)
};
static struct platform_driver sun4i_csi_driver = {
.probe = sun4i_csi_probe,
.remove = sun4i_csi_remove,
.driver = {
.name = "sun4i-csi",
.of_match_table = sun4i_csi_of_match,
.pm = &sun4i_csi_pm_ops,
},
};
module_platform_driver(sun4i_csi_driver);
MODULE_DESCRIPTION("Allwinner A10 Camera Sensor Interface driver");
MODULE_AUTHOR("Maxime Ripard <mripard@kernel.org>");
MODULE_LICENSE("GPL");
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2016 NextThing Co
* Copyright (C) 2016-2019 Bootlin
*
* Author: Maxime Ripard <maxime.ripard@bootlin.com>
*/
#ifndef _SUN4I_CSI_H_
#define _SUN4I_CSI_H_
#include <media/media-device.h>
#include <media/v4l2-async.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-device.h>
#include <media/v4l2-fwnode.h>
#include <media/videobuf2-core.h>
#define CSI_EN_REG 0x00
#define CSI_CFG_REG 0x04
#define CSI_CFG_INPUT_FMT(fmt) ((fmt) << 20)
#define CSI_CFG_OUTPUT_FMT(fmt) ((fmt) << 16)
#define CSI_CFG_YUV_DATA_SEQ(seq) ((seq) << 8)
#define CSI_CFG_VSYNC_POL(pol) ((pol) << 2)
#define CSI_CFG_HSYNC_POL(pol) ((pol) << 1)
#define CSI_CFG_PCLK_POL(pol) ((pol) << 0)
#define CSI_CPT_CTRL_REG 0x08
#define CSI_CPT_CTRL_VIDEO_START BIT(1)
#define CSI_CPT_CTRL_IMAGE_START BIT(0)
#define CSI_BUF_ADDR_REG(fifo, buf) (0x10 + (0x8 * (fifo)) + (0x4 * (buf)))
#define CSI_BUF_CTRL_REG 0x28
#define CSI_BUF_CTRL_DBN BIT(2)
#define CSI_BUF_CTRL_DBS BIT(1)
#define CSI_BUF_CTRL_DBE BIT(0)
#define CSI_INT_EN_REG 0x30
#define CSI_INT_FRM_DONE BIT(1)
#define CSI_INT_CPT_DONE BIT(0)
#define CSI_INT_STA_REG 0x34
#define CSI_WIN_CTRL_W_REG 0x40
#define CSI_WIN_CTRL_W_ACTIVE(w) ((w) << 16)
#define CSI_WIN_CTRL_H_REG 0x44
#define CSI_WIN_CTRL_H_ACTIVE(h) ((h) << 16)
#define CSI_BUF_LEN_REG 0x48
#define CSI_MAX_BUFFER 2
#define CSI_MAX_HEIGHT 8192U
#define CSI_MAX_WIDTH 8192U
enum csi_input {
CSI_INPUT_RAW = 0,
CSI_INPUT_BT656 = 2,
CSI_INPUT_YUV = 3,
};
enum csi_output_raw {
CSI_OUTPUT_RAW_PASSTHROUGH = 0,
};
enum csi_output_yuv {
CSI_OUTPUT_YUV_422_PLANAR = 0,
CSI_OUTPUT_YUV_420_PLANAR = 1,
CSI_OUTPUT_YUV_422_UV = 4,
CSI_OUTPUT_YUV_420_UV = 5,
CSI_OUTPUT_YUV_422_MACRO = 8,
CSI_OUTPUT_YUV_420_MACRO = 9,
};
enum csi_yuv_data_seq {
CSI_YUV_DATA_SEQ_YUYV = 0,
CSI_YUV_DATA_SEQ_YVYU = 1,
CSI_YUV_DATA_SEQ_UYVY = 2,
CSI_YUV_DATA_SEQ_VYUY = 3,
};
enum csi_subdev_pads {
CSI_SUBDEV_SINK,
CSI_SUBDEV_SOURCE,
CSI_SUBDEV_PADS,
};
extern const struct v4l2_subdev_ops sun4i_csi_subdev_ops;
struct sun4i_csi_format {
u32 mbus;
u32 fourcc;
enum csi_input input;
u32 output;
unsigned int num_planes;
u8 bpp[3];
unsigned int hsub;
unsigned int vsub;
};
const struct sun4i_csi_format *sun4i_csi_find_format(const u32 *fourcc,
const u32 *mbus);
struct sun4i_csi {
/* Device resources */
struct device *dev;
void __iomem *regs;
struct clk *bus_clk;
struct clk *isp_clk;
struct clk *ram_clk;
struct reset_control *rst;
struct vb2_v4l2_buffer *current_buf[CSI_MAX_BUFFER];
struct {
size_t size;
void *vaddr;
dma_addr_t paddr;
} scratch;
struct v4l2_fwnode_bus_parallel bus;
/* Main Device */
struct v4l2_device v4l;
struct media_device mdev;
struct video_device vdev;
struct media_pad vdev_pad;
struct v4l2_pix_format_mplane fmt;
/* Local subdev */
struct v4l2_subdev subdev;
struct media_pad subdev_pads[CSI_SUBDEV_PADS];
struct v4l2_mbus_framefmt subdev_fmt;
/* V4L2 Async variables */
struct v4l2_async_subdev asd;
struct v4l2_async_notifier notifier;
struct v4l2_subdev *src_subdev;
int src_pad;
/* V4L2 variables */
struct mutex lock;
/* Videobuf2 */
struct vb2_queue queue;
struct list_head buf_list;
spinlock_t qlock;
unsigned int sequence;
};
int sun4i_csi_dma_register(struct sun4i_csi *csi, int irq);
void sun4i_csi_dma_unregister(struct sun4i_csi *csi);
int sun4i_csi_v4l2_register(struct sun4i_csi *csi);
#endif /* _SUN4I_CSI_H_ */
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2016 NextThing Co
* Copyright (C) 2016-2019 Bootlin
*
* Author: Maxime Ripard <maxime.ripard@bootlin.com>
*/
#include <linux/device.h>
#include <linux/pm_runtime.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mc.h>
#include <media/videobuf2-v4l2.h>
#include "sun4i_csi.h"
#define CSI_DEFAULT_WIDTH 640
#define CSI_DEFAULT_HEIGHT 480
const struct sun4i_csi_format sun4i_csi_formats[] = {
/* YUV422 inputs */
{
.mbus = MEDIA_BUS_FMT_YUYV8_2X8,
.fourcc = V4L2_PIX_FMT_YUV420M,
.input = CSI_INPUT_YUV,
.output = CSI_OUTPUT_YUV_420_PLANAR,
.num_planes = 3,
.bpp = { 8, 8, 8 },
.hsub = 2,
.vsub = 2,
},
};
const struct sun4i_csi_format *sun4i_csi_find_format(const u32 *fourcc,
const u32 *mbus)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(sun4i_csi_formats); i++) {
if (fourcc && *fourcc != sun4i_csi_formats[i].fourcc)
continue;
if (mbus && *mbus != sun4i_csi_formats[i].mbus)
continue;
return &sun4i_csi_formats[i];
}
return NULL;
}
static int sun4i_csi_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
struct sun4i_csi *csi = video_drvdata(file);
strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver));
strscpy(cap->card, "sun4i-csi", sizeof(cap->card));
snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
dev_name(csi->dev));
return 0;
}
static int sun4i_csi_enum_input(struct file *file, void *priv,
struct v4l2_input *inp)
{
if (inp->index != 0)
return -EINVAL;
inp->type = V4L2_INPUT_TYPE_CAMERA;
strscpy(inp->name, "Camera", sizeof(inp->name));
return 0;
}
static int sun4i_csi_g_input(struct file *file, void *fh,
unsigned int *i)
{
*i = 0;
return 0;
}
static int sun4i_csi_s_input(struct file *file, void *fh,
unsigned int i)
{
if (i != 0)
return -EINVAL;
return 0;
}
static void _sun4i_csi_try_fmt(struct sun4i_csi *csi,
struct v4l2_pix_format_mplane *pix)
{
const struct sun4i_csi_format *_fmt;
unsigned int height, width;
unsigned int i;
_fmt = sun4i_csi_find_format(&pix->pixelformat, NULL);
if (!_fmt)
_fmt = &sun4i_csi_formats[0];
pix->field = V4L2_FIELD_NONE;
pix->colorspace = V4L2_COLORSPACE_SRGB;
pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace);
pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace);
pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, pix->colorspace,
pix->ycbcr_enc);
pix->num_planes = _fmt->num_planes;
pix->pixelformat = _fmt->fourcc;
memset(pix->reserved, 0, sizeof(pix->reserved));
/* Align the width and height on the subsampling */
width = ALIGN(pix->width, _fmt->hsub);
height = ALIGN(pix->height, _fmt->vsub);
/* Clamp the width and height to our capabilities */
pix->width = clamp(width, _fmt->hsub, CSI_MAX_WIDTH);
pix->height = clamp(height, _fmt->vsub, CSI_MAX_HEIGHT);
for (i = 0; i < _fmt->num_planes; i++) {
unsigned int hsub = i > 0 ? _fmt->hsub : 1;
unsigned int vsub = i > 0 ? _fmt->vsub : 1;
unsigned int bpl;
bpl = pix->width / hsub * _fmt->bpp[i] / 8;
pix->plane_fmt[i].bytesperline = bpl;
pix->plane_fmt[i].sizeimage = bpl * pix->height / vsub;
memset(pix->plane_fmt[i].reserved, 0,
sizeof(pix->plane_fmt[i].reserved));
}
}
static int sun4i_csi_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct sun4i_csi *csi = video_drvdata(file);
_sun4i_csi_try_fmt(csi, &f->fmt.pix_mp);
return 0;
}
static int sun4i_csi_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct sun4i_csi *csi = video_drvdata(file);
_sun4i_csi_try_fmt(csi, &f->fmt.pix_mp);
csi->fmt = f->fmt.pix_mp;
return 0;
}
static int sun4i_csi_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct sun4i_csi *csi = video_drvdata(file);
f->fmt.pix_mp = csi->fmt;
return 0;
}
static int sun4i_csi_enum_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
if (f->index >= ARRAY_SIZE(sun4i_csi_formats))
return -EINVAL;
f->pixelformat = sun4i_csi_formats[f->index].fourcc;
return 0;
}
static const struct v4l2_ioctl_ops sun4i_csi_ioctl_ops = {
.vidioc_querycap = sun4i_csi_querycap,
.vidioc_enum_fmt_vid_cap = sun4i_csi_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap_mplane = sun4i_csi_g_fmt_vid_cap,
.vidioc_s_fmt_vid_cap_mplane = sun4i_csi_s_fmt_vid_cap,
.vidioc_try_fmt_vid_cap_mplane = sun4i_csi_try_fmt_vid_cap,
.vidioc_enum_input = sun4i_csi_enum_input,
.vidioc_g_input = sun4i_csi_g_input,
.vidioc_s_input = sun4i_csi_s_input,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_expbuf = vb2_ioctl_expbuf,
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
};
static int sun4i_csi_open(struct file *file)
{
struct sun4i_csi *csi = video_drvdata(file);
int ret;
ret = mutex_lock_interruptible(&csi->lock);
if (ret)
return ret;
ret = pm_runtime_get_sync(csi->dev);
if (ret < 0)
goto err_pm_put;
ret = v4l2_pipeline_pm_use(&csi->vdev.entity, 1);
if (ret)
goto err_pm_put;
ret = v4l2_fh_open(file);
if (ret)
goto err_pipeline_pm_put;
mutex_unlock(&csi->lock);
return 0;
err_pipeline_pm_put:
v4l2_pipeline_pm_use(&csi->vdev.entity, 0);
err_pm_put:
pm_runtime_put(csi->dev);
mutex_unlock(&csi->lock);
return ret;
}
static int sun4i_csi_release(struct file *file)
{
struct sun4i_csi *csi = video_drvdata(file);
mutex_lock(&csi->lock);
v4l2_fh_release(file);
v4l2_pipeline_pm_use(&csi->vdev.entity, 0);
pm_runtime_put(csi->dev);
mutex_unlock(&csi->lock);
return 0;
}
static const struct v4l2_file_operations sun4i_csi_fops = {
.owner = THIS_MODULE,
.open = sun4i_csi_open,
.release = sun4i_csi_release,
.unlocked_ioctl = video_ioctl2,
.read = vb2_fop_read,
.write = vb2_fop_write,
.poll = vb2_fop_poll,
.mmap = vb2_fop_mmap,
};
static const struct v4l2_mbus_framefmt sun4i_csi_pad_fmt_default = {
.width = CSI_DEFAULT_WIDTH,
.height = CSI_DEFAULT_HEIGHT,
.code = MEDIA_BUS_FMT_YUYV8_2X8,
.field = V4L2_FIELD_NONE,
.colorspace = V4L2_COLORSPACE_RAW,
.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT,
.quantization = V4L2_QUANTIZATION_DEFAULT,
.xfer_func = V4L2_XFER_FUNC_DEFAULT,
};
static int sun4i_csi_subdev_init_cfg(struct v4l2_subdev *subdev,
struct v4l2_subdev_pad_config *cfg)
{
struct v4l2_mbus_framefmt *fmt;
fmt = v4l2_subdev_get_try_format(subdev, cfg, CSI_SUBDEV_SINK);
*fmt = sun4i_csi_pad_fmt_default;
return 0;
}
static int sun4i_csi_subdev_get_fmt(struct v4l2_subdev *subdev,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_format *fmt)
{
struct sun4i_csi *csi = container_of(subdev, struct sun4i_csi, subdev);
struct v4l2_mbus_framefmt *subdev_fmt;
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
subdev_fmt = v4l2_subdev_get_try_format(subdev, cfg, fmt->pad);
else
subdev_fmt = &csi->subdev_fmt;
fmt->format = *subdev_fmt;
return 0;
}
static int sun4i_csi_subdev_set_fmt(struct v4l2_subdev *subdev,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_format *fmt)
{
struct sun4i_csi *csi = container_of(subdev, struct sun4i_csi, subdev);
struct v4l2_mbus_framefmt *subdev_fmt;
if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
subdev_fmt = v4l2_subdev_get_try_format(subdev, cfg, fmt->pad);
else
subdev_fmt = &csi->subdev_fmt;
/* We can only set the format on the sink pad */
if (fmt->pad == CSI_SUBDEV_SINK) {
/* It's the sink, only allow changing the frame size */
subdev_fmt->width = fmt->format.width;
subdev_fmt->height = fmt->format.height;
subdev_fmt->code = fmt->format.code;
}
fmt->format = *subdev_fmt;
return 0;
}
static int
sun4i_csi_subdev_enum_mbus_code(struct v4l2_subdev *subdev,
struct v4l2_subdev_pad_config *cfg,
struct v4l2_subdev_mbus_code_enum *mbus)
{
if (mbus->index >= ARRAY_SIZE(sun4i_csi_formats))
return -EINVAL;
mbus->code = sun4i_csi_formats[mbus->index].mbus;
return 0;
}
static const struct v4l2_subdev_pad_ops sun4i_csi_subdev_pad_ops = {
.link_validate = v4l2_subdev_link_validate_default,
.init_cfg = sun4i_csi_subdev_init_cfg,
.get_fmt = sun4i_csi_subdev_get_fmt,
.set_fmt = sun4i_csi_subdev_set_fmt,
.enum_mbus_code = sun4i_csi_subdev_enum_mbus_code,
};
const struct v4l2_subdev_ops sun4i_csi_subdev_ops = {
.pad = &sun4i_csi_subdev_pad_ops,
};
int sun4i_csi_v4l2_register(struct sun4i_csi *csi)
{
struct video_device *vdev = &csi->vdev;
int ret;
vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING;
vdev->v4l2_dev = &csi->v4l;
vdev->queue = &csi->queue;
strscpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name));
vdev->release = video_device_release_empty;
vdev->lock = &csi->lock;
/* Set a default format */
csi->fmt.pixelformat = sun4i_csi_formats[0].fourcc,
csi->fmt.width = CSI_DEFAULT_WIDTH;
csi->fmt.height = CSI_DEFAULT_HEIGHT;
_sun4i_csi_try_fmt(csi, &csi->fmt);
csi->subdev_fmt = sun4i_csi_pad_fmt_default;
vdev->fops = &sun4i_csi_fops;
vdev->ioctl_ops = &sun4i_csi_ioctl_ops;
video_set_drvdata(vdev, csi);
ret = video_register_device(&csi->vdev, VFL_TYPE_GRABBER, -1);
if (ret)
return ret;
dev_info(csi->dev, "Device registered as %s\n",
video_device_node_name(vdev));
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment