diff options
author | Takashi Iwai <tiwai@suse.de> | 2017-08-02 17:11:45 +0200 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2017-08-02 17:11:45 +0200 |
commit | 5ef26e966d3fd105ad9a7e8e8f6d12c7fbd4c03d (patch) | |
tree | dd5c2ce3daab2e398ab8c0fb852587b647131568 /drivers/media/platform/vimc | |
parent | 3f3c371421e601fa93b6cb7fb52da9ad59ec90b4 (diff) | |
parent | 60668a2dcafcf8aad0860f5a5c93eb2d7438052e (diff) |
Merge tag 'asoc-fix-v4.13-rc3' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound into for-linus
ASoC: Fixes for v4.13
Quite a few fixes here that have been sent since the merge window, the
biggest one is the fix from Tony for some confusion with the device
property API which was causing issues with the of-graph card. This is
fixed with some changes in the graph API itself as it seemed very likely
to be error prone.
Diffstat (limited to 'drivers/media/platform/vimc')
-rw-r--r-- | drivers/media/platform/vimc/Kconfig | 1 | ||||
-rw-r--r-- | drivers/media/platform/vimc/Makefile | 10 | ||||
-rw-r--r-- | drivers/media/platform/vimc/vimc-capture.c | 321 | ||||
-rw-r--r-- | drivers/media/platform/vimc/vimc-capture.h | 28 | ||||
-rw-r--r-- | drivers/media/platform/vimc/vimc-common.c | 473 | ||||
-rw-r--r-- | drivers/media/platform/vimc/vimc-common.h | 229 | ||||
-rw-r--r-- | drivers/media/platform/vimc/vimc-core.c | 610 | ||||
-rw-r--r-- | drivers/media/platform/vimc/vimc-core.h | 112 | ||||
-rw-r--r-- | drivers/media/platform/vimc/vimc-debayer.c | 601 | ||||
-rw-r--r-- | drivers/media/platform/vimc/vimc-scaler.c | 455 | ||||
-rw-r--r-- | drivers/media/platform/vimc/vimc-sensor.c | 321 | ||||
-rw-r--r-- | drivers/media/platform/vimc/vimc-sensor.h | 28 |
12 files changed, 2325 insertions, 864 deletions
diff --git a/drivers/media/platform/vimc/Kconfig b/drivers/media/platform/vimc/Kconfig index a18f6352c422..71c9fe7d3370 100644 --- a/drivers/media/platform/vimc/Kconfig +++ b/drivers/media/platform/vimc/Kconfig @@ -2,6 +2,7 @@ config VIDEO_VIMC tristate "Virtual Media Controller Driver (VIMC)" depends on VIDEO_DEV && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API select VIDEOBUF2_VMALLOC + select VIDEO_V4L2_TPG default n ---help--- Skeleton driver for Virtual Media Controller diff --git a/drivers/media/platform/vimc/Makefile b/drivers/media/platform/vimc/Makefile index c45195e5e05c..68c5d9804c11 100644 --- a/drivers/media/platform/vimc/Makefile +++ b/drivers/media/platform/vimc/Makefile @@ -1,3 +1,9 @@ -vimc-objs := vimc-core.o vimc-capture.o vimc-sensor.o +vimc-objs := vimc-core.o +vimc_capture-objs := vimc-capture.o +vimc_common-objs := vimc-common.o +vimc_debayer-objs := vimc-debayer.o +vimc_scaler-objs := vimc-scaler.o +vimc_sensor-objs := vimc-sensor.o -obj-$(CONFIG_VIDEO_VIMC) += vimc.o +obj-$(CONFIG_VIDEO_VIMC) += vimc.o vimc_capture.o vimc_common.o vimc-debayer.o \ + vimc_scaler.o vimc_sensor.o diff --git a/drivers/media/platform/vimc/vimc-capture.c b/drivers/media/platform/vimc/vimc-capture.c index 9adb06d7e13d..14cb32e21130 100644 --- a/drivers/media/platform/vimc/vimc-capture.c +++ b/drivers/media/platform/vimc/vimc-capture.c @@ -15,15 +15,21 @@ * */ +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> #include <media/v4l2-ioctl.h> #include <media/videobuf2-core.h> #include <media/videobuf2-vmalloc.h> -#include "vimc-capture.h" +#include "vimc-common.h" + +#define VIMC_CAP_DRV_NAME "vimc-capture" struct vimc_cap_device { struct vimc_ent_device ved; struct video_device vdev; + struct device *dev; struct v4l2_pix_format format; struct vb2_queue queue; struct list_head buf_list; @@ -40,6 +46,14 @@ struct vimc_cap_device { struct media_pipeline pipe; }; +static const struct v4l2_pix_format fmt_default = { + .width = 640, + .height = 480, + .pixelformat = V4L2_PIX_FMT_RGB24, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, +}; + struct vimc_cap_buffer { /* * struct vb2_v4l2_buffer must be the first element @@ -64,7 +78,16 @@ static int vimc_cap_querycap(struct file *file, void *priv, return 0; } -static int vimc_cap_fmt_vid_cap(struct file *file, void *priv, +static void vimc_cap_get_format(struct vimc_ent_device *ved, + struct v4l2_pix_format *fmt) +{ + struct vimc_cap_device *vcap = container_of(ved, struct vimc_cap_device, + ved); + + *fmt = vcap->format; +} + +static int vimc_cap_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct vimc_cap_device *vcap = video_drvdata(file); @@ -74,16 +97,98 @@ static int vimc_cap_fmt_vid_cap(struct file *file, void *priv, return 0; } +static int vimc_cap_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format *format = &f->fmt.pix; + const struct vimc_pix_map *vpix; + + format->width = clamp_t(u32, format->width, VIMC_FRAME_MIN_WIDTH, + VIMC_FRAME_MAX_WIDTH) & ~1; + format->height = clamp_t(u32, format->height, VIMC_FRAME_MIN_HEIGHT, + VIMC_FRAME_MAX_HEIGHT) & ~1; + + /* Don't accept a pixelformat that is not on the table */ + vpix = vimc_pix_map_by_pixelformat(format->pixelformat); + if (!vpix) { + format->pixelformat = fmt_default.pixelformat; + vpix = vimc_pix_map_by_pixelformat(format->pixelformat); + } + /* TODO: Add support for custom bytesperline values */ + format->bytesperline = format->width * vpix->bpp; + format->sizeimage = format->bytesperline * format->height; + + if (format->field == V4L2_FIELD_ANY) + format->field = fmt_default.field; + + vimc_colorimetry_clamp(format); + + return 0; +} + +static int vimc_cap_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct vimc_cap_device *vcap = video_drvdata(file); + + /* Do not change the format while stream is on */ + if (vb2_is_busy(&vcap->queue)) + return -EBUSY; + + vimc_cap_try_fmt_vid_cap(file, priv, f); + + dev_dbg(vcap->dev, "%s: format update: " + "old:%dx%d (0x%x, %d, %d, %d, %d) " + "new:%dx%d (0x%x, %d, %d, %d, %d)\n", vcap->vdev.name, + /* old */ + vcap->format.width, vcap->format.height, + vcap->format.pixelformat, vcap->format.colorspace, + vcap->format.quantization, vcap->format.xfer_func, + vcap->format.ycbcr_enc, + /* new */ + f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.pixelformat, f->fmt.pix.colorspace, + f->fmt.pix.quantization, f->fmt.pix.xfer_func, + f->fmt.pix.ycbcr_enc); + + vcap->format = f->fmt.pix; + + return 0; +} + static int vimc_cap_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *f) { - struct vimc_cap_device *vcap = video_drvdata(file); + const struct vimc_pix_map *vpix = vimc_pix_map_by_index(f->index); + + if (!vpix) + return -EINVAL; + + f->pixelformat = vpix->pixelformat; + + return 0; +} + +static int vimc_cap_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + const struct vimc_pix_map *vpix; + + if (fsize->index) + return -EINVAL; - if (f->index > 0) + /* Only accept code in the pix map table */ + vpix = vimc_pix_map_by_code(fsize->pixel_format); + if (!vpix) return -EINVAL; - /* We only support one format for now */ - f->pixelformat = vcap->format.pixelformat; + fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + fsize->stepwise.min_width = VIMC_FRAME_MIN_WIDTH; + fsize->stepwise.max_width = VIMC_FRAME_MAX_WIDTH; + fsize->stepwise.min_height = VIMC_FRAME_MIN_HEIGHT; + fsize->stepwise.max_height = VIMC_FRAME_MAX_HEIGHT; + fsize->stepwise.step_width = 2; + fsize->stepwise.step_height = 2; return 0; } @@ -101,10 +206,11 @@ static const struct v4l2_file_operations vimc_cap_fops = { static const struct v4l2_ioctl_ops vimc_cap_ioctl_ops = { .vidioc_querycap = vimc_cap_querycap, - .vidioc_g_fmt_vid_cap = vimc_cap_fmt_vid_cap, - .vidioc_s_fmt_vid_cap = vimc_cap_fmt_vid_cap, - .vidioc_try_fmt_vid_cap = vimc_cap_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vimc_cap_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vimc_cap_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vimc_cap_try_fmt_vid_cap, .vidioc_enum_fmt_vid_cap = vimc_cap_enum_fmt_vid_cap, + .vidioc_enum_framesizes = vimc_cap_enum_framesizes, .vidioc_reqbufs = vb2_ioctl_reqbufs, .vidioc_create_bufs = vb2_ioctl_create_bufs, @@ -132,31 +238,6 @@ static void vimc_cap_return_all_buffers(struct vimc_cap_device *vcap, spin_unlock(&vcap->qlock); } -static int vimc_cap_pipeline_s_stream(struct vimc_cap_device *vcap, int enable) -{ - struct v4l2_subdev *sd; - struct media_pad *pad; - int ret; - - /* Start the stream in the subdevice direct connected */ - pad = media_entity_remote_pad(&vcap->vdev.entity.pads[0]); - - /* - * if it is a raw node from vimc-core, there is nothing to activate - * TODO: remove this when there are no more raw nodes in the - * core and return error instead - */ - if (pad->entity->obj_type == MEDIA_ENTITY_TYPE_BASE) - return 0; - - sd = media_entity_to_v4l2_subdev(pad->entity); - ret = v4l2_subdev_call(sd, video, s_stream, enable); - if (ret && ret != -ENOIOCTLCMD) - return ret; - - return 0; -} - static int vimc_cap_start_streaming(struct vb2_queue *vq, unsigned int count) { struct vimc_cap_device *vcap = vb2_get_drv_priv(vq); @@ -173,7 +254,7 @@ static int vimc_cap_start_streaming(struct vb2_queue *vq, unsigned int count) } /* Enable streaming from the pipe */ - ret = vimc_cap_pipeline_s_stream(vcap, 1); + ret = vimc_pipeline_s_stream(&vcap->vdev.entity, 1); if (ret) { media_pipeline_stop(entity); vimc_cap_return_all_buffers(vcap, VB2_BUF_STATE_QUEUED); @@ -192,7 +273,7 @@ static void vimc_cap_stop_streaming(struct vb2_queue *vq) struct vimc_cap_device *vcap = vb2_get_drv_priv(vq); /* Disable streaming from the pipe */ - vimc_cap_pipeline_s_stream(vcap, 0); + vimc_pipeline_s_stream(&vcap->vdev.entity, 0); /* Stop the media pipeline */ media_pipeline_stop(&vcap->vdev.entity); @@ -234,8 +315,7 @@ static int vimc_cap_buffer_prepare(struct vb2_buffer *vb) unsigned long size = vcap->format.sizeimage; if (vb2_plane_size(vb, 0) < size) { - dev_err(vcap->vdev.v4l2_dev->dev, - "%s: buffer too small (%lu < %lu)\n", + dev_err(vcap->dev, "%s: buffer too small (%lu < %lu)\n", vcap->vdev.name, vb2_plane_size(vb, 0), size); return -EINVAL; } @@ -256,78 +336,14 @@ static const struct vb2_ops vimc_cap_qops = { .wait_finish = vb2_ops_wait_finish, }; -/* - * NOTE: this function is a copy of v4l2_subdev_link_validate_get_format - * maybe the v4l2 function should be public - */ -static int vimc_cap_v4l2_subdev_link_validate_get_format(struct media_pad *pad, - struct v4l2_subdev_format *fmt) -{ - struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(pad->entity); - - fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; - fmt->pad = pad->index; - - return v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt); -} - -static int vimc_cap_link_validate(struct media_link *link) -{ - struct v4l2_subdev_format source_fmt; - const struct vimc_pix_map *vpix; - struct vimc_cap_device *vcap = container_of(link->sink->entity, - struct vimc_cap_device, - vdev.entity); - struct v4l2_pix_format *sink_fmt = &vcap->format; - int ret; - - /* - * if it is a raw node from vimc-core, ignore the link for now - * TODO: remove this when there are no more raw nodes in the - * core and return error instead - */ - if (link->source->entity->obj_type == MEDIA_ENTITY_TYPE_BASE) - return 0; - - /* Get the the format of the subdev */ - ret = vimc_cap_v4l2_subdev_link_validate_get_format(link->source, - &source_fmt); - if (ret) - return ret; - - dev_dbg(vcap->vdev.v4l2_dev->dev, - "%s: link validate formats src:%dx%d %d sink:%dx%d %d\n", - vcap->vdev.name, - source_fmt.format.width, source_fmt.format.height, - source_fmt.format.code, - sink_fmt->width, sink_fmt->height, - sink_fmt->pixelformat); - - /* The width, height and code must match. */ - vpix = vimc_pix_map_by_pixelformat(sink_fmt->pixelformat); - if (source_fmt.format.width != sink_fmt->width - || source_fmt.format.height != sink_fmt->height - || vpix->code != source_fmt.format.code) - return -EPIPE; - - /* - * The field order must match, or the sink field order must be NONE - * to support interlaced hardware connected to bridges that support - * progressive formats only. - */ - if (source_fmt.format.field != sink_fmt->field && - sink_fmt->field != V4L2_FIELD_NONE) - return -EPIPE; - - return 0; -} - static const struct media_entity_operations vimc_cap_mops = { - .link_validate = vimc_cap_link_validate, + .link_validate = vimc_link_validate, }; -static void vimc_cap_destroy(struct vimc_ent_device *ved) +static void vimc_cap_comp_unbind(struct device *comp, struct device *master, + void *master_data) { + struct vimc_ent_device *ved = dev_get_drvdata(comp); struct vimc_cap_device *vcap = container_of(ved, struct vimc_cap_device, ved); @@ -376,42 +392,35 @@ static void vimc_cap_process_frame(struct vimc_ent_device *ved, vb2_buffer_done(&vimc_buf->vb2.vb2_buf, VB2_BUF_STATE_DONE); } -struct vimc_ent_device *vimc_cap_create(struct v4l2_device *v4l2_dev, - const char *const name, - u16 num_pads, - const unsigned long *pads_flag) +static int vimc_cap_comp_bind(struct device *comp, struct device *master, + void *master_data) { + struct v4l2_device *v4l2_dev = master_data; + struct vimc_platform_data *pdata = comp->platform_data; const struct vimc_pix_map *vpix; struct vimc_cap_device *vcap; struct video_device *vdev; struct vb2_queue *q; int ret; - /* - * Check entity configuration params - * NOTE: we only support a single sink pad - */ - if (!name || num_pads != 1 || !pads_flag || - !(pads_flag[0] & MEDIA_PAD_FL_SINK)) - return ERR_PTR(-EINVAL); - /* Allocate the vimc_cap_device struct */ vcap = kzalloc(sizeof(*vcap), GFP_KERNEL); if (!vcap) - return ERR_PTR(-ENOMEM); + return -ENOMEM; /* Allocate the pads */ - vcap->ved.pads = vimc_pads_init(num_pads, pads_flag); + vcap->ved.pads = + vimc_pads_init(1, (const unsigned long[1]) {MEDIA_PAD_FL_SINK}); if (IS_ERR(vcap->ved.pads)) { ret = PTR_ERR(vcap->ved.pads); goto err_free_vcap; } /* Initialize the media entity */ - vcap->vdev.entity.name = name; + vcap->vdev.entity.name = pdata->entity_name; vcap->vdev.entity.function = MEDIA_ENT_F_IO_V4L; ret = media_entity_pads_init(&vcap->vdev.entity, - num_pads, vcap->ved.pads); + 1, vcap->ved.pads); if (ret) goto err_clean_pads; @@ -432,9 +441,8 @@ struct vimc_ent_device *vimc_cap_create(struct v4l2_device *v4l2_dev, ret = vb2_queue_init(q); if (ret) { - dev_err(vcap->vdev.v4l2_dev->dev, - "%s: vb2 queue init failed (err=%d)\n", - vcap->vdev.name, ret); + dev_err(comp, "%s: vb2 queue init failed (err=%d)\n", + pdata->entity_name, ret); goto err_clean_m_ent; } @@ -442,23 +450,19 @@ struct vimc_ent_device *vimc_cap_create(struct v4l2_device *v4l2_dev, INIT_LIST_HEAD(&vcap->buf_list); spin_lock_init(&vcap->qlock); - /* Set the frame format (this is hardcoded for now) */ - vcap->format.width = 640; - vcap->format.height = 480; - vcap->format.pixelformat = V4L2_PIX_FMT_RGB24; - vcap->format.field = V4L2_FIELD_NONE; - vcap->format.colorspace = V4L2_COLORSPACE_SRGB; - + /* Set default frame format */ + vcap->format = fmt_default; vpix = vimc_pix_map_by_pixelformat(vcap->format.pixelformat); - vcap->format.bytesperline = vcap->format.width * vpix->bpp; vcap->format.sizeimage = vcap->format.bytesperline * vcap->format.height; /* Fill the vimc_ent_device struct */ - vcap->ved.destroy = vimc_cap_destroy; vcap->ved.ent = &vcap->vdev.entity; vcap->ved.process_frame = vimc_cap_process_frame; + vcap->ved.vdev_get_format = vimc_cap_get_format; + dev_set_drvdata(comp, &vcap->ved); + vcap->dev = comp; /* Initialize the video_device struct */ vdev = &vcap->vdev; @@ -471,19 +475,18 @@ struct vimc_ent_device *vimc_cap_create(struct v4l2_device *v4l2_dev, vdev->queue = q; vdev->v4l2_dev = v4l2_dev; vdev->vfl_dir = VFL_DIR_RX; - strlcpy(vdev->name, name, sizeof(vdev->name)); + strlcpy(vdev->name, pdata->entity_name, sizeof(vdev->name)); video_set_drvdata(vdev, &vcap->ved); /* Register the video_device with the v4l2 and the media framework */ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); if (ret) { - dev_err(vcap->vdev.v4l2_dev->dev, - "%s: video register failed (err=%d)\n", + dev_err(comp, "%s: video register failed (err=%d)\n", vcap->vdev.name, ret); goto err_release_queue; } - return &vcap->ved; + return 0; err_release_queue: vb2_queue_release(q); @@ -494,5 +497,45 @@ err_clean_pads: err_free_vcap: kfree(vcap); - return ERR_PTR(ret); + return ret; +} + +static const struct component_ops vimc_cap_comp_ops = { + .bind = vimc_cap_comp_bind, + .unbind = vimc_cap_comp_unbind, +}; + +static int vimc_cap_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &vimc_cap_comp_ops); } + +static int vimc_cap_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vimc_cap_comp_ops); + + return 0; +} + +static struct platform_driver vimc_cap_pdrv = { + .probe = vimc_cap_probe, + .remove = vimc_cap_remove, + .driver = { + .name = VIMC_CAP_DRV_NAME, + }, +}; + +static const struct platform_device_id vimc_cap_driver_ids[] = { + { + .name = VIMC_CAP_DRV_NAME, + }, + { } +}; + +module_platform_driver(vimc_cap_pdrv); + +MODULE_DEVICE_TABLE(platform, vimc_cap_driver_ids); + +MODULE_DESCRIPTION("Virtual Media Controller Driver (VIMC) Capture"); +MODULE_AUTHOR("Helen Mae Koike Fornazier <helen.fornazier@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vimc/vimc-capture.h b/drivers/media/platform/vimc/vimc-capture.h deleted file mode 100644 index 581a813abdf1..000000000000 --- a/drivers/media/platform/vimc/vimc-capture.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * vimc-capture.h Virtual Media Controller Driver - * - * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef _VIMC_CAPTURE_H_ -#define _VIMC_CAPTURE_H_ - -#include "vimc-core.h" - -struct vimc_ent_device *vimc_cap_create(struct v4l2_device *v4l2_dev, - const char *const name, - u16 num_pads, - const unsigned long *pads_flag); - -#endif diff --git a/drivers/media/platform/vimc/vimc-common.c b/drivers/media/platform/vimc/vimc-common.c new file mode 100644 index 000000000000..9d63c84a9876 --- /dev/null +++ b/drivers/media/platform/vimc/vimc-common.c @@ -0,0 +1,473 @@ +/* + * vimc-common.c Virtual Media Controller Driver + * + * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/init.h> +#include <linux/module.h> + +#include "vimc-common.h" + +/* + * NOTE: non-bayer formats need to come first (necessary for enum_mbus_code + * in the scaler) + */ +static const struct vimc_pix_map vimc_pix_map_list[] = { + /* TODO: add all missing formats */ + + /* RGB formats */ + { + .code = MEDIA_BUS_FMT_BGR888_1X24, + .pixelformat = V4L2_PIX_FMT_BGR24, + .bpp = 3, + .bayer = false, + }, + { + .code = MEDIA_BUS_FMT_RGB888_1X24, + .pixelformat = V4L2_PIX_FMT_RGB24, + .bpp = 3, + .bayer = false, + }, + { + .code = MEDIA_BUS_FMT_ARGB8888_1X32, + .pixelformat = V4L2_PIX_FMT_ARGB32, + .bpp = 4, + .bayer = false, + }, + + /* Bayer formats */ + { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .pixelformat = V4L2_PIX_FMT_SGBRG8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .pixelformat = V4L2_PIX_FMT_SGRBG8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .pixelformat = V4L2_PIX_FMT_SRGGB8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .pixelformat = V4L2_PIX_FMT_SBGGR10, + .bpp = 2, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .pixelformat = V4L2_PIX_FMT_SGBRG10, + .bpp = 2, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .pixelformat = V4L2_PIX_FMT_SGRBG10, + .bpp = 2, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .pixelformat = V4L2_PIX_FMT_SRGGB10, + .bpp = 2, + .bayer = true, + }, + + /* 10bit raw bayer a-law compressed to 8 bits */ + { + .code = MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8, + .pixelformat = V4L2_PIX_FMT_SBGGR10ALAW8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8, + .pixelformat = V4L2_PIX_FMT_SGBRG10ALAW8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8, + .pixelformat = V4L2_PIX_FMT_SGRBG10ALAW8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8, + .pixelformat = V4L2_PIX_FMT_SRGGB10ALAW8, + .bpp = 1, + .bayer = true, + }, + + /* 10bit raw bayer DPCM compressed to 8 bits */ + { + .code = MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8, + .pixelformat = V4L2_PIX_FMT_SBGGR10DPCM8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8, + .pixelformat = V4L2_PIX_FMT_SGBRG10DPCM8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, + .pixelformat = V4L2_PIX_FMT_SGRBG10DPCM8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8, + .pixelformat = V4L2_PIX_FMT_SRGGB10DPCM8, + .bpp = 1, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .pixelformat = V4L2_PIX_FMT_SBGGR12, + .bpp = 2, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .pixelformat = V4L2_PIX_FMT_SGBRG12, + .bpp = 2, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .pixelformat = V4L2_PIX_FMT_SGRBG12, + .bpp = 2, + .bayer = true, + }, + { + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .pixelformat = V4L2_PIX_FMT_SRGGB12, + .bpp = 2, + .bayer = true, + }, +}; + +const struct vimc_pix_map *vimc_pix_map_by_index(unsigned int i) +{ + if (i >= ARRAY_SIZE(vimc_pix_map_list)) + return NULL; + + return &vimc_pix_map_list[i]; +} +EXPORT_SYMBOL_GPL(vimc_pix_map_by_index); + +const struct vimc_pix_map *vimc_pix_map_by_code(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vimc_pix_map_list); i++) { + if (vimc_pix_map_list[i].code == code) + return &vimc_pix_map_list[i]; + } + return NULL; +} +EXPORT_SYMBOL_GPL(vimc_pix_map_by_code); + +const struct vimc_pix_map *vimc_pix_map_by_pixelformat(u32 pixelformat) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vimc_pix_map_list); i++) { + if (vimc_pix_map_list[i].pixelformat == pixelformat) + return &vimc_pix_map_list[i]; + } + return NULL; +} +EXPORT_SYMBOL_GPL(vimc_pix_map_by_pixelformat); + +int vimc_propagate_frame(struct media_pad *src, const void *frame) +{ + struct media_link *link; + + if (!(src->flags & MEDIA_PAD_FL_SOURCE)) + return -EINVAL; + + /* Send this frame to all sink pads that are direct linked */ + list_for_each_entry(link, &src->entity->links, list) { + if (link->source == src && + (link->flags & MEDIA_LNK_FL_ENABLED)) { + struct vimc_ent_device *ved = NULL; + struct media_entity *entity = link->sink->entity; + + if (is_media_entity_v4l2_subdev(entity)) { + struct v4l2_subdev *sd = + container_of(entity, struct v4l2_subdev, + entity); + ved = v4l2_get_subdevdata(sd); + } else if (is_media_entity_v4l2_video_device(entity)) { + struct video_device *vdev = + container_of(entity, + struct video_device, + entity); + ved = video_get_drvdata(vdev); + } + if (ved && ved->process_frame) + ved->process_frame(ved, link->sink, frame); + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(vimc_propagate_frame); + +/* Helper function to allocate and initialize pads */ +struct media_pad *vimc_pads_init(u16 num_pads, const unsigned long *pads_flag) +{ + struct media_pad *pads; + unsigned int i; + + /* Allocate memory for the pads */ + pads = kcalloc(num_pads, sizeof(*pads), GFP_KERNEL); + if (!pads) + return ERR_PTR(-ENOMEM); + + /* Initialize the pads */ + for (i = 0; i < num_pads; i++) { + pads[i].index = i; + pads[i].flags = pads_flag[i]; + } + + return pads; +} +EXPORT_SYMBOL_GPL(vimc_pads_init); + +int vimc_pipeline_s_stream(struct media_entity *ent, int enable) +{ + struct v4l2_subdev *sd; + struct media_pad *pad; + unsigned int i; + int ret; + + for (i = 0; i < ent->num_pads; i++) { + if (ent->pads[i].flags & MEDIA_PAD_FL_SOURCE) + continue; + + /* Start the stream in the subdevice direct connected */ + pad = media_entity_remote_pad(&ent->pads[i]); + + if (!is_media_entity_v4l2_subdev(pad->entity)) + return -EINVAL; + + sd = media_entity_to_v4l2_subdev(pad->entity); + ret = v4l2_subdev_call(sd, video, s_stream, enable); + if (ret && ret != -ENOIOCTLCMD) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(vimc_pipeline_s_stream); + +static int vimc_get_mbus_format(struct media_pad *pad, + struct v4l2_subdev_format *fmt) +{ + if (is_media_entity_v4l2_subdev(pad->entity)) { + struct v4l2_subdev *sd = + media_entity_to_v4l2_subdev(pad->entity); + int ret; + + fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt->pad = pad->index; + + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt); + if (ret) + return ret; + + } else if (is_media_entity_v4l2_video_device(pad->entity)) { + struct video_device *vdev = container_of(pad->entity, + struct video_device, + entity); + struct vimc_ent_device *ved = video_get_drvdata(vdev); + const struct vimc_pix_map *vpix; + struct v4l2_pix_format vdev_fmt; + + if (!ved->vdev_get_format) + return -ENOIOCTLCMD; + + ved->vdev_get_format(ved, &vdev_fmt); + vpix = vimc_pix_map_by_pixelformat(vdev_fmt.pixelformat); + v4l2_fill_mbus_format(&fmt->format, &vdev_fmt, vpix->code); + } else { + return -EINVAL; + } + + return 0; +} + +int vimc_link_validate(struct media_link *link) +{ + struct v4l2_subdev_format source_fmt, sink_fmt; + int ret; + + ret = vimc_get_mbus_format(link->source, &source_fmt); + if (ret) + return ret; + + ret = vimc_get_mbus_format(link->sink, &sink_fmt); + if (ret) + return ret; + + pr_info("vimc link validate: " + "%s:src:%dx%d (0x%x, %d, %d, %d, %d) " + "%s:snk:%dx%d (0x%x, %d, %d, %d, %d)\n", + /* src */ + link->source->entity->name, + source_fmt.format.width, source_fmt.format.height, + source_fmt.format.code, source_fmt.format.colorspace, + source_fmt.format.quantization, source_fmt.format.xfer_func, + source_fmt.format.ycbcr_enc, + /* sink */ + link->sink->entity->name, + sink_fmt.format.width, sink_fmt.format.height, + sink_fmt.format.code, sink_fmt.format.colorspace, + sink_fmt.format.quantization, sink_fmt.format.xfer_func, + sink_fmt.format.ycbcr_enc); + + /* The width, height and code must match. */ + if (source_fmt.format.width != sink_fmt.format.width + || source_fmt.format.height != sink_fmt.format.height + || source_fmt.format.code != sink_fmt.format.code) + return -EPIPE; + + /* + * The field order must match, or the sink field order must be NONE + * to support interlaced hardware connected to bridges that support + * progressive formats only. + */ + if (source_fmt.format.field != sink_fmt.format.field && + sink_fmt.format.field != V4L2_FIELD_NONE) + return -EPIPE; + + /* + * If colorspace is DEFAULT, then assume all the colorimetry is also + * DEFAULT, return 0 to skip comparing the other colorimetry parameters + */ + if (source_fmt.format.colorspace == V4L2_COLORSPACE_DEFAULT + || sink_fmt.format.colorspace == V4L2_COLORSPACE_DEFAULT) + return 0; + + /* Colorspace must match. */ + if (source_fmt.format.colorspace != sink_fmt.format.colorspace) + return -EPIPE; + + /* Colorimetry must match if they are not set to DEFAULT */ + if (source_fmt.format.ycbcr_enc != V4L2_YCBCR_ENC_DEFAULT + && sink_fmt.format.ycbcr_enc != V4L2_YCBCR_ENC_DEFAULT + && source_fmt.format.ycbcr_enc != sink_fmt.format.ycbcr_enc) + return -EPIPE; + + if (source_fmt.format.quantization != V4L2_QUANTIZATION_DEFAULT + && sink_fmt.format.quantization != V4L2_QUANTIZATION_DEFAULT + && source_fmt.format.quantization != sink_fmt.format.quantization) + return -EPIPE; + + if (source_fmt.format.xfer_func != V4L2_XFER_FUNC_DEFAULT + && sink_fmt.format.xfer_func != V4L2_XFER_FUNC_DEFAULT + && source_fmt.format.xfer_func != sink_fmt.format.xfer_func) + return -EPIPE; + + return 0; +} +EXPORT_SYMBOL_GPL(vimc_link_validate); + +static const struct media_entity_operations vimc_ent_sd_mops = { + .link_validate = vimc_link_validate, +}; + +int vimc_ent_sd_register(struct vimc_ent_device *ved, + struct v4l2_subdev *sd, + struct v4l2_device *v4l2_dev, + const char *const name, + u32 function, + u16 num_pads, + const unsigned long *pads_flag, + const struct v4l2_subdev_ops *sd_ops) +{ + int ret; + + /* Allocate the pads */ + ved->pads = vimc_pads_init(num_pads, pads_flag); + if (IS_ERR(ved->pads)) + return PTR_ERR(ved->pads); + + /* Fill the vimc_ent_device struct */ + ved->ent = &sd->entity; + + /* Initialize the subdev */ + v4l2_subdev_init(sd, sd_ops); + sd->entity.function = function; + sd->entity.ops = &vimc_ent_sd_mops; + sd->owner = THIS_MODULE; + strlcpy(sd->name, name, sizeof(sd->name)); + v4l2_set_subdevdata(sd, ved); + + /* Expose this subdev to user space */ + sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + /* Initialize the media entity */ + ret = media_entity_pads_init(&sd->entity, num_pads, ved->pads); + if (ret) + goto err_clean_pads; + + /* Register the subdev with the v4l2 and the media framework */ + ret = v4l2_device_register_subdev(v4l2_dev, sd); + if (ret) { + dev_err(v4l2_dev->dev, + "%s: subdev register failed (err=%d)\n", + name, ret); + goto err_clean_m_ent; + } + + return 0; + +err_clean_m_ent: + media_entity_cleanup(&sd->entity); +err_clean_pads: + vimc_pads_cleanup(ved->pads); + return ret; +} +EXPORT_SYMBOL_GPL(vimc_ent_sd_register); + +void vimc_ent_sd_unregister(struct vimc_ent_device *ved, struct v4l2_subdev *sd) +{ + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(ved->ent); + vimc_pads_cleanup(ved->pads); +} +EXPORT_SYMBOL_GPL(vimc_ent_sd_unregister); + +MODULE_DESCRIPTION("Virtual Media Controller Driver (VIMC) Common"); +MODULE_AUTHOR("Helen Koike <helen.fornazier@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vimc/vimc-common.h b/drivers/media/platform/vimc/vimc-common.h new file mode 100644 index 000000000000..dca528a316e7 --- /dev/null +++ b/drivers/media/platform/vimc/vimc-common.h @@ -0,0 +1,229 @@ +/* + * vimc-common.h Virtual Media Controller Driver + * + * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _VIMC_COMMON_H_ +#define _VIMC_COMMON_H_ + +#include <linux/slab.h> +#include <media/media-device.h> +#include <media/v4l2-device.h> + +#define VIMC_FRAME_MAX_WIDTH 4096 +#define VIMC_FRAME_MAX_HEIGHT 2160 +#define VIMC_FRAME_MIN_WIDTH 16 +#define VIMC_FRAME_MIN_HEIGHT 16 + +#define VIMC_FRAME_INDEX(lin, col, width, bpp) ((lin * width + col) * bpp) + +/** + * struct vimc_colorimetry_clamp - Adjust colorimetry parameters + * + * @fmt: the pointer to struct v4l2_pix_format or + * struct v4l2_mbus_framefmt + * + * Entities must check if colorimetry given by the userspace is valid, if not + * then set them as DEFAULT + */ +#define vimc_colorimetry_clamp(fmt) \ +do { \ + if ((fmt)->colorspace == V4L2_COLORSPACE_DEFAULT \ + || (fmt)->colorspace > V4L2_COLORSPACE_DCI_P3) { \ + (fmt)->colorspace = V4L2_COLORSPACE_DEFAULT; \ + (fmt)->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; \ + (fmt)->quantization = V4L2_QUANTIZATION_DEFAULT; \ + (fmt)->xfer_func = V4L2_XFER_FUNC_DEFAULT; \ + } \ + if ((fmt)->ycbcr_enc > V4L2_YCBCR_ENC_SMPTE240M) \ + (fmt)->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; \ + if ((fmt)->quantization > V4L2_QUANTIZATION_LIM_RANGE) \ + (fmt)->quantization = V4L2_QUANTIZATION_DEFAULT; \ + if ((fmt)->xfer_func > V4L2_XFER_FUNC_SMPTE2084) \ + (fmt)->xfer_func = V4L2_XFER_FUNC_DEFAULT; \ +} while (0) + +/** + * struct vimc_platform_data - platform data to components + * + * @entity_name: The name of the entity to be created + * + * Board setup code will often provide additional information using the device's + * platform_data field to hold additional information. + * When injecting a new platform_device in the component system the core needs + * to provide to the corresponding submodules the name of the entity that should + * be used when registering the subdevice in the Media Controller system. + */ +struct vimc_platform_data { + char entity_name[32]; +}; + +/** + * struct vimc_pix_map - maps media bus code with v4l2 pixel format + * + * @code: media bus format code defined by MEDIA_BUS_FMT_* macros + * @bbp: number of bytes each pixel occupies + * @pixelformat: pixel format devined by V4L2_PIX_FMT_* macros + * + * Struct which matches the MEDIA_BUS_FMT_* codes with the corresponding + * V4L2_PIX_FMT_* fourcc pixelformat and its bytes per pixel (bpp) + */ +struct vimc_pix_map { + unsigned int code; + unsigned int bpp; + u32 pixelformat; + bool bayer; +}; + +/** + * struct vimc_ent_device - core struct that represents a node in the topology + * + * @ent: the pointer to struct media_entity for the node + * @pads: the list of pads of the node + * @process_frame: callback send a frame to that node + * @vdev_get_format: callback that returns the current format a pad, used + * only when is_media_entity_v4l2_video_device(ent) returns + * true + * + * Each node of the topology must create a vimc_ent_device struct. Depending on + * the node it will be of an instance of v4l2_subdev or video_device struct + * where both contains a struct media_entity. + * Those structures should embedded the vimc_ent_device struct through + * v4l2_set_subdevdata() and video_set_drvdata() respectivaly, allowing the + * vimc_ent_device struct to be retrieved from the corresponding struct + * media_entity + */ +struct vimc_ent_device { + struct media_entity *ent; + struct media_pad *pads; + void (*process_frame)(struct vimc_ent_device *ved, + struct media_pad *sink, const void *frame); + void (*vdev_get_format)(struct vimc_ent_device *ved, + struct v4l2_pix_format *fmt); +}; + +/** + * vimc_propagate_frame - propagate a frame through the topology + * + * @src: the source pad where the frame is being originated + * @frame: the frame to be propagated + * + * This function will call the process_frame callback from the vimc_ent_device + * struct of the nodes directly connected to the @src pad + */ +int vimc_propagate_frame(struct media_pad *src, const void *frame); + +/** + * vimc_pads_init - initialize pads + * + * @num_pads: number of pads to initialize + * @pads_flags: flags to use in each pad + * + * Helper functions to allocate/initialize pads + */ +struct media_pad *vimc_pads_init(u16 num_pads, + const unsigned long *pads_flag); + +/** + * vimc_pads_cleanup - free pads + * + * @pads: pointer to the pads + * + * Helper function to free the pads initialized with vimc_pads_init + */ +static inline void vimc_pads_cleanup(struct media_pad *pads) +{ + kfree(pads); +} + +/** + * vimc_pipeline_s_stream - start stream through the pipeline + * + * @ent: the pointer to struct media_entity for the node + * @enable: 1 to start the stream and 0 to stop + * + * Helper function to call the s_stream of the subdevices connected + * in all the sink pads of the entity + */ +int vimc_pipeline_s_stream(struct media_entity *ent, int enable); + +/** + * vimc_pix_map_by_index - get vimc_pix_map struct by its index + * + * @i: index of the vimc_pix_map struct in vimc_pix_map_list + */ +const struct vimc_pix_map *vimc_pix_map_by_index(unsigned int i); + +/** + * vimc_pix_map_by_code - get vimc_pix_map struct by media bus code + * + * @code: media bus format code defined by MEDIA_BUS_FMT_* macros + */ +const struct vimc_pix_map *vimc_pix_map_by_code(u32 code); + +/** + * vimc_pix_map_by_pixelformat - get vimc_pix_map struct by v4l2 pixel format + * + * @pixelformat: pixel format devined by V4L2_PIX_FMT_* macros + */ +const struct vimc_pix_map *vimc_pix_map_by_pixelformat(u32 pixelformat); + +/** + * vimc_ent_sd_register - initialize and register a subdev node + * + * @ved: the vimc_ent_device struct to be initialize + * @sd: the v4l2_subdev struct to be initialize and registered + * @v4l2_dev: the v4l2 device to register the v4l2_subdev + * @name: name of the sub-device. Please notice that the name must be + * unique. + * @function: media entity function defined by MEDIA_ENT_F_* macros + * @num_pads: number of pads to initialize + * @pads_flag: flags to use in each pad + * @sd_ops: pointer to &struct v4l2_subdev_ops. + * + * Helper function initialize and register the struct vimc_ent_device and struct + * v4l2_subdev which represents a subdev node in the topology + */ +int vimc_ent_sd_register(struct vimc_ent_device *ved, + struct v4l2_subdev *sd, + struct v4l2_device *v4l2_dev, + const char *const name, + u32 function, + u16 num_pads, + const unsigned long *pads_flag, + const struct v4l2_subdev_ops *sd_ops); + +/** + * vimc_ent_sd_unregister - cleanup and unregister a subdev node + * + * @ved: the vimc_ent_device struct to be cleaned up + * @sd: the v4l2_subdev struct to be unregistered + * + * Helper function cleanup and unregister the struct vimc_ent_device and struct + * v4l2_subdev which represents a subdev node in the topology + */ +void vimc_ent_sd_unregister(struct vimc_ent_device *ved, + struct v4l2_subdev *sd); + +/** + * vimc_link_validate - validates a media link + * + * @link: pointer to &struct media_link + * + * This function calls validates if a media link is valid for streaming. + */ +int vimc_link_validate(struct media_link *link); + +#endif diff --git a/drivers/media/platform/vimc/vimc-core.c b/drivers/media/platform/vimc/vimc-core.c index bc107da8fbd5..51c0eee61ca6 100644 --- a/drivers/media/platform/vimc/vimc-core.c +++ b/drivers/media/platform/vimc/vimc-core.c @@ -15,15 +15,14 @@ * */ +#include <linux/component.h> #include <linux/init.h> #include <linux/module.h> #include <linux/platform_device.h> #include <media/media-device.h> #include <media/v4l2-device.h> -#include "vimc-capture.h" -#include "vimc-core.h" -#include "vimc-sensor.h" +#include "vimc-common.h" #define VIMC_PDEV_NAME "vimc" #define VIMC_MDEV_MODEL_NAME "VIMC MDEV" @@ -37,10 +36,10 @@ } struct vimc_device { - /* - * The pipeline configuration - * (filled before calling vimc_device_register) - */ + /* The platform device */ + struct platform_device pdev; + + /* The pipeline configuration */ const struct vimc_pipeline_config *pipe_cfg; /* The Associated media_device parent */ @@ -49,43 +48,14 @@ struct vimc_device { /* Internal v4l2 parent device*/ struct v4l2_device v4l2_dev; - /* Internal topology */ - struct vimc_ent_device **ved; -}; - -/** - * enum vimc_ent_node - Select the functionality of a node in the topology - * @VIMC_ENT_NODE_SENSOR: A node of type SENSOR simulates a camera sensor - * generating internal images in bayer format and - * propagating those images through the pipeline - * @VIMC_ENT_NODE_CAPTURE: A node of type CAPTURE is a v4l2 video_device - * that exposes the received image from the - * pipeline to the user space - * @VIMC_ENT_NODE_INPUT: A node of type INPUT is a v4l2 video_device that - * receives images from the user space and - * propagates them through the pipeline - * @VIMC_ENT_NODE_DEBAYER: A node type DEBAYER expects to receive a frame - * in bayer format converts it to RGB - * @VIMC_ENT_NODE_SCALER: A node of type SCALER scales the received image - * by a given multiplier - * - * This enum is used in the entity configuration struct to allow the definition - * of a custom topology specifying the role of each node on it. - */ -enum vimc_ent_node { - VIMC_ENT_NODE_SENSOR, - VIMC_ENT_NODE_CAPTURE, - VIMC_ENT_NODE_INPUT, - VIMC_ENT_NODE_DEBAYER, - VIMC_ENT_NODE_SCALER, + /* Subdevices */ + struct platform_device **subdevs; }; /* Structure which describes individual configuration for each entity */ struct vimc_ent_config { const char *name; - size_t pads_qty; - const unsigned long *pads_flag; - enum vimc_ent_node node; + const char *drv; }; /* Structure which describes links between entities */ @@ -112,60 +82,40 @@ struct vimc_pipeline_config { static const struct vimc_ent_config ent_config[] = { { .name = "Sensor A", - .pads_qty = 1, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SOURCE}, - .node = VIMC_ENT_NODE_SENSOR, + .drv = "vimc-sensor", }, { .name = "Sensor B", - .pads_qty = 1, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SOURCE}, - .node = VIMC_ENT_NODE_SENSOR, + .drv = "vimc-sensor", }, { .name = "Debayer A", - .pads_qty = 2, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SINK, - MEDIA_PAD_FL_SOURCE}, - .node = VIMC_ENT_NODE_DEBAYER, + .drv = "vimc-debayer", }, { .name = "Debayer B", - .pads_qty = 2, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SINK, - MEDIA_PAD_FL_SOURCE}, - .node = VIMC_ENT_NODE_DEBAYER, + .drv = "vimc-debayer", }, { .name = "Raw Capture 0", - .pads_qty = 1, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SINK}, - .node = VIMC_ENT_NODE_CAPTURE, + .drv = "vimc-capture", }, { .name = "Raw Capture 1", - .pads_qty = 1, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SINK}, - .node = VIMC_ENT_NODE_CAPTURE, + .drv = "vimc-capture", }, { .name = "RGB/YUV Input", - .pads_qty = 1, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SOURCE}, - .node = VIMC_ENT_NODE_INPUT, + /* TODO: change this to vimc-input when it is implemented */ + .drv = "vimc-sensor", }, { .name = "Scaler", - .pads_qty = 2, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SINK, - MEDIA_PAD_FL_SOURCE}, - .node = VIMC_ENT_NODE_SCALER, + .drv = "vimc-scaler", }, { .name = "RGB/YUV Capture", - .pads_qty = 1, - .pads_flag = (const unsigned long[]){MEDIA_PAD_FL_SINK}, - .node = VIMC_ENT_NODE_CAPTURE, + .drv = "vimc-capture", }, }; @@ -197,314 +147,40 @@ static const struct vimc_pipeline_config pipe_cfg = { /* -------------------------------------------------------------------------- */ -static const struct vimc_pix_map vimc_pix_map_list[] = { - /* TODO: add all missing formats */ - - /* RGB formats */ - { - .code = MEDIA_BUS_FMT_BGR888_1X24, - .pixelformat = V4L2_PIX_FMT_BGR24, - .bpp = 3, - }, - { - .code = MEDIA_BUS_FMT_RGB888_1X24, - .pixelformat = V4L2_PIX_FMT_RGB24, - .bpp = 3, - }, - { - .code = MEDIA_BUS_FMT_ARGB8888_1X32, - .pixelformat = V4L2_PIX_FMT_ARGB32, - .bpp = 4, - }, - - /* Bayer formats */ - { - .code = MEDIA_BUS_FMT_SBGGR8_1X8, - .pixelformat = V4L2_PIX_FMT_SBGGR8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SGBRG8_1X8, - .pixelformat = V4L2_PIX_FMT_SGBRG8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SGRBG8_1X8, - .pixelformat = V4L2_PIX_FMT_SGRBG8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SRGGB8_1X8, - .pixelformat = V4L2_PIX_FMT_SRGGB8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SBGGR10_1X10, - .pixelformat = V4L2_PIX_FMT_SBGGR10, - .bpp = 2, - }, - { - .code = MEDIA_BUS_FMT_SGBRG10_1X10, - .pixelformat = V4L2_PIX_FMT_SGBRG10, - .bpp = 2, - }, - { - .code = MEDIA_BUS_FMT_SGRBG10_1X10, - .pixelformat = V4L2_PIX_FMT_SGRBG10, - .bpp = 2, - }, - { - .code = MEDIA_BUS_FMT_SRGGB10_1X10, - .pixelformat = V4L2_PIX_FMT_SRGGB10, - .bpp = 2, - }, - - /* 10bit raw bayer a-law compressed to 8 bits */ - { - .code = MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8, - .pixelformat = V4L2_PIX_FMT_SBGGR10ALAW8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8, - .pixelformat = V4L2_PIX_FMT_SGBRG10ALAW8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8, - .pixelformat = V4L2_PIX_FMT_SGRBG10ALAW8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8, - .pixelformat = V4L2_PIX_FMT_SRGGB10ALAW8, - .bpp = 1, - }, - - /* 10bit raw bayer DPCM compressed to 8 bits */ - { - .code = MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8, - .pixelformat = V4L2_PIX_FMT_SBGGR10DPCM8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8, - .pixelformat = V4L2_PIX_FMT_SGBRG10DPCM8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, - .pixelformat = V4L2_PIX_FMT_SGRBG10DPCM8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8, - .pixelformat = V4L2_PIX_FMT_SRGGB10DPCM8, - .bpp = 1, - }, - { - .code = MEDIA_BUS_FMT_SBGGR12_1X12, - .pixelformat = V4L2_PIX_FMT_SBGGR12, - .bpp = 2, - }, - { - .code = MEDIA_BUS_FMT_SGBRG12_1X12, - .pixelformat = V4L2_PIX_FMT_SGBRG12, - .bpp = 2, - }, - { - .code = MEDIA_BUS_FMT_SGRBG12_1X12, - .pixelformat = V4L2_PIX_FMT_SGRBG12, - .bpp = 2, - }, - { - .code = MEDIA_BUS_FMT_SRGGB12_1X12, - .pixelformat = V4L2_PIX_FMT_SRGGB12, - .bpp = 2, - }, -}; - -const struct vimc_pix_map *vimc_pix_map_by_code(u32 code) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(vimc_pix_map_list); i++) { - if (vimc_pix_map_list[i].code == code) - return &vimc_pix_map_list[i]; - } - return NULL; -} - -const struct vimc_pix_map *vimc_pix_map_by_pixelformat(u32 pixelformat) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(vimc_pix_map_list); i++) { - if (vimc_pix_map_list[i].pixelformat == pixelformat) - return &vimc_pix_map_list[i]; - } - return NULL; -} - -int vimc_propagate_frame(struct media_pad *src, const void *frame) -{ - struct media_link *link; - - if (!(src->flags & MEDIA_PAD_FL_SOURCE)) - return -EINVAL; - - /* Send this frame to all sink pads that are direct linked */ - list_for_each_entry(link, &src->entity->links, list) { - if (link->source == src && - (link->flags & MEDIA_LNK_FL_ENABLED)) { - struct vimc_ent_device *ved = NULL; - struct media_entity *entity = link->sink->entity; - - if (is_media_entity_v4l2_subdev(entity)) { - struct v4l2_subdev *sd = - container_of(entity, struct v4l2_subdev, - entity); - ved = v4l2_get_subdevdata(sd); - } else if (is_media_entity_v4l2_video_device(entity)) { - struct video_device *vdev = - container_of(entity, - struct video_device, - entity); - ved = video_get_drvdata(vdev); - } - if (ved && ved->process_frame) - ved->process_frame(ved, link->sink, frame); - } - } - - return 0; -} - -static void vimc_device_unregister(struct vimc_device *vimc) -{ - unsigned int i; - - media_device_unregister(&vimc->mdev); - /* Cleanup (only initialized) entities */ - for (i = 0; i < vimc->pipe_cfg->num_ents; i++) { - if (vimc->ved[i] && vimc->ved[i]->destroy) - vimc->ved[i]->destroy(vimc->ved[i]); - - vimc->ved[i] = NULL; - } - v4l2_device_unregister(&vimc->v4l2_dev); - media_device_cleanup(&vimc->mdev); -} - -/* Helper function to allocate and initialize pads */ -struct media_pad *vimc_pads_init(u16 num_pads, const unsigned long *pads_flag) +static int vimc_create_links(struct vimc_device *vimc) { - struct media_pad *pads; unsigned int i; - - /* Allocate memory for the pads */ - pads = kcalloc(num_pads, sizeof(*pads), GFP_KERNEL); - if (!pads) - return ERR_PTR(-ENOMEM); - - /* Initialize the pads */ - for (i = 0; i < num_pads; i++) { - pads[i].index = i; - pads[i].flags = pads_flag[i]; - } - - return pads; -} - -/* - * TODO: remove this function when all the - * entities specific code are implemented - */ -static void vimc_raw_destroy(struct vimc_ent_device *ved) -{ - media_device_unregister_entity(ved->ent); - - media_entity_cleanup(ved->ent); - - vimc_pads_cleanup(ved->pads); - - kfree(ved->ent); - - kfree(ved); -} - -/* - * TODO: remove this function when all the - * entities specific code are implemented - */ -static struct vimc_ent_device *vimc_raw_create(struct v4l2_device *v4l2_dev, - const char *const name, - u16 num_pads, - const unsigned long *pads_flag) -{ - struct vimc_ent_device *ved; int ret; - /* Allocate the main ved struct */ - ved = kzalloc(sizeof(*ved), GFP_KERNEL); - if (!ved) - return ERR_PTR(-ENOMEM); - - /* Allocate the media entity */ - ved->ent = kzalloc(sizeof(*ved->ent), GFP_KERNEL); - if (!ved->ent) { - ret = -ENOMEM; - goto err_free_ved; - } - - /* Allocate the pads */ - ved->pads = vimc_pads_init(num_pads, pads_flag); - if (IS_ERR(ved->pads)) { - ret = PTR_ERR(ved->pads); - goto err_free_ent; + /* Initialize the links between entities */ + for (i = 0; i < vimc->pipe_cfg->num_links; i++) { + const struct vimc_ent_link *link = &vimc->pipe_cfg->links[i]; + /* + * TODO: Check another way of retrieving ved struct without + * relying on platform_get_drvdata + */ + struct vimc_ent_device *ved_src = + platform_get_drvdata(vimc->subdevs[link->src_ent]); + struct vimc_ent_device *ved_sink = + platform_get_drvdata(vimc->subdevs[link->sink_ent]); + + ret = media_create_pad_link(ved_src->ent, link->src_pad, + ved_sink->ent, link->sink_pad, + link->flags); + if (ret) + return ret; } - /* Initialize the media entity */ - ved->ent->name = name; - ved->ent->function = MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN; - ret = media_entity_pads_init(ved->ent, num_pads, ved->pads); - if (ret) - goto err_cleanup_pads; - - /* Register the media entity */ - ret = media_device_register_entity(v4l2_dev->mdev, ved->ent); - if (ret) - goto err_cleanup_entity; - - /* Fill out the destroy function and return */ - ved->destroy = vimc_raw_destroy; - return ved; - -err_cleanup_entity: - media_entity_cleanup(ved->ent); -err_cleanup_pads: - vimc_pads_cleanup(ved->pads); -err_free_ent: - kfree(ved->ent); -err_free_ved: - kfree(ved); - - return ERR_PTR(ret); + return 0; } -static int vimc_device_register(struct vimc_device *vimc) +static int vimc_comp_bind(struct device *master) { - unsigned int i; + struct vimc_device *vimc = container_of(to_platform_device(master), + struct vimc_device, pdev); int ret; - /* Allocate memory for the vimc_ent_devices pointers */ - vimc->ved = devm_kcalloc(vimc->mdev.dev, vimc->pipe_cfg->num_ents, - sizeof(*vimc->ved), GFP_KERNEL); - if (!vimc->ved) - return -ENOMEM; - - /* Link the media device within the v4l2_device */ - vimc->v4l2_dev.mdev = &vimc->mdev; + dev_dbg(master, "bind"); /* Register the v4l2 struct */ ret = v4l2_device_register(vimc->mdev.dev, &vimc->v4l2_dev); @@ -514,66 +190,22 @@ static int vimc_device_register(struct vimc_device *vimc) return ret; } - /* Initialize entities */ - for (i = 0; i < vimc->pipe_cfg->num_ents; i++) { - struct vimc_ent_device *(*create_func)(struct v4l2_device *, - const char *const, - u16, - const unsigned long *); - - /* Register the specific node */ - switch (vimc->pipe_cfg->ents[i].node) { - case VIMC_ENT_NODE_SENSOR: - create_func = vimc_sen_create; - break; - - case VIMC_ENT_NODE_CAPTURE: - create_func = vimc_cap_create; - break; - - /* TODO: Instantiate the specific topology node */ - case VIMC_ENT_NODE_INPUT: - case VIMC_ENT_NODE_DEBAYER: - case VIMC_ENT_NODE_SCALER: - default: - /* - * TODO: remove this when all the entities specific - * code are implemented - */ - create_func = vimc_raw_create; - break; - } - - vimc->ved[i] = create_func(&vimc->v4l2_dev, - vimc->pipe_cfg->ents[i].name, - vimc->pipe_cfg->ents[i].pads_qty, - vimc->pipe_cfg->ents[i].pads_flag); - if (IS_ERR(vimc->ved[i])) { - ret = PTR_ERR(vimc->ved[i]); - vimc->ved[i] = NULL; - goto err; - } - } - - /* Initialize the links between entities */ - for (i = 0; i < vimc->pipe_cfg->num_links; i++) { - const struct vimc_ent_link *link = &vimc->pipe_cfg->links[i]; + /* Bind subdevices */ + ret = component_bind_all(master, &vimc->v4l2_dev); + if (ret) + goto err_v4l2_unregister; - ret = media_create_pad_link(vimc->ved[link->src_ent]->ent, - link->src_pad, - vimc->ved[link->sink_ent]->ent, - link->sink_pad, - link->flags); - if (ret) - goto err; - } + /* Initialize links */ + ret = vimc_create_links(vimc); + if (ret) + goto err_comp_unbind_all; /* Register the media device */ ret = media_device_register(&vimc->mdev); if (ret) { dev_err(vimc->mdev.dev, "media device register failed (err=%d)\n", ret); - return ret; + goto err_comp_unbind_all; } /* Expose all subdev's nodes*/ @@ -582,32 +214,106 @@ static int vimc_device_register(struct vimc_device *vimc) dev_err(vimc->mdev.dev, "vimc subdev nodes registration failed (err=%d)\n", ret); - goto err; + goto err_mdev_unregister; } return 0; -err: - /* Destroy the so far created topology */ - vimc_device_unregister(vimc); +err_mdev_unregister: + media_device_unregister(&vimc->mdev); +err_comp_unbind_all: + component_unbind_all(master, NULL); +err_v4l2_unregister: + v4l2_device_unregister(&vimc->v4l2_dev); return ret; } +static void vimc_comp_unbind(struct device *master) +{ + struct vimc_device *vimc = container_of(to_platform_device(master), + struct vimc_device, pdev); + + dev_dbg(master, "unbind"); + + media_device_unregister(&vimc->mdev); + component_unbind_all(master, NULL); + v4l2_device_unregister(&vimc->v4l2_dev); +} + +static int vimc_comp_compare(struct device *comp, void *data) +{ + const struct platform_device *pdev = to_platform_device(comp); + const char *name = data; + + return !strcmp(pdev->dev.platform_data, name); +} + +static struct component_match *vimc_add_subdevs(struct vimc_device *vimc) +{ + struct component_match *match = NULL; + struct vimc_platform_data pdata; + int i; + + for (i = 0; i < vimc->pipe_cfg->num_ents; i++) { + dev_dbg(&vimc->pdev.dev, "new pdev for %s\n", + vimc->pipe_cfg->ents[i].drv); + + strlcpy(pdata.entity_name, vimc->pipe_cfg->ents[i].name, + sizeof(pdata.entity_name)); + + vimc->subdevs[i] = platform_device_register_data(&vimc->pdev.dev, + vimc->pipe_cfg->ents[i].drv, + PLATFORM_DEVID_AUTO, + &pdata, + sizeof(pdata)); + if (!vimc->subdevs[i]) { + while (--i >= 0) + platform_device_unregister(vimc->subdevs[i]); + + return ERR_PTR(-ENOMEM); + } + + component_match_add(&vimc->pdev.dev, &match, vimc_comp_compare, + (void *)vimc->pipe_cfg->ents[i].name); + } + + return match; +} + +static void vimc_rm_subdevs(struct vimc_device *vimc) +{ + unsigned int i; + + for (i = 0; i < vimc->pipe_cfg->num_ents; i++) + platform_device_unregister(vimc->subdevs[i]); +} + +static const struct component_master_ops vimc_comp_ops = { + .bind = vimc_comp_bind, + .unbind = vimc_comp_unbind, +}; + static int vimc_probe(struct platform_device *pdev) { - struct vimc_device *vimc; + struct vimc_device *vimc = container_of(pdev, struct vimc_device, pdev); + struct component_match *match = NULL; int ret; - /* Prepare the vimc topology structure */ + dev_dbg(&pdev->dev, "probe"); - /* Allocate memory for the vimc structure */ - vimc = kzalloc(sizeof(*vimc), GFP_KERNEL); - if (!vimc) + /* Create platform_device for each entity in the topology*/ + vimc->subdevs = devm_kcalloc(&vimc->pdev.dev, vimc->pipe_cfg->num_ents, + sizeof(*vimc->subdevs), GFP_KERNEL); + if (!vimc->subdevs) return -ENOMEM; - /* Set the pipeline configuration struct */ - vimc->pipe_cfg = &pipe_cfg; + match = vimc_add_subdevs(vimc); + if (IS_ERR(match)) + return PTR_ERR(match); + + /* Link the media device within the v4l2_device */ + vimc->v4l2_dev.mdev = &vimc->mdev; /* Initialize media device */ strlcpy(vimc->mdev.model, VIMC_MDEV_MODEL_NAME, @@ -615,28 +321,27 @@ static int vimc_probe(struct platform_device *pdev) vimc->mdev.dev = &pdev->dev; media_device_init(&vimc->mdev); - /* Create vimc topology */ - ret = vimc_device_register(vimc); + /* Add self to the component system */ + ret = component_master_add_with_match(&pdev->dev, &vimc_comp_ops, + match); if (ret) { - dev_err(vimc->mdev.dev, - "vimc device registration failed (err=%d)\n", ret); + media_device_cleanup(&vimc->mdev); + vimc_rm_subdevs(vimc); kfree(vimc); return ret; } - /* Link the topology object with the platform device object */ - platform_set_drvdata(pdev, vimc); - return 0; } static int vimc_remove(struct platform_device *pdev) { - struct vimc_device *vimc = platform_get_drvdata(pdev); + struct vimc_device *vimc = container_of(pdev, struct vimc_device, pdev); + + dev_dbg(&pdev->dev, "remove"); - /* Destroy all the topology */ - vimc_device_unregister(vimc); - kfree(vimc); + component_master_del(&pdev->dev, &vimc_comp_ops); + vimc_rm_subdevs(vimc); return 0; } @@ -645,9 +350,12 @@ static void vimc_dev_release(struct device *dev) { } -static struct platform_device vimc_pdev = { - .name = VIMC_PDEV_NAME, - .dev.release = vimc_dev_release, +static struct vimc_device vimc_dev = { + .pipe_cfg = &pipe_cfg, + .pdev = { + .name = VIMC_PDEV_NAME, + .dev.release = vimc_dev_release, + } }; static struct platform_driver vimc_pdrv = { @@ -662,29 +370,29 @@ static int __init vimc_init(void) { int ret; - ret = platform_device_register(&vimc_pdev); + ret = platform_device_register(&vimc_dev.pdev); if (ret) { - dev_err(&vimc_pdev.dev, + dev_err(&vimc_dev.pdev.dev, "platform device registration failed (err=%d)\n", ret); return ret; } ret = platform_driver_register(&vimc_pdrv); if (ret) { - dev_err(&vimc_pdev.dev, + dev_err(&vimc_dev.pdev.dev, "platform driver registration failed (err=%d)\n", ret); - - platform_device_unregister(&vimc_pdev); + platform_driver_unregister(&vimc_pdrv); + return ret; } - return ret; + return 0; } static void __exit vimc_exit(void) { platform_driver_unregister(&vimc_pdrv); - platform_device_unregister(&vimc_pdev); + platform_device_unregister(&vimc_dev.pdev); } module_init(vimc_init); diff --git a/drivers/media/platform/vimc/vimc-core.h b/drivers/media/platform/vimc/vimc-core.h deleted file mode 100644 index 4525d23211ca..000000000000 --- a/drivers/media/platform/vimc/vimc-core.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * vimc-core.h Virtual Media Controller Driver - * - * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef _VIMC_CORE_H_ -#define _VIMC_CORE_H_ - -#include <linux/slab.h> -#include <media/v4l2-device.h> - -/** - * struct vimc_pix_map - maps media bus code with v4l2 pixel format - * - * @code: media bus format code defined by MEDIA_BUS_FMT_* macros - * @bbp: number of bytes each pixel occupies - * @pixelformat: pixel format devined by V4L2_PIX_FMT_* macros - * - * Struct which matches the MEDIA_BUS_FMT_* codes with the corresponding - * V4L2_PIX_FMT_* fourcc pixelformat and its bytes per pixel (bpp) - */ -struct vimc_pix_map { - unsigned int code; - unsigned int bpp; - u32 pixelformat; -}; - -/** - * struct vimc_ent_device - core struct that represents a node in the topology - * - * @ent: the pointer to struct media_entity for the node - * @pads: the list of pads of the node - * @destroy: callback to destroy the node - * @process_frame: callback send a frame to that node - * - * Each node of the topology must create a vimc_ent_device struct. Depending on - * the node it will be of an instance of v4l2_subdev or video_device struct - * where both contains a struct media_entity. - * Those structures should embedded the vimc_ent_device struct through - * v4l2_set_subdevdata() and video_set_drvdata() respectivaly, allowing the - * vimc_ent_device struct to be retrieved from the corresponding struct - * media_entity - */ -struct vimc_ent_device { - struct media_entity *ent; - struct media_pad *pads; - void (*destroy)(struct vimc_ent_device *); - void (*process_frame)(struct vimc_ent_device *ved, - struct media_pad *sink, const void *frame); -}; - -/** - * vimc_propagate_frame - propagate a frame through the topology - * - * @src: the source pad where the frame is being originated - * @frame: the frame to be propagated - * - * This function will call the process_frame callback from the vimc_ent_device - * struct of the nodes directly connected to the @src pad - */ -int vimc_propagate_frame(struct media_pad *src, const void *frame); - -/** - * vimc_pads_init - initialize pads - * - * @num_pads: number of pads to initialize - * @pads_flags: flags to use in each pad - * - * Helper functions to allocate/initialize pads - */ -struct media_pad *vimc_pads_init(u16 num_pads, - const unsigned long *pads_flag); - -/** - * vimc_pads_cleanup - free pads - * - * @pads: pointer to the pads - * - * Helper function to free the pads initialized with vimc_pads_init - */ -static inline void vimc_pads_cleanup(struct media_pad *pads) -{ - kfree(pads); -} - -/** - * vimc_pix_map_by_code - get vimc_pix_map struct by media bus code - * - * @code: media bus format code defined by MEDIA_BUS_FMT_* macros - */ -const struct vimc_pix_map *vimc_pix_map_by_code(u32 code); - -/** - * vimc_pix_map_by_pixelformat - get vimc_pix_map struct by v4l2 pixel format - * - * @pixelformat: pixel format devined by V4L2_PIX_FMT_* macros - */ -const struct vimc_pix_map *vimc_pix_map_by_pixelformat(u32 pixelformat); - -#endif diff --git a/drivers/media/platform/vimc/vimc-debayer.c b/drivers/media/platform/vimc/vimc-debayer.c new file mode 100644 index 000000000000..35b15bd4d61d --- /dev/null +++ b/drivers/media/platform/vimc/vimc-debayer.c @@ -0,0 +1,601 @@ +/* + * vimc-debayer.c Virtual Media Controller Driver + * + * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/vmalloc.h> +#include <linux/v4l2-mediabus.h> +#include <media/v4l2-subdev.h> + +#include "vimc-common.h" + +#define VIMC_DEB_DRV_NAME "vimc-debayer" + +static unsigned int deb_mean_win_size = 3; +module_param(deb_mean_win_size, uint, 0000); +MODULE_PARM_DESC(deb_mean_win_size, " the window size to calculate the mean.\n" + "NOTE: the window size need to be an odd number, as the main pixel " + "stays in the center of the window, otherwise the next odd number " + "is considered"); + +#define IS_SINK(pad) (!pad) +#define IS_SRC(pad) (pad) + +enum vimc_deb_rgb_colors { + VIMC_DEB_RED = 0, + VIMC_DEB_GREEN = 1, + VIMC_DEB_BLUE = 2, +}; + +struct vimc_deb_pix_map { + u32 code; + enum vimc_deb_rgb_colors order[2][2]; +}; + +struct vimc_deb_device { + struct vimc_ent_device ved; + struct v4l2_subdev sd; + struct device *dev; + /* The active format */ + struct v4l2_mbus_framefmt sink_fmt; + u32 src_code; + void (*set_rgb_src)(struct vimc_deb_device *vdeb, unsigned int lin, + unsigned int col, unsigned int rgb[3]); + /* Values calculated when the stream starts */ + u8 *src_frame; + const struct vimc_deb_pix_map *sink_pix_map; + unsigned int sink_bpp; +}; + +static const struct v4l2_mbus_framefmt sink_fmt_default = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, +}; + +static const struct vimc_deb_pix_map vimc_deb_pix_map_list[] = { + { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .order = { { VIMC_DEB_BLUE, VIMC_DEB_GREEN }, + { VIMC_DEB_GREEN, VIMC_DEB_RED } } + }, + { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .order = { { VIMC_DEB_GREEN, VIMC_DEB_BLUE }, + { VIMC_DEB_RED, VIMC_DEB_GREEN } } + }, + { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .order = { { VIMC_DEB_GREEN, VIMC_DEB_RED }, + { VIMC_DEB_BLUE, VIMC_DEB_GREEN } } + }, + { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .order = { { VIMC_DEB_RED, VIMC_DEB_GREEN }, + { VIMC_DEB_GREEN, VIMC_DEB_BLUE } } + }, + { + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .order = { { VIMC_DEB_BLUE, VIMC_DEB_GREEN }, + { VIMC_DEB_GREEN, VIMC_DEB_RED } } + }, + { + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .order = { { VIMC_DEB_GREEN, VIMC_DEB_BLUE }, + { VIMC_DEB_RED, VIMC_DEB_GREEN } } + }, + { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .order = { { VIMC_DEB_GREEN, VIMC_DEB_RED }, + { VIMC_DEB_BLUE, VIMC_DEB_GREEN } } + }, + { + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .order = { { VIMC_DEB_RED, VIMC_DEB_GREEN }, + { VIMC_DEB_GREEN, VIMC_DEB_BLUE } } + }, + { + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .order = { { VIMC_DEB_BLUE, VIMC_DEB_GREEN }, + { VIMC_DEB_GREEN, VIMC_DEB_RED } } + }, + { + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .order = { { VIMC_DEB_GREEN, VIMC_DEB_BLUE }, + { VIMC_DEB_RED, VIMC_DEB_GREEN } } + }, + { + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .order = { { VIMC_DEB_GREEN, VIMC_DEB_RED }, + { VIMC_DEB_BLUE, VIMC_DEB_GREEN } } + }, + { + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .order = { { VIMC_DEB_RED, VIMC_DEB_GREEN }, + { VIMC_DEB_GREEN, VIMC_DEB_BLUE } } + }, +}; + +static const struct vimc_deb_pix_map *vimc_deb_pix_map_by_code(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vimc_deb_pix_map_list); i++) + if (vimc_deb_pix_map_list[i].code == code) + return &vimc_deb_pix_map_list[i]; + + return NULL; +} + +static int vimc_deb_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ + struct vimc_deb_device *vdeb = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *mf; + unsigned int i; + + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + *mf = sink_fmt_default; + + for (i = 1; i < sd->entity.num_pads; i++) { + mf = v4l2_subdev_get_try_format(sd, cfg, i); + *mf = sink_fmt_default; + mf->code = vdeb->src_code; + } + + return 0; +} + +static int vimc_deb_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + /* We only support one format for source pads */ + if (IS_SRC(code->pad)) { + struct vimc_deb_device *vdeb = v4l2_get_subdevdata(sd); + + if (code->index) + return -EINVAL; + + code->code = vdeb->src_code; + } else { + if (code->index >= ARRAY_SIZE(vimc_deb_pix_map_list)) + return -EINVAL; + + code->code = vimc_deb_pix_map_list[code->index].code; + } + + return 0; +} + +static int vimc_deb_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vimc_deb_device *vdeb = v4l2_get_subdevdata(sd); + + if (fse->index) + return -EINVAL; + + if (IS_SINK(fse->pad)) { + const struct vimc_deb_pix_map *vpix = + vimc_deb_pix_map_by_code(fse->code); + + if (!vpix) + return -EINVAL; + } else if (fse->code != vdeb->src_code) { + return -EINVAL; + } + + fse->min_width = VIMC_FRAME_MIN_WIDTH; + fse->max_width = VIMC_FRAME_MAX_WIDTH; + fse->min_height = VIMC_FRAME_MIN_HEIGHT; + fse->max_height = VIMC_FRAME_MAX_HEIGHT; + + return 0; +} + +static int vimc_deb_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct vimc_deb_device *vdeb = v4l2_get_subdevdata(sd); + + /* Get the current sink format */ + fmt->format = fmt->which == V4L2_SUBDEV_FORMAT_TRY ? + *v4l2_subdev_get_try_format(sd, cfg, 0) : + vdeb->sink_fmt; + + /* Set the right code for the source pad */ + if (IS_SRC(fmt->pad)) + fmt->format.code = vdeb->src_code; + + return 0; +} + +static void vimc_deb_adjust_sink_fmt(struct v4l2_mbus_framefmt *fmt) +{ + const struct vimc_deb_pix_map *vpix; + + /* Don't accept a code that is not on the debayer table */ + vpix = vimc_deb_pix_map_by_code(fmt->code); + if (!vpix) + fmt->code = sink_fmt_default.code; + + fmt->width = clamp_t(u32, fmt->width, VIMC_FRAME_MIN_WIDTH, + VIMC_FRAME_MAX_WIDTH) & ~1; + fmt->height = clamp_t(u32, fmt->height, VIMC_FRAME_MIN_HEIGHT, + VIMC_FRAME_MAX_HEIGHT) & ~1; + + if (fmt->field == V4L2_FIELD_ANY) + fmt->field = sink_fmt_default.field; + + vimc_colorimetry_clamp(fmt); +} + +static int vimc_deb_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct vimc_deb_device *vdeb = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *sink_fmt; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + /* Do not change the format while stream is on */ + if (vdeb->src_frame) + return -EBUSY; + + sink_fmt = &vdeb->sink_fmt; + } else { + sink_fmt = v4l2_subdev_get_try_format(sd, cfg, 0); + } + + /* + * Do not change the format of the source pad, + * it is propagated from the sink + */ + if (IS_SRC(fmt->pad)) { + fmt->format = *sink_fmt; + /* TODO: Add support for other formats */ + fmt->format.code = vdeb->src_code; + } else { + /* Set the new format in the sink pad */ + vimc_deb_adjust_sink_fmt(&fmt->format); + + dev_dbg(vdeb->dev, "%s: sink format update: " + "old:%dx%d (0x%x, %d, %d, %d, %d) " + "new:%dx%d (0x%x, %d, %d, %d, %d)\n", vdeb->sd.name, + /* old */ + sink_fmt->width, sink_fmt->height, sink_fmt->code, + sink_fmt->colorspace, sink_fmt->quantization, + sink_fmt->xfer_func, sink_fmt->ycbcr_enc, + /* new */ + fmt->format.width, fmt->format.height, fmt->format.code, + fmt->format.colorspace, fmt->format.quantization, + fmt->format.xfer_func, fmt->format.ycbcr_enc); + + *sink_fmt = fmt->format; + } + + return 0; +} + +static const struct v4l2_subdev_pad_ops vimc_deb_pad_ops = { + .init_cfg = vimc_deb_init_cfg, + .enum_mbus_code = vimc_deb_enum_mbus_code, + .enum_frame_size = vimc_deb_enum_frame_size, + .get_fmt = vimc_deb_get_fmt, + .set_fmt = vimc_deb_set_fmt, +}; + +static void vimc_deb_set_rgb_mbus_fmt_rgb888_1x24(struct vimc_deb_device *vdeb, + unsigned int lin, + unsigned int col, + unsigned int rgb[3]) +{ + unsigned int i, index; + + index = VIMC_FRAME_INDEX(lin, col, vdeb->sink_fmt.width, 3); + for (i = 0; i < 3; i++) + vdeb->src_frame[index + i] = rgb[i]; +} + +static int vimc_deb_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct vimc_deb_device *vdeb = v4l2_get_subdevdata(sd); + int ret; + + if (enable) { + const struct vimc_pix_map *vpix; + unsigned int frame_size; + + if (vdeb->src_frame) + return 0; + + /* Calculate the frame size of the source pad */ + vpix = vimc_pix_map_by_code(vdeb->src_code); + frame_size = vdeb->sink_fmt.width * vdeb->sink_fmt.height * + vpix->bpp; + + /* Save the bytes per pixel of the sink */ + vpix = vimc_pix_map_by_code(vdeb->sink_fmt.code); + vdeb->sink_bpp = vpix->bpp; + + /* Get the corresponding pixel map from the table */ + vdeb->sink_pix_map = + vimc_deb_pix_map_by_code(vdeb->sink_fmt.code); + + /* + * Allocate the frame buffer. Use vmalloc to be able to + * allocate a large amount of memory + */ + vdeb->src_frame = vmalloc(frame_size); + if (!vdeb->src_frame) + return -ENOMEM; + + /* Turn the stream on in the subdevices directly connected */ + ret = vimc_pipeline_s_stream(&vdeb->sd.entity, 1); + if (ret) { + vfree(vdeb->src_frame); + vdeb->src_frame = NULL; + return ret; + } + } else { + if (!vdeb->src_frame) + return 0; + + /* Disable streaming from the pipe */ + ret = vimc_pipeline_s_stream(&vdeb->sd.entity, 0); + if (ret) + return ret; + + vfree(vdeb->src_frame); + vdeb->src_frame = NULL; + } + + return 0; +} + +static struct v4l2_subdev_video_ops vimc_deb_video_ops = { + .s_stream = vimc_deb_s_stream, +}; + +static const struct v4l2_subdev_ops vimc_deb_ops = { + .pad = &vimc_deb_pad_ops, + .video = &vimc_deb_video_ops, +}; + +static unsigned int vimc_deb_get_val(const u8 *bytes, + const unsigned int n_bytes) +{ + unsigned int i; + unsigned int acc = 0; + + for (i = 0; i < n_bytes; i++) + acc = acc + (bytes[i] << (8 * i)); + + return acc; +} + +static void vimc_deb_calc_rgb_sink(struct vimc_deb_device *vdeb, + const u8 *frame, + const unsigned int lin, + const unsigned int col, + unsigned int rgb[3]) +{ + unsigned int i, seek, wlin, wcol; + unsigned int n_rgb[3] = {0, 0, 0}; + + for (i = 0; i < 3; i++) + rgb[i] = 0; + + /* + * Calculate how many we need to subtract to get to the pixel in + * the top left corner of the mean window (considering the current + * pixel as the center) + */ + seek = deb_mean_win_size / 2; + + /* Sum the values of the colors in the mean window */ + + dev_dbg(vdeb->dev, + "deb: %s: --- Calc pixel %dx%d, window mean %d, seek %d ---\n", + vdeb->sd.name, lin, col, vdeb->sink_fmt.height, seek); + + /* + * Iterate through all the lines in the mean window, start + * with zero if the pixel is outside the frame and don't pass + * the height when the pixel is in the bottom border of the + * frame + */ + for (wlin = seek > lin ? 0 : lin - seek; + wlin < lin + seek + 1 && wlin < vdeb->sink_fmt.height; + wlin++) { + + /* + * Iterate through all the columns in the mean window, start + * with zero if the pixel is outside the frame and don't pass + * the width when the pixel is in the right border of the + * frame + */ + for (wcol = seek > col ? 0 : col - seek; + wcol < col + seek + 1 && wcol < vdeb->sink_fmt.width; + wcol++) { + enum vimc_deb_rgb_colors color; + unsigned int index; + + /* Check which color this pixel is */ + color = vdeb->sink_pix_map->order[wlin % 2][wcol % 2]; + + index = VIMC_FRAME_INDEX(wlin, wcol, + vdeb->sink_fmt.width, + vdeb->sink_bpp); + + dev_dbg(vdeb->dev, + "deb: %s: RGB CALC: frame index %d, win pos %dx%d, color %d\n", + vdeb->sd.name, index, wlin, wcol, color); + + /* Get its value */ + rgb[color] = rgb[color] + + vimc_deb_get_val(&frame[index], vdeb->sink_bpp); + + /* Save how many values we already added */ + n_rgb[color]++; + + dev_dbg(vdeb->dev, "deb: %s: RGB CALC: val %d, n %d\n", + vdeb->sd.name, rgb[color], n_rgb[color]); + } + } + + /* Calculate the mean */ + for (i = 0; i < 3; i++) { + dev_dbg(vdeb->dev, + "deb: %s: PRE CALC: %dx%d Color %d, val %d, n %d\n", + vdeb->sd.name, lin, col, i, rgb[i], n_rgb[i]); + + if (n_rgb[i]) + rgb[i] = rgb[i] / n_rgb[i]; + + dev_dbg(vdeb->dev, + "deb: %s: FINAL CALC: %dx%d Color %d, val %d\n", + vdeb->sd.name, lin, col, i, rgb[i]); + } +} + +static void vimc_deb_process_frame(struct vimc_ent_device *ved, + struct media_pad *sink, + const void *sink_frame) +{ + struct vimc_deb_device *vdeb = container_of(ved, struct vimc_deb_device, + ved); + unsigned int rgb[3]; + unsigned int i, j; + + /* If the stream in this node is not active, just return */ + if (!vdeb->src_frame) + return; + + for (i = 0; i < vdeb->sink_fmt.height; i++) + for (j = 0; j < vdeb->sink_fmt.width; j++) { + vimc_deb_calc_rgb_sink(vdeb, sink_frame, i, j, rgb); + vdeb->set_rgb_src(vdeb, i, j, rgb); + } + + /* Propagate the frame through all source pads */ + for (i = 1; i < vdeb->sd.entity.num_pads; i++) { + struct media_pad *pad = &vdeb->sd.entity.pads[i]; + + vimc_propagate_frame(pad, vdeb->src_frame); + } +} + +static void vimc_deb_comp_unbind(struct device *comp, struct device *master, + void *master_data) +{ + struct vimc_ent_device *ved = dev_get_drvdata(comp); + struct vimc_deb_device *vdeb = container_of(ved, struct vimc_deb_device, + ved); + + vimc_ent_sd_unregister(ved, &vdeb->sd); + kfree(vdeb); +} + +static int vimc_deb_comp_bind(struct device *comp, struct device *master, + void *master_data) +{ + struct v4l2_device *v4l2_dev = master_data; + struct vimc_platform_data *pdata = comp->platform_data; + struct vimc_deb_device *vdeb; + int ret; + + /* Allocate the vdeb struct */ + vdeb = kzalloc(sizeof(*vdeb), GFP_KERNEL); + if (!vdeb) + return -ENOMEM; + + /* Initialize ved and sd */ + ret = vimc_ent_sd_register(&vdeb->ved, &vdeb->sd, v4l2_dev, + pdata->entity_name, + MEDIA_ENT_F_ATV_DECODER, 2, + (const unsigned long[2]) {MEDIA_PAD_FL_SINK, + MEDIA_PAD_FL_SOURCE}, + &vimc_deb_ops); + if (ret) { + kfree(vdeb); + return ret; + } + + vdeb->ved.process_frame = vimc_deb_process_frame; + dev_set_drvdata(comp, &vdeb->ved); + vdeb->dev = comp; + + /* Initialize the frame format */ + vdeb->sink_fmt = sink_fmt_default; + /* + * TODO: Add support for more output formats, we only support + * RGB888 for now + * NOTE: the src format is always the same as the sink, except + * for the code + */ + vdeb->src_code = MEDIA_BUS_FMT_RGB888_1X24; + vdeb->set_rgb_src = vimc_deb_set_rgb_mbus_fmt_rgb888_1x24; + + return 0; +} + +static const struct component_ops vimc_deb_comp_ops = { + .bind = vimc_deb_comp_bind, + .unbind = vimc_deb_comp_unbind, +}; + +static int vimc_deb_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &vimc_deb_comp_ops); +} + +static int vimc_deb_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vimc_deb_comp_ops); + + return 0; +} + +static struct platform_driver vimc_deb_pdrv = { + .probe = vimc_deb_probe, + .remove = vimc_deb_remove, + .driver = { + .name = VIMC_DEB_DRV_NAME, + }, +}; + +static const struct platform_device_id vimc_deb_driver_ids[] = { + { + .name = VIMC_DEB_DRV_NAME, + }, + { } +}; + +module_platform_driver(vimc_deb_pdrv); + +MODULE_DEVICE_TABLE(platform, vimc_deb_driver_ids); + +MODULE_DESCRIPTION("Virtual Media Controller Driver (VIMC) Debayer"); +MODULE_AUTHOR("Helen Mae Koike Fornazier <helen.fornazier@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vimc/vimc-scaler.c b/drivers/media/platform/vimc/vimc-scaler.c new file mode 100644 index 000000000000..fe77505d2679 --- /dev/null +++ b/drivers/media/platform/vimc/vimc-scaler.c @@ -0,0 +1,455 @@ +/* + * vimc-scaler.c Virtual Media Controller Driver + * + * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/component.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/vmalloc.h> +#include <linux/v4l2-mediabus.h> +#include <media/v4l2-subdev.h> + +#include "vimc-common.h" + +#define VIMC_SCA_DRV_NAME "vimc-scaler" + +static unsigned int sca_mult = 3; +module_param(sca_mult, uint, 0000); +MODULE_PARM_DESC(sca_mult, " the image size multiplier"); + +#define IS_SINK(pad) (!pad) +#define IS_SRC(pad) (pad) +#define MAX_ZOOM 8 + +struct vimc_sca_device { + struct vimc_ent_device ved; + struct v4l2_subdev sd; + struct device *dev; + /* NOTE: the source fmt is the same as the sink + * with the width and hight multiplied by mult + */ + struct v4l2_mbus_framefmt sink_fmt; + /* Values calculated when the stream starts */ + u8 *src_frame; + unsigned int src_line_size; + unsigned int bpp; +}; + +static const struct v4l2_mbus_framefmt sink_fmt_default = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, +}; + +static int vimc_sca_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ + struct v4l2_mbus_framefmt *mf; + unsigned int i; + + mf = v4l2_subdev_get_try_format(sd, cfg, 0); + *mf = sink_fmt_default; + + for (i = 1; i < sd->entity.num_pads; i++) { + mf = v4l2_subdev_get_try_format(sd, cfg, i); + *mf = sink_fmt_default; + mf->width = mf->width * sca_mult; + mf->height = mf->height * sca_mult; + } + + return 0; +} + +static int vimc_sca_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + const struct vimc_pix_map *vpix = vimc_pix_map_by_index(code->index); + + /* We don't support bayer format */ + if (!vpix || vpix->bayer) + return -EINVAL; + + code->code = vpix->code; + + return 0; +} + +static int vimc_sca_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + const struct vimc_pix_map *vpix; + + if (fse->index) + return -EINVAL; + + /* Only accept code in the pix map table in non bayer format */ + vpix = vimc_pix_map_by_code(fse->code); + if (!vpix || vpix->bayer) + return -EINVAL; + + fse->min_width = VIMC_FRAME_MIN_WIDTH; + fse->min_height = VIMC_FRAME_MIN_HEIGHT; + + if (IS_SINK(fse->pad)) { + fse->max_width = VIMC_FRAME_MAX_WIDTH; + fse->max_height = VIMC_FRAME_MAX_HEIGHT; + } else { + fse->max_width = VIMC_FRAME_MAX_WIDTH * MAX_ZOOM; + fse->max_height = VIMC_FRAME_MAX_HEIGHT * MAX_ZOOM; + } + + return 0; +} + +static int vimc_sca_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *format) +{ + struct vimc_sca_device *vsca = v4l2_get_subdevdata(sd); + + /* Get the current sink format */ + format->format = (format->which == V4L2_SUBDEV_FORMAT_TRY) ? + *v4l2_subdev_get_try_format(sd, cfg, 0) : + vsca->sink_fmt; + + /* Scale the frame size for the source pad */ + if (IS_SRC(format->pad)) { + format->format.width = vsca->sink_fmt.width * sca_mult; + format->format.height = vsca->sink_fmt.height * sca_mult; + } + + return 0; +} + +static void vimc_sca_adjust_sink_fmt(struct v4l2_mbus_framefmt *fmt) +{ + const struct vimc_pix_map *vpix; + + /* Only accept code in the pix map table in non bayer format */ + vpix = vimc_pix_map_by_code(fmt->code); + if (!vpix || vpix->bayer) + fmt->code = sink_fmt_default.code; + + fmt->width = clamp_t(u32, fmt->width, VIMC_FRAME_MIN_WIDTH, + VIMC_FRAME_MAX_WIDTH) & ~1; + fmt->height = clamp_t(u32, fmt->height, VIMC_FRAME_MIN_HEIGHT, + VIMC_FRAME_MAX_HEIGHT) & ~1; + + if (fmt->field == V4L2_FIELD_ANY) + fmt->field = sink_fmt_default.field; + + vimc_colorimetry_clamp(fmt); +} + +static int vimc_sca_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct vimc_sca_device *vsca = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *sink_fmt; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + /* Do not change the format while stream is on */ + if (vsca->src_frame) + return -EBUSY; + + sink_fmt = &vsca->sink_fmt; + } else { + sink_fmt = v4l2_subdev_get_try_format(sd, cfg, 0); + } + + /* + * Do not change the format of the source pad, + * it is propagated from the sink + */ + if (IS_SRC(fmt->pad)) { + fmt->format = *sink_fmt; + fmt->format.width = sink_fmt->width * sca_mult; + fmt->format.height = sink_fmt->height * sca_mult; + } else { + /* Set the new format in the sink pad */ + vimc_sca_adjust_sink_fmt(&fmt->format); + + dev_dbg(vsca->dev, "%s: sink format update: " + "old:%dx%d (0x%x, %d, %d, %d, %d) " + "new:%dx%d (0x%x, %d, %d, %d, %d)\n", vsca->sd.name, + /* old */ + sink_fmt->width, sink_fmt->height, sink_fmt->code, + sink_fmt->colorspace, sink_fmt->quantization, + sink_fmt->xfer_func, sink_fmt->ycbcr_enc, + /* new */ + fmt->format.width, fmt->format.height, fmt->format.code, + fmt->format.colorspace, fmt->format.quantization, + fmt->format.xfer_func, fmt->format.ycbcr_enc); + + *sink_fmt = fmt->format; + } + + return 0; +} + +static const struct v4l2_subdev_pad_ops vimc_sca_pad_ops = { + .init_cfg = vimc_sca_init_cfg, + .enum_mbus_code = vimc_sca_enum_mbus_code, + .enum_frame_size = vimc_sca_enum_frame_size, + .get_fmt = vimc_sca_get_fmt, + .set_fmt = vimc_sca_set_fmt, +}; + +static int vimc_sca_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct vimc_sca_device *vsca = v4l2_get_subdevdata(sd); + int ret; + + if (enable) { + const struct vimc_pix_map *vpix; + unsigned int frame_size; + + if (vsca->src_frame) + return 0; + + /* Save the bytes per pixel of the sink */ + vpix = vimc_pix_map_by_code(vsca->sink_fmt.code); + vsca->bpp = vpix->bpp; + + /* Calculate the width in bytes of the src frame */ + vsca->src_line_size = vsca->sink_fmt.width * + sca_mult * vsca->bpp; + + /* Calculate the frame size of the source pad */ + frame_size = vsca->src_line_size * vsca->sink_fmt.height * + sca_mult; + + /* Allocate the frame buffer. Use vmalloc to be able to + * allocate a large amount of memory + */ + vsca->src_frame = vmalloc(frame_size); + if (!vsca->src_frame) + return -ENOMEM; + + /* Turn the stream on in the subdevices directly connected */ + ret = vimc_pipeline_s_stream(&vsca->sd.entity, 1); + if (ret) { + vfree(vsca->src_frame); + vsca->src_frame = NULL; + return ret; + } + } else { + if (!vsca->src_frame) + return 0; + + /* Disable streaming from the pipe */ + ret = vimc_pipeline_s_stream(&vsca->sd.entity, 0); + if (ret) + return ret; + + vfree(vsca->src_frame); + vsca->src_frame = NULL; + } + + return 0; +} + +static struct v4l2_subdev_video_ops vimc_sca_video_ops = { + .s_stream = vimc_sca_s_stream, +}; + +static const struct v4l2_subdev_ops vimc_sca_ops = { + .pad = &vimc_sca_pad_ops, + .video = &vimc_sca_video_ops, +}; + +static void vimc_sca_fill_pix(u8 *const ptr, + const u8 *const pixel, + const unsigned int bpp) +{ + unsigned int i; + + /* copy the pixel to the pointer */ + for (i = 0; i < bpp; i++) + ptr[i] = pixel[i]; +} + +static void vimc_sca_scale_pix(const struct vimc_sca_device *const vsca, + const unsigned int lin, const unsigned int col, + const u8 *const sink_frame) +{ + unsigned int i, j, index; + const u8 *pixel; + + /* Point to the pixel value in position (lin, col) in the sink frame */ + index = VIMC_FRAME_INDEX(lin, col, + vsca->sink_fmt.width, + vsca->bpp); + pixel = &sink_frame[index]; + + dev_dbg(vsca->dev, + "sca: %s: --- scale_pix sink pos %dx%d, index %d ---\n", + vsca->sd.name, lin, col, index); + + /* point to the place we are going to put the first pixel + * in the scaled src frame + */ + index = VIMC_FRAME_INDEX(lin * sca_mult, col * sca_mult, + vsca->sink_fmt.width * sca_mult, vsca->bpp); + + dev_dbg(vsca->dev, "sca: %s: scale_pix src pos %dx%d, index %d\n", + vsca->sd.name, lin * sca_mult, col * sca_mult, index); + + /* Repeat this pixel mult times */ + for (i = 0; i < sca_mult; i++) { + /* Iterate through each beginning of a + * pixel repetition in a line + */ + for (j = 0; j < sca_mult * vsca->bpp; j += vsca->bpp) { + dev_dbg(vsca->dev, + "sca: %s: sca: scale_pix src pos %d\n", + vsca->sd.name, index + j); + + /* copy the pixel to the position index + j */ + vimc_sca_fill_pix(&vsca->src_frame[index + j], + pixel, vsca->bpp); + } + + /* move the index to the next line */ + index += vsca->src_line_size; + } +} + +static void vimc_sca_fill_src_frame(const struct vimc_sca_device *const vsca, + const u8 *const sink_frame) +{ + unsigned int i, j; + + /* Scale each pixel from the original sink frame */ + /* TODO: implement scale down, only scale up is supported for now */ + for (i = 0; i < vsca->sink_fmt.height; i++) + for (j = 0; j < vsca->sink_fmt.width; j++) + vimc_sca_scale_pix(vsca, i, j, sink_frame); +} + +static void vimc_sca_process_frame(struct vimc_ent_device *ved, + struct media_pad *sink, + const void *sink_frame) +{ + struct vimc_sca_device *vsca = container_of(ved, struct vimc_sca_device, + ved); + unsigned int i; + + /* If the stream in this node is not active, just return */ + if (!vsca->src_frame) + return; + + vimc_sca_fill_src_frame(vsca, sink_frame); + + /* Propagate the frame through all source pads */ + for (i = 1; i < vsca->sd.entity.num_pads; i++) { + struct media_pad *pad = &vsca->sd.entity.pads[i]; + + vimc_propagate_frame(pad, vsca->src_frame); + } +}; + +static void vimc_sca_comp_unbind(struct device *comp, struct device *master, + void *master_data) +{ + struct vimc_ent_device *ved = dev_get_drvdata(comp); + struct vimc_sca_device *vsca = container_of(ved, struct vimc_sca_device, + ved); + + vimc_ent_sd_unregister(ved, &vsca->sd); + kfree(vsca); +} + + +static int vimc_sca_comp_bind(struct device *comp, struct device *master, + void *master_data) +{ + struct v4l2_device *v4l2_dev = master_data; + struct vimc_platform_data *pdata = comp->platform_data; + struct vimc_sca_device *vsca; + int ret; + + /* Allocate the vsca struct */ + vsca = kzalloc(sizeof(*vsca), GFP_KERNEL); + if (!vsca) + return -ENOMEM; + + /* Initialize ved and sd */ + ret = vimc_ent_sd_register(&vsca->ved, &vsca->sd, v4l2_dev, + pdata->entity_name, + MEDIA_ENT_F_ATV_DECODER, 2, + (const unsigned long[2]) {MEDIA_PAD_FL_SINK, + MEDIA_PAD_FL_SOURCE}, + &vimc_sca_ops); + if (ret) { + kfree(vsca); + return ret; + } + + vsca->ved.process_frame = vimc_sca_process_frame; + dev_set_drvdata(comp, &vsca->ved); + vsca->dev = comp; + + /* Initialize the frame format */ + vsca->sink_fmt = sink_fmt_default; + + return 0; +} + +static const struct component_ops vimc_sca_comp_ops = { + .bind = vimc_sca_comp_bind, + .unbind = vimc_sca_comp_unbind, +}; + +static int vimc_sca_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &vimc_sca_comp_ops); +} + +static int vimc_sca_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vimc_sca_comp_ops); + + return 0; +} + +static struct platform_driver vimc_sca_pdrv = { + .probe = vimc_sca_probe, + .remove = vimc_sca_remove, + .driver = { + .name = VIMC_SCA_DRV_NAME, + }, +}; + +static const struct platform_device_id vimc_sca_driver_ids[] = { + { + .name = VIMC_SCA_DRV_NAME, + }, + { } +}; + +module_platform_driver(vimc_sca_pdrv); + +MODULE_DEVICE_TABLE(platform, vimc_sca_driver_ids); + +MODULE_DESCRIPTION("Virtual Media Controller Driver (VIMC) Scaler"); +MODULE_AUTHOR("Helen Mae Koike Fornazier <helen.fornazier@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vimc/vimc-sensor.c b/drivers/media/platform/vimc/vimc-sensor.c index 591f6a4f8bd3..ebdbbe8c05ed 100644 --- a/drivers/media/platform/vimc/vimc-sensor.c +++ b/drivers/media/platform/vimc/vimc-sensor.c @@ -15,36 +15,64 @@ * */ +#include <linux/component.h> #include <linux/freezer.h> #include <linux/kthread.h> +#include <linux/module.h> +#include <linux/platform_device.h> #include <linux/v4l2-mediabus.h> #include <linux/vmalloc.h> #include <media/v4l2-subdev.h> +#include <media/v4l2-tpg.h> -#include "vimc-sensor.h" +#include "vimc-common.h" + +#define VIMC_SEN_DRV_NAME "vimc-sensor" struct vimc_sen_device { struct vimc_ent_device ved; struct v4l2_subdev sd; + struct device *dev; + struct tpg_data tpg; struct task_struct *kthread_sen; u8 *frame; /* The active format */ struct v4l2_mbus_framefmt mbus_format; - int frame_size; }; +static const struct v4l2_mbus_framefmt fmt_default = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, +}; + +static int vimc_sen_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ + unsigned int i; + + for (i = 0; i < sd->entity.num_pads; i++) { + struct v4l2_mbus_framefmt *mf; + + mf = v4l2_subdev_get_try_format(sd, cfg, i); + *mf = fmt_default; + } + + return 0; +} + static int vimc_sen_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_mbus_code_enum *code) { - struct vimc_sen_device *vsen = - container_of(sd, struct vimc_sen_device, sd); + const struct vimc_pix_map *vpix = vimc_pix_map_by_index(code->index); - /* TODO: Add support for other codes */ - if (code->index) + if (!vpix) return -EINVAL; - code->code = vsen->mbus_format.code; + code->code = vpix->code; return 0; } @@ -53,51 +81,123 @@ static int vimc_sen_enum_frame_size(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_frame_size_enum *fse) { - struct vimc_sen_device *vsen = - container_of(sd, struct vimc_sen_device, sd); + const struct vimc_pix_map *vpix; - /* TODO: Add support to other formats */ if (fse->index) return -EINVAL; - /* TODO: Add support for other codes */ - if (fse->code != vsen->mbus_format.code) + /* Only accept code in the pix map table */ + vpix = vimc_pix_map_by_code(fse->code); + if (!vpix) return -EINVAL; - fse->min_width = vsen->mbus_format.width; - fse->max_width = vsen->mbus_format.width; - fse->min_height = vsen->mbus_format.height; - fse->max_height = vsen->mbus_format.height; + fse->min_width = VIMC_FRAME_MIN_WIDTH; + fse->max_width = VIMC_FRAME_MAX_WIDTH; + fse->min_height = VIMC_FRAME_MIN_HEIGHT; + fse->max_height = VIMC_FRAME_MAX_HEIGHT; return 0; } static int vimc_sen_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_pad_config *cfg, - struct v4l2_subdev_format *format) + struct v4l2_subdev_format *fmt) { struct vimc_sen_device *vsen = container_of(sd, struct vimc_sen_device, sd); - format->format = vsen->mbus_format; + fmt->format = fmt->which == V4L2_SUBDEV_FORMAT_TRY ? + *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) : + vsen->mbus_format; + + return 0; +} + +static void vimc_sen_tpg_s_format(struct vimc_sen_device *vsen) +{ + const struct vimc_pix_map *vpix = + vimc_pix_map_by_code(vsen->mbus_format.code); + + tpg_reset_source(&vsen->tpg, vsen->mbus_format.width, + vsen->mbus_format.height, vsen->mbus_format.field); + tpg_s_bytesperline(&vsen->tpg, 0, vsen->mbus_format.width * vpix->bpp); + tpg_s_buf_height(&vsen->tpg, vsen->mbus_format.height); + tpg_s_fourcc(&vsen->tpg, vpix->pixelformat); + /* TODO: add support for V4L2_FIELD_ALTERNATE */ + tpg_s_field(&vsen->tpg, vsen->mbus_format.field, false); + tpg_s_colorspace(&vsen->tpg, vsen->mbus_format.colorspace); + tpg_s_ycbcr_enc(&vsen->tpg, vsen->mbus_format.ycbcr_enc); + tpg_s_quantization(&vsen->tpg, vsen->mbus_format.quantization); + tpg_s_xfer_func(&vsen->tpg, vsen->mbus_format.xfer_func); +} + +static void vimc_sen_adjust_fmt(struct v4l2_mbus_framefmt *fmt) +{ + const struct vimc_pix_map *vpix; + + /* Only accept code in the pix map table */ + vpix = vimc_pix_map_by_code(fmt->code); + if (!vpix) + fmt->code = fmt_default.code; + + fmt->width = clamp_t(u32, fmt->width, VIMC_FRAME_MIN_WIDTH, + VIMC_FRAME_MAX_WIDTH) & ~1; + fmt->height = clamp_t(u32, fmt->height, VIMC_FRAME_MIN_HEIGHT, + VIMC_FRAME_MAX_HEIGHT) & ~1; + + /* TODO: add support for V4L2_FIELD_ALTERNATE */ + if (fmt->field == V4L2_FIELD_ANY || fmt->field == V4L2_FIELD_ALTERNATE) + fmt->field = fmt_default.field; + + vimc_colorimetry_clamp(fmt); +} + +static int vimc_sen_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct vimc_sen_device *vsen = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *mf; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + /* Do not change the format while stream is on */ + if (vsen->frame) + return -EBUSY; + + mf = &vsen->mbus_format; + } else { + mf = v4l2_subdev_get_try_format(sd, cfg, fmt->pad); + } + + /* Set the new format */ + vimc_sen_adjust_fmt(&fmt->format); + + dev_dbg(vsen->dev, "%s: format update: " + "old:%dx%d (0x%x, %d, %d, %d, %d) " + "new:%dx%d (0x%x, %d, %d, %d, %d)\n", vsen->sd.name, + /* old */ + mf->width, mf->height, mf->code, + mf->colorspace, mf->quantization, + mf->xfer_func, mf->ycbcr_enc, + /* new */ + fmt->format.width, fmt->format.height, fmt->format.code, + fmt->format.colorspace, fmt->format.quantization, + fmt->format.xfer_func, fmt->format.ycbcr_enc); + + *mf = fmt->format; return 0; } static const struct v4l2_subdev_pad_ops vimc_sen_pad_ops = { + .init_cfg = vimc_sen_init_cfg, .enum_mbus_code = vimc_sen_enum_mbus_code, .enum_frame_size = vimc_sen_enum_frame_size, .get_fmt = vimc_sen_get_fmt, - /* TODO: Add support to other formats */ - .set_fmt = vimc_sen_get_fmt, + .set_fmt = vimc_sen_set_fmt, }; -/* media operations */ -static const struct media_entity_operations vimc_sen_mops = { - .link_validate = v4l2_subdev_link_validate, -}; - -static int vimc_thread_sen(void *data) +static int vimc_sen_tpg_thread(void *data) { struct vimc_sen_device *vsen = data; unsigned int i; @@ -110,7 +210,7 @@ static int vimc_thread_sen(void *data) if (kthread_should_stop()) break; - memset(vsen->frame, 100, vsen->frame_size); + tpg_fill_plane_buffer(&vsen->tpg, 0, 0, vsen->frame); /* Send the frame to all source pads */ for (i = 0; i < vsen->sd.entity.num_pads; i++) @@ -132,50 +232,57 @@ static int vimc_sen_s_stream(struct v4l2_subdev *sd, int enable) if (enable) { const struct vimc_pix_map *vpix; + unsigned int frame_size; if (vsen->kthread_sen) - return -EINVAL; + /* tpg is already executing */ + return 0; /* Calculate the frame size */ vpix = vimc_pix_map_by_code(vsen->mbus_format.code); - vsen->frame_size = vsen->mbus_format.width * vpix->bpp * - vsen->mbus_format.height; + frame_size = vsen->mbus_format.width * vpix->bpp * + vsen->mbus_format.height; /* * Allocate the frame buffer. Use vmalloc to be able to * allocate a large amount of memory */ - vsen->frame = vmalloc(vsen->frame_size); + vsen->frame = vmalloc(frame_size); if (!vsen->frame) return -ENOMEM; + /* configure the test pattern generator */ + vimc_sen_tpg_s_format(vsen); + /* Initialize the image generator thread */ - vsen->kthread_sen = kthread_run(vimc_thread_sen, vsen, "%s-sen", - vsen->sd.v4l2_dev->name); + vsen->kthread_sen = kthread_run(vimc_sen_tpg_thread, vsen, + "%s-sen", vsen->sd.v4l2_dev->name); if (IS_ERR(vsen->kthread_sen)) { - dev_err(vsen->sd.v4l2_dev->dev, - "%s: kernel_thread() failed\n", vsen->sd.name); + dev_err(vsen->dev, "%s: kernel_thread() failed\n", + vsen->sd.name); vfree(vsen->frame); vsen->frame = NULL; return PTR_ERR(vsen->kthread_sen); } } else { if (!vsen->kthread_sen) - return -EINVAL; + return 0; /* Stop image generator */ ret = kthread_stop(vsen->kthread_sen); - vsen->kthread_sen = NULL; + if (ret) + return ret; + vsen->kthread_sen = NULL; vfree(vsen->frame); vsen->frame = NULL; - return ret; + return 0; } return 0; } -struct v4l2_subdev_video_ops vimc_sen_video_ops = { +static struct v4l2_subdev_video_ops vimc_sen_video_ops = { .s_stream = vimc_sen_s_stream, }; @@ -184,93 +291,99 @@ static const struct v4l2_subdev_ops vimc_sen_ops = { .video = &vimc_sen_video_ops, }; -static void vimc_sen_destroy(struct vimc_ent_device *ved) +static void vimc_sen_comp_unbind(struct device *comp, struct device *master, + void *master_data) { + struct vimc_ent_device *ved = dev_get_drvdata(comp); struct vimc_sen_device *vsen = container_of(ved, struct vimc_sen_device, ved); - v4l2_device_unregister_subdev(&vsen->sd); - media_entity_cleanup(ved->ent); + vimc_ent_sd_unregister(ved, &vsen->sd); + tpg_free(&vsen->tpg); kfree(vsen); } -struct vimc_ent_device *vimc_sen_create(struct v4l2_device *v4l2_dev, - const char *const name, - u16 num_pads, - const unsigned long *pads_flag) +static int vimc_sen_comp_bind(struct device *comp, struct device *master, + void *master_data) { + struct v4l2_device *v4l2_dev = master_data; + struct vimc_platform_data *pdata = comp->platform_data; struct vimc_sen_device *vsen; - unsigned int i; int ret; - /* NOTE: a sensor node may be created with more then one pad */ - if (!name || !num_pads || !pads_flag) - return ERR_PTR(-EINVAL); - - /* check if all pads are sources */ - for (i = 0; i < num_pads; i++) - if (!(pads_flag[i] & MEDIA_PAD_FL_SOURCE)) - return ERR_PTR(-EINVAL); - /* Allocate the vsen struct */ vsen = kzalloc(sizeof(*vsen), GFP_KERNEL); if (!vsen) - return ERR_PTR(-ENOMEM); - - /* Allocate the pads */ - vsen->ved.pads = vimc_pads_init(num_pads, pads_flag); - if (IS_ERR(vsen->ved.pads)) { - ret = PTR_ERR(vsen->ved.pads); + return -ENOMEM; + + /* Initialize ved and sd */ + ret = vimc_ent_sd_register(&vsen->ved, &vsen->sd, v4l2_dev, + pdata->entity_name, + MEDIA_ENT_F_ATV_DECODER, 1, + (const unsigned long[1]) {MEDIA_PAD_FL_SOURCE}, + &vimc_sen_ops); + if (ret) goto err_free_vsen; - } - - /* Fill the vimc_ent_device struct */ - vsen->ved.destroy = vimc_sen_destroy; - vsen->ved.ent = &vsen->sd.entity; - /* Initialize the subdev */ - v4l2_subdev_init(&vsen->sd, &vimc_sen_ops); - vsen->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; - vsen->sd.entity.ops = &vimc_sen_mops; - vsen->sd.owner = THIS_MODULE; - strlcpy(vsen->sd.name, name, sizeof(vsen->sd.name)); - v4l2_set_subdevdata(&vsen->sd, &vsen->ved); + dev_set_drvdata(comp, &vsen->ved); + vsen->dev = comp; - /* Expose this subdev to user space */ - vsen->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + /* Initialize the frame format */ + vsen->mbus_format = fmt_default; - /* Initialize the media entity */ - ret = media_entity_pads_init(&vsen->sd.entity, - num_pads, vsen->ved.pads); + /* Initialize the test pattern generator */ + tpg_init(&vsen->tpg, vsen->mbus_format.width, + vsen->mbus_format.height); + ret = tpg_alloc(&vsen->tpg, VIMC_FRAME_MAX_WIDTH); if (ret) - goto err_clean_pads; - - /* Set the active frame format (this is hardcoded for now) */ - vsen->mbus_format.width = 640; - vsen->mbus_format.height = 480; - vsen->mbus_format.code = MEDIA_BUS_FMT_RGB888_1X24; - vsen->mbus_format.field = V4L2_FIELD_NONE; - vsen->mbus_format.colorspace = V4L2_COLORSPACE_SRGB; - vsen->mbus_format.quantization = V4L2_QUANTIZATION_FULL_RANGE; - vsen->mbus_format.xfer_func = V4L2_XFER_FUNC_SRGB; - - /* Register the subdev with the v4l2 and the media framework */ - ret = v4l2_device_register_subdev(v4l2_dev, &vsen->sd); - if (ret) { - dev_err(vsen->sd.v4l2_dev->dev, - "%s: subdev register failed (err=%d)\n", - vsen->sd.name, ret); - goto err_clean_m_ent; - } + goto err_unregister_ent_sd; - return &vsen->ved; + return 0; -err_clean_m_ent: - media_entity_cleanup(&vsen->sd.entity); -err_clean_pads: - vimc_pads_cleanup(vsen->ved.pads); +err_unregister_ent_sd: + vimc_ent_sd_unregister(&vsen->ved, &vsen->sd); err_free_vsen: kfree(vsen); - return ERR_PTR(ret); + return ret; } + +static const struct component_ops vimc_sen_comp_ops = { + .bind = vimc_sen_comp_bind, + .unbind = vimc_sen_comp_unbind, +}; + +static int vimc_sen_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &vimc_sen_comp_ops); +} + +static int vimc_sen_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &vimc_sen_comp_ops); + + return 0; +} + +static struct platform_driver vimc_sen_pdrv = { + .probe = vimc_sen_probe, + .remove = vimc_sen_remove, + .driver = { + .name = VIMC_SEN_DRV_NAME, + }, +}; + +static const struct platform_device_id vimc_sen_driver_ids[] = { + { + .name = VIMC_SEN_DRV_NAME, + }, + { } +}; + +module_platform_driver(vimc_sen_pdrv); + +MODULE_DEVICE_TABLE(platform, vimc_sen_driver_ids); + +MODULE_DESCRIPTION("Virtual Media Controller Driver (VIMC) Sensor"); +MODULE_AUTHOR("Helen Mae Koike Fornazier <helen.fornazier@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/vimc/vimc-sensor.h b/drivers/media/platform/vimc/vimc-sensor.h deleted file mode 100644 index 505310e8aeb7..000000000000 --- a/drivers/media/platform/vimc/vimc-sensor.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * vimc-sensor.h Virtual Media Controller Driver - * - * Copyright (C) 2015-2017 Helen Koike <helen.fornazier@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - */ - -#ifndef _VIMC_SENSOR_H_ -#define _VIMC_SENSOR_H_ - -#include "vimc-core.h" - -struct vimc_ent_device *vimc_sen_create(struct v4l2_device *v4l2_dev, - const char *const name, - u16 num_pads, - const unsigned long *pads_flag); - -#endif |