diff options
Diffstat (limited to 'drivers/media/platform/vsp1')
27 files changed, 1939 insertions, 185 deletions
diff --git a/drivers/media/platform/vsp1/Makefile b/drivers/media/platform/vsp1/Makefile index 1328e1bd2143..a33afc385a48 100644 --- a/drivers/media/platform/vsp1/Makefile +++ b/drivers/media/platform/vsp1/Makefile @@ -3,6 +3,7 @@ vsp1-y += vsp1_dl.o vsp1_drm.o vsp1_video.o vsp1-y += vsp1_rpf.o vsp1_rwpf.o vsp1_wpf.o vsp1-y += vsp1_clu.o vsp1_hsit.o vsp1_lut.o vsp1-y += vsp1_bru.o vsp1_sru.o vsp1_uds.o +vsp1-y += vsp1_hgo.o vsp1_hgt.o vsp1_histo.o vsp1-y += vsp1_lif.o obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1.o diff --git a/drivers/media/platform/vsp1/vsp1.h b/drivers/media/platform/vsp1/vsp1.h index b23fa879a9aa..85387a64179a 100644 --- a/drivers/media/platform/vsp1/vsp1.h +++ b/drivers/media/platform/vsp1/vsp1.h @@ -32,6 +32,8 @@ struct vsp1_entity; struct vsp1_platform_data; struct vsp1_bru; struct vsp1_clu; +struct vsp1_hgo; +struct vsp1_hgt; struct vsp1_hsit; struct vsp1_lif; struct vsp1_lut; @@ -50,6 +52,8 @@ struct vsp1_uds; #define VSP1_HAS_CLU (1 << 4) #define VSP1_HAS_WPF_VFLIP (1 << 5) #define VSP1_HAS_WPF_HFLIP (1 << 6) +#define VSP1_HAS_HGO (1 << 7) +#define VSP1_HAS_HGT (1 << 8) struct vsp1_device_info { u32 version; @@ -73,6 +77,8 @@ struct vsp1_device { struct vsp1_bru *bru; struct vsp1_clu *clu; + struct vsp1_hgo *hgo; + struct vsp1_hgt *hgt; struct vsp1_hsit *hsi; struct vsp1_hsit *hst; struct vsp1_lif *lif; diff --git a/drivers/media/platform/vsp1/vsp1_bru.c b/drivers/media/platform/vsp1/vsp1_bru.c index ee8355c28f94..85362c5ef57a 100644 --- a/drivers/media/platform/vsp1/vsp1_bru.c +++ b/drivers/media/platform/vsp1/vsp1_bru.c @@ -251,7 +251,8 @@ static int bru_set_selection(struct v4l2_subdev *subdev, sel->r.left = clamp_t(unsigned int, sel->r.left, 0, format->width - 1); sel->r.top = clamp_t(unsigned int, sel->r.top, 0, format->height - 1); - /* Scaling isn't supported, the compose rectangle size must be identical + /* + * Scaling isn't supported, the compose rectangle size must be identical * to the sink format size. */ format = vsp1_entity_get_pad_format(&bru->entity, config, sel->pad); @@ -300,13 +301,15 @@ static void bru_configure(struct vsp1_entity *entity, format = vsp1_entity_get_pad_format(&bru->entity, bru->entity.config, bru->entity.source_pad); - /* The hardware is extremely flexible but we have no userspace API to + /* + * The hardware is extremely flexible but we have no userspace API to * expose all the parameters, nor is it clear whether we would have use * cases for all the supported modes. Let's just harcode the parameters * to sane default values for now. */ - /* Disable dithering and enable color data normalization unless the + /* + * Disable dithering and enable color data normalization unless the * format at the pipeline output is premultiplied. */ flags = pipe->output ? pipe->output->format.flags : 0; @@ -314,7 +317,8 @@ static void bru_configure(struct vsp1_entity *entity, flags & V4L2_PIX_FMT_FLAG_PREMUL_ALPHA ? 0 : VI6_BRU_INCTRL_NRM); - /* Set the background position to cover the whole output image and + /* + * Set the background position to cover the whole output image and * configure its color. */ vsp1_bru_write(bru, dl, VI6_BRU_VIRRPF_SIZE, @@ -325,7 +329,8 @@ static void bru_configure(struct vsp1_entity *entity, vsp1_bru_write(bru, dl, VI6_BRU_VIRRPF_COL, bru->bgcolor | (0xff << VI6_BRU_VIRRPF_COL_A_SHIFT)); - /* Route BRU input 1 as SRC input to the ROP unit and configure the ROP + /* + * Route BRU input 1 as SRC input to the ROP unit and configure the ROP * unit with a NOP operation to make BRU input 1 available as the * Blend/ROP unit B SRC input. */ @@ -337,7 +342,8 @@ static void bru_configure(struct vsp1_entity *entity, bool premultiplied = false; u32 ctrl = 0; - /* Configure all Blend/ROP units corresponding to an enabled BRU + /* + * Configure all Blend/ROP units corresponding to an enabled BRU * input for alpha blending. Blend/ROP units corresponding to * disabled BRU inputs are used in ROP NOP mode to ignore the * SRC input. @@ -352,13 +358,15 @@ static void bru_configure(struct vsp1_entity *entity, | VI6_BRU_CTRL_AROP(VI6_ROP_NOP); } - /* Select the virtual RPF as the Blend/ROP unit A DST input to + /* + * Select the virtual RPF as the Blend/ROP unit A DST input to * serve as a background color. */ if (i == 0) ctrl |= VI6_BRU_CTRL_DSTSEL_VRPF; - /* Route BRU inputs 0 to 3 as SRC inputs to Blend/ROP units A to + /* + * Route BRU inputs 0 to 3 as SRC inputs to Blend/ROP units A to * D in that order. The Blend/ROP unit B SRC is hardwired to the * ROP unit output, the corresponding register bits must be set * to 0. @@ -368,7 +376,8 @@ static void bru_configure(struct vsp1_entity *entity, vsp1_bru_write(bru, dl, VI6_BRU_CTRL(i), ctrl); - /* Harcode the blending formula to + /* + * Harcode the blending formula to * * DSTc = DSTc * (1 - SRCa) + SRCc * SRCa * DSTa = DSTa * (1 - SRCa) + SRCa diff --git a/drivers/media/platform/vsp1/vsp1_dl.c b/drivers/media/platform/vsp1/vsp1_dl.c index ad545aff4e35..7d8f37772b56 100644 --- a/drivers/media/platform/vsp1/vsp1_dl.c +++ b/drivers/media/platform/vsp1/vsp1_dl.c @@ -240,7 +240,8 @@ static struct vsp1_dl_list *vsp1_dl_list_alloc(struct vsp1_dl_manager *dlm) INIT_LIST_HEAD(&dl->fragments); dl->dlm = dlm; - /* Initialize the display list body and allocate DMA memory for the body + /* + * Initialize the display list body and allocate DMA memory for the body * and the optional header. Both are allocated together to avoid memory * fragmentation, with the header located right after the body in * memory. @@ -511,7 +512,8 @@ void vsp1_dl_list_commit(struct vsp1_dl_list *dl) goto done; } - /* Once the UPD bit has been set the hardware can start processing the + /* + * Once the UPD bit has been set the hardware can start processing the * display list at any time and we can't touch the address and size * registers. In that case mark the update as pending, it will be * queued up to the hardware by the frame end interrupt handler. @@ -523,7 +525,8 @@ void vsp1_dl_list_commit(struct vsp1_dl_list *dl) goto done; } - /* Program the hardware with the display list body address and size. + /* + * Program the hardware with the display list body address and size. * The UPD bit will be cleared by the device when the display list is * processed. */ @@ -547,7 +550,8 @@ void vsp1_dlm_irq_display_start(struct vsp1_dl_manager *dlm) { spin_lock(&dlm->lock); - /* The display start interrupt signals the end of the display list + /* + * The display start interrupt signals the end of the display list * processing by the device. The active display list, if any, won't be * accessed anymore and can be reused. */ @@ -566,14 +570,16 @@ void vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm) __vsp1_dl_list_put(dlm->active); dlm->active = NULL; - /* Header mode is used for mem-to-mem pipelines only. We don't need to + /* + * Header mode is used for mem-to-mem pipelines only. We don't need to * perform any operation as there can't be any new display list queued * in that case. */ if (dlm->mode == VSP1_DL_MODE_HEADER) goto done; - /* The UPD bit set indicates that the commit operation raced with the + /* + * The UPD bit set indicates that the commit operation raced with the * interrupt and occurred after the frame end event and UPD clear but * before interrupt processing. The hardware hasn't taken the update * into account yet, we'll thus skip one frame and retry. @@ -581,7 +587,8 @@ void vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm) if (vsp1_read(vsp1, VI6_DL_BODY_SIZE) & VI6_DL_BODY_SIZE_UPD) goto done; - /* The device starts processing the queued display list right after the + /* + * The device starts processing the queued display list right after the * frame end interrupt. The display list thus becomes active. */ if (dlm->queued) { @@ -589,7 +596,8 @@ void vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm) dlm->queued = NULL; } - /* Now that the UPD bit has been cleared we can queue the next display + /* + * Now that the UPD bit has been cleared we can queue the next display * list to the hardware if one has been prepared. */ if (dlm->pending) { @@ -615,7 +623,8 @@ void vsp1_dlm_setup(struct vsp1_device *vsp1) | VI6_DL_CTRL_DC2 | VI6_DL_CTRL_DC1 | VI6_DL_CTRL_DC0 | VI6_DL_CTRL_DLE; - /* The DRM pipeline operates with display lists in Continuous Frame + /* + * The DRM pipeline operates with display lists in Continuous Frame * Mode, all other pipelines use manual start. */ if (vsp1->drm) diff --git a/drivers/media/platform/vsp1/vsp1_drm.c b/drivers/media/platform/vsp1/vsp1_drm.c index b4c0f10fc3b0..9d235e830f5a 100644 --- a/drivers/media/platform/vsp1/vsp1_drm.c +++ b/drivers/media/platform/vsp1/vsp1_drm.c @@ -78,7 +78,8 @@ int vsp1_du_setup_lif(struct device *dev, const struct vsp1_du_lif_config *cfg) int ret; if (!cfg) { - /* NULL configuration means the CRTC is being disabled, stop + /* + * NULL configuration means the CRTC is being disabled, stop * the pipeline and turn the light off. */ ret = vsp1_pipeline_stop(pipe); @@ -106,7 +107,8 @@ int vsp1_du_setup_lif(struct device *dev, const struct vsp1_du_lif_config *cfg) dev_dbg(vsp1->dev, "%s: configuring LIF with format %ux%u\n", __func__, cfg->width, cfg->height); - /* Configure the format at the BRU sinks and propagate it through the + /* + * Configure the format at the BRU sinks and propagate it through the * pipeline. */ memset(&format, 0, sizeof(format)); @@ -175,7 +177,8 @@ int vsp1_du_setup_lif(struct device *dev, const struct vsp1_du_lif_config *cfg) __func__, format.format.width, format.format.height, format.format.code); - /* Verify that the format at the output of the pipeline matches the + /* + * Verify that the format at the output of the pipeline matches the * requested frame size and media bus code. */ if (format.format.width != cfg->width || @@ -185,7 +188,8 @@ int vsp1_du_setup_lif(struct device *dev, const struct vsp1_du_lif_config *cfg) return -EPIPE; } - /* Mark the pipeline as streaming and enable the VSP1. This will store + /* + * Mark the pipeline as streaming and enable the VSP1. This will store * the pipeline pointer in all entities, which the s_stream handlers * will need. We don't start the entities themselves right at this point * as there's no plane configured yet, so we can't start processing @@ -219,9 +223,6 @@ void vsp1_du_atomic_begin(struct device *dev) struct vsp1_pipeline *pipe = &vsp1->drm->pipe; vsp1->drm->num_inputs = pipe->num_inputs; - - /* Prepare the display list. */ - pipe->dl = vsp1_dl_list_get(pipe->output->dlm); } EXPORT_SYMBOL_GPL(vsp1_du_atomic_begin); @@ -320,7 +321,8 @@ static int vsp1_du_setup_rpf_pipe(struct vsp1_device *vsp1, const struct v4l2_rect *crop; int ret; - /* Configure the format on the RPF sink pad and propagate it up to the + /* + * Configure the format on the RPF sink pad and propagate it up to the * BRU sink pad. */ crop = &vsp1->drm->inputs[rpf->entity.index].crop; @@ -359,7 +361,8 @@ static int vsp1_du_setup_rpf_pipe(struct vsp1_device *vsp1, __func__, sel.r.left, sel.r.top, sel.r.width, sel.r.height, rpf->entity.index); - /* RPF source, hardcode the format to ARGB8888 to turn on format + /* + * RPF source, hardcode the format to ARGB8888 to turn on format * conversion if needed. */ format.pad = RWPF_PAD_SOURCE; @@ -425,10 +428,14 @@ void vsp1_du_atomic_flush(struct device *dev) struct vsp1_pipeline *pipe = &vsp1->drm->pipe; struct vsp1_rwpf *inputs[VSP1_MAX_RPF] = { NULL, }; struct vsp1_entity *entity; + struct vsp1_dl_list *dl; unsigned long flags; unsigned int i; int ret; + /* Prepare the display list. */ + dl = vsp1_dl_list_get(pipe->output->dlm); + /* Count the number of enabled inputs and sort them by Z-order. */ pipe->num_inputs = 0; @@ -483,26 +490,25 @@ void vsp1_du_atomic_flush(struct device *dev) struct vsp1_rwpf *rpf = to_rwpf(&entity->subdev); if (!pipe->inputs[rpf->entity.index]) { - vsp1_dl_list_write(pipe->dl, entity->route->reg, + vsp1_dl_list_write(dl, entity->route->reg, VI6_DPR_NODE_UNUSED); continue; } } - vsp1_entity_route_setup(entity, pipe->dl); + vsp1_entity_route_setup(entity, pipe, dl); if (entity->ops->configure) { - entity->ops->configure(entity, pipe, pipe->dl, + entity->ops->configure(entity, pipe, dl, VSP1_ENTITY_PARAMS_INIT); - entity->ops->configure(entity, pipe, pipe->dl, + entity->ops->configure(entity, pipe, dl, VSP1_ENTITY_PARAMS_RUNTIME); - entity->ops->configure(entity, pipe, pipe->dl, + entity->ops->configure(entity, pipe, dl, VSP1_ENTITY_PARAMS_PARTITION); } } - vsp1_dl_list_commit(pipe->dl); - pipe->dl = NULL; + vsp1_dl_list_commit(dl); /* Start or stop the pipeline if needed. */ if (!vsp1->drm->num_inputs && pipe->num_inputs) { @@ -528,7 +534,8 @@ int vsp1_drm_create_links(struct vsp1_device *vsp1) unsigned int i; int ret; - /* VSPD instances require a BRU to perform composition and a LIF to + /* + * VSPD instances require a BRU to perform composition and a LIF to * output to the DU. */ if (!vsp1->bru || !vsp1->lif) @@ -595,6 +602,7 @@ int vsp1_drm_init(struct vsp1_device *vsp1) pipe->bru = &vsp1->bru->entity; pipe->lif = &vsp1->lif->entity; pipe->output = vsp1->wpf[0]; + pipe->output->pipe = pipe; return 0; } diff --git a/drivers/media/platform/vsp1/vsp1_drm.h b/drivers/media/platform/vsp1/vsp1_drm.h index 9e28ab9254ba..c8d2f88fc483 100644 --- a/drivers/media/platform/vsp1/vsp1_drm.h +++ b/drivers/media/platform/vsp1/vsp1_drm.h @@ -21,7 +21,7 @@ * vsp1_drm - State for the API exposed to the DRM driver * @pipe: the VSP1 pipeline used for display * @num_inputs: number of active pipeline inputs at the beginning of an update - * @planes: source crop rectangle, destination compose rectangle and z-order + * @inputs: source crop rectangle, destination compose rectangle and z-order * position for every input */ struct vsp1_drm { diff --git a/drivers/media/platform/vsp1/vsp1_drv.c b/drivers/media/platform/vsp1/vsp1_drv.c index aa237b48ad55..048446af5ae7 100644 --- a/drivers/media/platform/vsp1/vsp1_drv.c +++ b/drivers/media/platform/vsp1/vsp1_drv.c @@ -30,6 +30,8 @@ #include "vsp1_clu.h" #include "vsp1_dl.h" #include "vsp1_drm.h" +#include "vsp1_hgo.h" +#include "vsp1_hgt.h" #include "vsp1_hsit.h" #include "vsp1_lif.h" #include "vsp1_lut.h" @@ -105,7 +107,9 @@ static int vsp1_create_sink_links(struct vsp1_device *vsp1, if (source->type == sink->type) continue; - if (source->type == VSP1_ENTITY_LIF || + if (source->type == VSP1_ENTITY_HGO || + source->type == VSP1_ENTITY_HGT || + source->type == VSP1_ENTITY_LIF || source->type == VSP1_ENTITY_WPF) continue; @@ -148,6 +152,26 @@ static int vsp1_uapi_create_links(struct vsp1_device *vsp1) return ret; } + if (vsp1->hgo) { + ret = media_create_pad_link(&vsp1->hgo->histo.entity.subdev.entity, + HISTO_PAD_SOURCE, + &vsp1->hgo->histo.video.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) + return ret; + } + + if (vsp1->hgt) { + ret = media_create_pad_link(&vsp1->hgt->histo.entity.subdev.entity, + HISTO_PAD_SOURCE, + &vsp1->hgt->histo.video.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) + return ret; + } + if (vsp1->lif) { ret = media_create_pad_link(&vsp1->wpf[0]->entity.subdev.entity, RWPF_PAD_SOURCE, @@ -170,7 +194,8 @@ static int vsp1_uapi_create_links(struct vsp1_device *vsp1) } for (i = 0; i < vsp1->info->wpf_count; ++i) { - /* Connect the video device to the WPF. All connections are + /* + * Connect the video device to the WPF. All connections are * immutable. */ struct vsp1_rwpf *wpf = vsp1->wpf[i]; @@ -227,7 +252,8 @@ static int vsp1_create_entities(struct vsp1_device *vsp1) media_device_init(mdev); vsp1->media_ops.link_setup = vsp1_entity_link_setup; - /* Don't perform link validation when the userspace API is disabled as + /* + * Don't perform link validation when the userspace API is disabled as * the pipeline is configured internally by the driver in that case, and * its configuration can thus be trusted. */ @@ -279,7 +305,30 @@ static int vsp1_create_entities(struct vsp1_device *vsp1) list_add_tail(&vsp1->hst->entity.list_dev, &vsp1->entities); - /* The LIF is only supported when used in conjunction with the DU, in + if (vsp1->info->features & VSP1_HAS_HGO && vsp1->info->uapi) { + vsp1->hgo = vsp1_hgo_create(vsp1); + if (IS_ERR(vsp1->hgo)) { + ret = PTR_ERR(vsp1->hgo); + goto done; + } + + list_add_tail(&vsp1->hgo->histo.entity.list_dev, + &vsp1->entities); + } + + if (vsp1->info->features & VSP1_HAS_HGT && vsp1->info->uapi) { + vsp1->hgt = vsp1_hgt_create(vsp1); + if (IS_ERR(vsp1->hgt)) { + ret = PTR_ERR(vsp1->hgt); + goto done; + } + + list_add_tail(&vsp1->hgt->histo.entity.list_dev, + &vsp1->entities); + } + + /* + * The LIF is only supported when used in conjunction with the DU, in * which case the userspace API is disabled. If the userspace API is * enabled skip the LIF, even when present. */ @@ -391,7 +440,8 @@ static int vsp1_create_entities(struct vsp1_device *vsp1) if (ret < 0) goto done; - /* Register subdev nodes if the userspace API is enabled or initialize + /* + * Register subdev nodes if the userspace API is enabled or initialize * the DRM pipeline otherwise. */ if (vsp1->info->uapi) { @@ -562,8 +612,9 @@ static const struct vsp1_device_info vsp1_device_infos[] = { .version = VI6_IP_VERSION_MODEL_VSPS_H2, .model = "VSP1-S", .gen = 2, - .features = VSP1_HAS_BRU | VSP1_HAS_CLU | VSP1_HAS_LUT - | VSP1_HAS_SRU | VSP1_HAS_WPF_VFLIP, + .features = VSP1_HAS_BRU | VSP1_HAS_CLU | VSP1_HAS_HGO + | VSP1_HAS_HGT | VSP1_HAS_LUT | VSP1_HAS_SRU + | VSP1_HAS_WPF_VFLIP, .rpf_count = 5, .uds_count = 3, .wpf_count = 4, @@ -583,7 +634,8 @@ static const struct vsp1_device_info vsp1_device_infos[] = { .version = VI6_IP_VERSION_MODEL_VSPD_GEN2, .model = "VSP1-D", .gen = 2, - .features = VSP1_HAS_BRU | VSP1_HAS_LIF | VSP1_HAS_LUT, + .features = VSP1_HAS_BRU | VSP1_HAS_HGO | VSP1_HAS_LIF + | VSP1_HAS_LUT, .rpf_count = 4, .uds_count = 1, .wpf_count = 1, @@ -593,8 +645,9 @@ static const struct vsp1_device_info vsp1_device_infos[] = { .version = VI6_IP_VERSION_MODEL_VSPS_M2, .model = "VSP1-S", .gen = 2, - .features = VSP1_HAS_BRU | VSP1_HAS_CLU | VSP1_HAS_LUT - | VSP1_HAS_SRU | VSP1_HAS_WPF_VFLIP, + .features = VSP1_HAS_BRU | VSP1_HAS_CLU | VSP1_HAS_HGO + | VSP1_HAS_HGT | VSP1_HAS_LUT | VSP1_HAS_SRU + | VSP1_HAS_WPF_VFLIP, .rpf_count = 5, .uds_count = 1, .wpf_count = 4, @@ -626,8 +679,9 @@ static const struct vsp1_device_info vsp1_device_infos[] = { .version = VI6_IP_VERSION_MODEL_VSPI_GEN3, .model = "VSP2-I", .gen = 3, - .features = VSP1_HAS_CLU | VSP1_HAS_LUT | VSP1_HAS_SRU - | VSP1_HAS_WPF_HFLIP | VSP1_HAS_WPF_VFLIP, + .features = VSP1_HAS_CLU | VSP1_HAS_HGO | VSP1_HAS_HGT + | VSP1_HAS_LUT | VSP1_HAS_SRU | VSP1_HAS_WPF_HFLIP + | VSP1_HAS_WPF_VFLIP, .rpf_count = 1, .uds_count = 1, .wpf_count = 1, @@ -645,8 +699,8 @@ static const struct vsp1_device_info vsp1_device_infos[] = { .version = VI6_IP_VERSION_MODEL_VSPBC_GEN3, .model = "VSP2-BC", .gen = 3, - .features = VSP1_HAS_BRU | VSP1_HAS_CLU | VSP1_HAS_LUT - | VSP1_HAS_WPF_VFLIP, + .features = VSP1_HAS_BRU | VSP1_HAS_CLU | VSP1_HAS_HGO + | VSP1_HAS_LUT | VSP1_HAS_WPF_VFLIP, .rpf_count = 5, .wpf_count = 1, .num_bru_inputs = 5, diff --git a/drivers/media/platform/vsp1/vsp1_entity.c b/drivers/media/platform/vsp1/vsp1_entity.c index da673495c222..4bdb3b141611 100644 --- a/drivers/media/platform/vsp1/vsp1_entity.c +++ b/drivers/media/platform/vsp1/vsp1_entity.c @@ -21,6 +21,8 @@ #include "vsp1.h" #include "vsp1_dl.h" #include "vsp1_entity.h" +#include "vsp1_pipe.h" +#include "vsp1_rwpf.h" static inline struct vsp1_entity * media_entity_to_vsp1_entity(struct media_entity *entity) @@ -28,11 +30,42 @@ media_entity_to_vsp1_entity(struct media_entity *entity) return container_of(entity, struct vsp1_entity, subdev.entity); } -void vsp1_entity_route_setup(struct vsp1_entity *source, +void vsp1_entity_route_setup(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, struct vsp1_dl_list *dl) { + struct vsp1_entity *source; struct vsp1_entity *sink; + if (entity->type == VSP1_ENTITY_HGO) { + u32 smppt; + + /* + * The HGO is a special case, its routing is configured on the + * sink pad. + */ + source = media_entity_to_vsp1_entity(entity->sources[0]); + smppt = (pipe->output->entity.index << VI6_DPR_SMPPT_TGW_SHIFT) + | (source->route->output << VI6_DPR_SMPPT_PT_SHIFT); + + vsp1_dl_list_write(dl, VI6_DPR_HGO_SMPPT, smppt); + return; + } else if (entity->type == VSP1_ENTITY_HGT) { + u32 smppt; + + /* + * The HGT is a special case, its routing is configured on the + * sink pad. + */ + source = media_entity_to_vsp1_entity(entity->sources[0]); + smppt = (pipe->output->entity.index << VI6_DPR_SMPPT_TGW_SHIFT) + | (source->route->output << VI6_DPR_SMPPT_PT_SHIFT); + + vsp1_dl_list_write(dl, VI6_DPR_HGT_SMPPT, smppt); + return; + } + + source = entity; if (source->route->reg == 0) return; @@ -199,7 +232,8 @@ int vsp1_subdev_enum_mbus_code(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *config; struct v4l2_mbus_framefmt *format; - /* The entity can't perform format conversion, the sink format + /* + * The entity can't perform format conversion, the sink format * is always identical to the source format. */ if (code->index) @@ -263,7 +297,8 @@ int vsp1_subdev_enum_frame_size(struct v4l2_subdev *subdev, fse->min_height = min_height; fse->max_height = max_height; } else { - /* The size on the source pad are fixed and always identical to + /* + * The size on the source pad are fixed and always identical to * the size on the sink pad. */ fse->min_width = format->width; @@ -281,25 +316,32 @@ done: * Media Operations */ -int vsp1_entity_link_setup(struct media_entity *entity, - const struct media_pad *local, - const struct media_pad *remote, u32 flags) +static int vsp1_entity_link_setup_source(const struct media_pad *source_pad, + const struct media_pad *sink_pad, + u32 flags) { struct vsp1_entity *source; - if (!(local->flags & MEDIA_PAD_FL_SOURCE)) - return 0; - - source = media_entity_to_vsp1_entity(local->entity); + source = media_entity_to_vsp1_entity(source_pad->entity); if (!source->route) return 0; if (flags & MEDIA_LNK_FL_ENABLED) { - if (source->sink) - return -EBUSY; - source->sink = remote->entity; - source->sink_pad = remote->index; + struct vsp1_entity *sink + = media_entity_to_vsp1_entity(sink_pad->entity); + + /* + * Fan-out is limited to one for the normal data path plus + * optional HGO and HGT. We ignore the HGO and HGT here. + */ + if (sink->type != VSP1_ENTITY_HGO && + sink->type != VSP1_ENTITY_HGT) { + if (source->sink) + return -EBUSY; + source->sink = sink_pad->entity; + source->sink_pad = sink_pad->index; + } } else { source->sink = NULL; source->sink_pad = 0; @@ -308,6 +350,85 @@ int vsp1_entity_link_setup(struct media_entity *entity, return 0; } +static int vsp1_entity_link_setup_sink(const struct media_pad *source_pad, + const struct media_pad *sink_pad, + u32 flags) +{ + struct vsp1_entity *sink; + + sink = media_entity_to_vsp1_entity(sink_pad->entity); + + if (flags & MEDIA_LNK_FL_ENABLED) { + /* Fan-in is limited to one. */ + if (sink->sources[sink_pad->index]) + return -EBUSY; + + sink->sources[sink_pad->index] = source_pad->entity; + } else { + sink->sources[sink_pad->index] = NULL; + } + + return 0; +} + +int vsp1_entity_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + if (local->flags & MEDIA_PAD_FL_SOURCE) + return vsp1_entity_link_setup_source(local, remote, flags); + else + return vsp1_entity_link_setup_sink(remote, local, flags); +} + +/** + * vsp1_entity_remote_pad - Find the pad at the remote end of a link + * @pad: Pad at the local end of the link + * + * Search for a remote pad connected to the given pad by iterating over all + * links originating or terminating at that pad until an enabled link is found. + * + * Our link setup implementation guarantees that the output fan-out will not be + * higher than one for the data pipelines, except for the links to the HGO and + * HGT that can be enabled in addition to a regular data link. When traversing + * outgoing links this function ignores HGO and HGT entities and should thus be + * used in place of the generic media_entity_remote_pad() function to traverse + * data pipelines. + * + * Return a pointer to the pad at the remote end of the first found enabled + * link, or NULL if no enabled link has been found. + */ +struct media_pad *vsp1_entity_remote_pad(struct media_pad *pad) +{ + struct media_link *link; + + list_for_each_entry(link, &pad->entity->links, list) { + struct vsp1_entity *entity; + + if (!(link->flags & MEDIA_LNK_FL_ENABLED)) + continue; + + /* If we're the sink the source will never be an HGO or HGT. */ + if (link->sink == pad) + return link->source; + + if (link->source != pad) + continue; + + /* If the sink isn't a subdevice it can't be an HGO or HGT. */ + if (!is_media_entity_v4l2_subdev(link->sink->entity)) + return link->sink; + + entity = media_entity_to_vsp1_entity(link->sink->entity); + if (entity->type != VSP1_ENTITY_HGO && + entity->type != VSP1_ENTITY_HGT) + return link->sink; + } + + return NULL; + +} + /* ----------------------------------------------------------------------------- * Initialization */ @@ -334,6 +455,8 @@ static const struct vsp1_route vsp1_routes[] = { VI6_DPR_NODE_BRU_IN(2), VI6_DPR_NODE_BRU_IN(3), VI6_DPR_NODE_BRU_IN(4) }, VI6_DPR_NODE_BRU_OUT }, VSP1_ENTITY_ROUTE(CLU), + { VSP1_ENTITY_HGO, 0, 0, { 0, }, 0 }, + { VSP1_ENTITY_HGT, 0, 0, { 0, }, 0 }, VSP1_ENTITY_ROUTE(HSI), VSP1_ENTITY_ROUTE(HST), { VSP1_ENTITY_LIF, 0, 0, { VI6_DPR_NODE_LIF, }, VI6_DPR_NODE_LIF }, @@ -386,7 +509,14 @@ int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity, for (i = 0; i < num_pads - 1; ++i) entity->pads[i].flags = MEDIA_PAD_FL_SINK; - entity->pads[num_pads - 1].flags = MEDIA_PAD_FL_SOURCE; + entity->sources = devm_kcalloc(vsp1->dev, max(num_pads - 1, 1U), + sizeof(*entity->sources), GFP_KERNEL); + if (entity->sources == NULL) + return -ENOMEM; + + /* Single-pad entities only have a sink. */ + entity->pads[num_pads - 1].flags = num_pads > 1 ? MEDIA_PAD_FL_SOURCE + : MEDIA_PAD_FL_SINK; /* Initialize the media entity. */ ret = media_entity_pads_init(&entity->subdev.entity, num_pads, @@ -407,7 +537,8 @@ int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity, vsp1_entity_init_cfg(subdev, NULL); - /* Allocate the pad configuration to store formats and selection + /* + * Allocate the pad configuration to store formats and selection * rectangles. */ entity->config = v4l2_subdev_alloc_pad_config(&entity->subdev); diff --git a/drivers/media/platform/vsp1/vsp1_entity.h b/drivers/media/platform/vsp1/vsp1_entity.h index 901146f807b9..c169a060b6d2 100644 --- a/drivers/media/platform/vsp1/vsp1_entity.h +++ b/drivers/media/platform/vsp1/vsp1_entity.h @@ -25,6 +25,8 @@ struct vsp1_pipeline; enum vsp1_entity_type { VSP1_ENTITY_BRU, VSP1_ENTITY_CLU, + VSP1_ENTITY_HGO, + VSP1_ENTITY_HGT, VSP1_ENTITY_HSI, VSP1_ENTITY_HST, VSP1_ENTITY_LIF, @@ -102,6 +104,7 @@ struct vsp1_entity { struct media_pad *pads; unsigned int source_pad; + struct media_entity **sources; struct media_entity *sink; unsigned int sink_pad; @@ -142,9 +145,12 @@ vsp1_entity_get_pad_selection(struct vsp1_entity *entity, int vsp1_entity_init_cfg(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg); -void vsp1_entity_route_setup(struct vsp1_entity *source, +void vsp1_entity_route_setup(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, struct vsp1_dl_list *dl); +struct media_pad *vsp1_entity_remote_pad(struct media_pad *pad); + int vsp1_subdev_get_pad_format(struct v4l2_subdev *subdev, struct v4l2_subdev_pad_config *cfg, struct v4l2_subdev_format *fmt); diff --git a/drivers/media/platform/vsp1/vsp1_hgo.c b/drivers/media/platform/vsp1/vsp1_hgo.c new file mode 100644 index 000000000000..50309c053b78 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_hgo.c @@ -0,0 +1,230 @@ +/* + * vsp1_hgo.c -- R-Car VSP1 Histogram Generator 1D + * + * Copyright (C) 2016 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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. + */ + +#include <linux/device.h> +#include <linux/gfp.h> + +#include <media/v4l2-subdev.h> +#include <media/videobuf2-vmalloc.h> + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_hgo.h" + +#define HGO_DATA_SIZE ((2 + 256) * 4) + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_hgo_read(struct vsp1_hgo *hgo, u32 reg) +{ + return vsp1_read(hgo->histo.entity.vsp1, reg); +} + +static inline void vsp1_hgo_write(struct vsp1_hgo *hgo, struct vsp1_dl_list *dl, + u32 reg, u32 data) +{ + vsp1_dl_list_write(dl, reg, data); +} + +/* ----------------------------------------------------------------------------- + * Frame End Handler + */ + +void vsp1_hgo_frame_end(struct vsp1_entity *entity) +{ + struct vsp1_hgo *hgo = to_hgo(&entity->subdev); + struct vsp1_histogram_buffer *buf; + unsigned int i; + size_t size; + u32 *data; + + buf = vsp1_histogram_buffer_get(&hgo->histo); + if (!buf) + return; + + data = buf->addr; + + if (hgo->num_bins == 256) { + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_MAXMIN); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_SUM); + + for (i = 0; i < 256; ++i) { + vsp1_write(hgo->histo.entity.vsp1, + VI6_HGO_EXT_HIST_ADDR, i); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_EXT_HIST_DATA); + } + + size = (2 + 256) * sizeof(u32); + } else if (hgo->max_rgb) { + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_MAXMIN); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_SUM); + + for (i = 0; i < 64; ++i) + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_HISTO(i)); + + size = (2 + 64) * sizeof(u32); + } else { + *data++ = vsp1_hgo_read(hgo, VI6_HGO_R_MAXMIN); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_MAXMIN); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_B_MAXMIN); + + *data++ = vsp1_hgo_read(hgo, VI6_HGO_R_SUM); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_SUM); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_B_SUM); + + for (i = 0; i < 64; ++i) { + data[i] = vsp1_hgo_read(hgo, VI6_HGO_R_HISTO(i)); + data[i+64] = vsp1_hgo_read(hgo, VI6_HGO_G_HISTO(i)); + data[i+128] = vsp1_hgo_read(hgo, VI6_HGO_B_HISTO(i)); + } + + size = (6 + 64 * 3) * sizeof(u32); + } + + vsp1_histogram_buffer_complete(&hgo->histo, buf, size); +} + +/* ----------------------------------------------------------------------------- + * Controls + */ + +#define V4L2_CID_VSP1_HGO_MAX_RGB (V4L2_CID_USER_BASE | 0x1001) +#define V4L2_CID_VSP1_HGO_NUM_BINS (V4L2_CID_USER_BASE | 0x1002) + +static const struct v4l2_ctrl_config hgo_max_rgb_control = { + .id = V4L2_CID_VSP1_HGO_MAX_RGB, + .name = "Maximum RGB Mode", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .def = 0, + .step = 1, + .flags = V4L2_CTRL_FLAG_MODIFY_LAYOUT, +}; + +static const s64 hgo_num_bins[] = { + 64, 256, +}; + +static const struct v4l2_ctrl_config hgo_num_bins_control = { + .id = V4L2_CID_VSP1_HGO_NUM_BINS, + .name = "Number of Bins", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .min = 0, + .max = 1, + .def = 0, + .qmenu_int = hgo_num_bins, + .flags = V4L2_CTRL_FLAG_MODIFY_LAYOUT, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void hgo_configure(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + enum vsp1_entity_params params) +{ + struct vsp1_hgo *hgo = to_hgo(&entity->subdev); + struct v4l2_rect *compose; + struct v4l2_rect *crop; + unsigned int hratio; + unsigned int vratio; + + if (params != VSP1_ENTITY_PARAMS_INIT) + return; + + crop = vsp1_entity_get_pad_selection(entity, entity->config, + HISTO_PAD_SINK, V4L2_SEL_TGT_CROP); + compose = vsp1_entity_get_pad_selection(entity, entity->config, + HISTO_PAD_SINK, + V4L2_SEL_TGT_COMPOSE); + + vsp1_hgo_write(hgo, dl, VI6_HGO_REGRST, VI6_HGO_REGRST_RCLEA); + + vsp1_hgo_write(hgo, dl, VI6_HGO_OFFSET, + (crop->left << VI6_HGO_OFFSET_HOFFSET_SHIFT) | + (crop->top << VI6_HGO_OFFSET_VOFFSET_SHIFT)); + vsp1_hgo_write(hgo, dl, VI6_HGO_SIZE, + (crop->width << VI6_HGO_SIZE_HSIZE_SHIFT) | + (crop->height << VI6_HGO_SIZE_VSIZE_SHIFT)); + + mutex_lock(hgo->ctrls.handler.lock); + hgo->max_rgb = hgo->ctrls.max_rgb->cur.val; + if (hgo->ctrls.num_bins) + hgo->num_bins = hgo_num_bins[hgo->ctrls.num_bins->cur.val]; + mutex_unlock(hgo->ctrls.handler.lock); + + hratio = crop->width * 2 / compose->width / 3; + vratio = crop->height * 2 / compose->height / 3; + vsp1_hgo_write(hgo, dl, VI6_HGO_MODE, + (hgo->num_bins == 256 ? VI6_HGO_MODE_STEP : 0) | + (hgo->max_rgb ? VI6_HGO_MODE_MAXRGB : 0) | + (hratio << VI6_HGO_MODE_HRATIO_SHIFT) | + (vratio << VI6_HGO_MODE_VRATIO_SHIFT)); +} + +static const struct vsp1_entity_operations hgo_entity_ops = { + .configure = hgo_configure, + .destroy = vsp1_histogram_destroy, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +static const unsigned int hgo_mbus_formats[] = { + MEDIA_BUS_FMT_AYUV8_1X32, + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AHSV8888_1X32, +}; + +struct vsp1_hgo *vsp1_hgo_create(struct vsp1_device *vsp1) +{ + struct vsp1_hgo *hgo; + int ret; + + hgo = devm_kzalloc(vsp1->dev, sizeof(*hgo), GFP_KERNEL); + if (hgo == NULL) + return ERR_PTR(-ENOMEM); + + /* Initialize the control handler. */ + v4l2_ctrl_handler_init(&hgo->ctrls.handler, + vsp1->info->gen == 3 ? 2 : 1); + hgo->ctrls.max_rgb = v4l2_ctrl_new_custom(&hgo->ctrls.handler, + &hgo_max_rgb_control, NULL); + if (vsp1->info->gen == 3) + hgo->ctrls.num_bins = + v4l2_ctrl_new_custom(&hgo->ctrls.handler, + &hgo_num_bins_control, NULL); + + hgo->max_rgb = false; + hgo->num_bins = 64; + + hgo->histo.entity.subdev.ctrl_handler = &hgo->ctrls.handler; + + /* Initialize the video device and queue for statistics data. */ + ret = vsp1_histogram_init(vsp1, &hgo->histo, VSP1_ENTITY_HGO, "hgo", + &hgo_entity_ops, hgo_mbus_formats, + ARRAY_SIZE(hgo_mbus_formats), + HGO_DATA_SIZE, V4L2_META_FMT_VSP1_HGO); + if (ret < 0) { + vsp1_entity_destroy(&hgo->histo.entity); + return ERR_PTR(ret); + } + + return hgo; +} diff --git a/drivers/media/platform/vsp1/vsp1_hgo.h b/drivers/media/platform/vsp1/vsp1_hgo.h new file mode 100644 index 000000000000..c6c0b7a80e0c --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_hgo.h @@ -0,0 +1,45 @@ +/* + * vsp1_hgo.h -- R-Car VSP1 Histogram Generator 1D + * + * Copyright (C) 2016 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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. + */ +#ifndef __VSP1_HGO_H__ +#define __VSP1_HGO_H__ + +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#include "vsp1_histo.h" + +struct vsp1_device; + +struct vsp1_hgo { + struct vsp1_histogram histo; + + struct { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *max_rgb; + struct v4l2_ctrl *num_bins; + } ctrls; + + bool max_rgb; + unsigned int num_bins; +}; + +static inline struct vsp1_hgo *to_hgo(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_hgo, histo.entity.subdev); +} + +struct vsp1_hgo *vsp1_hgo_create(struct vsp1_device *vsp1); +void vsp1_hgo_frame_end(struct vsp1_entity *hgo); + +#endif /* __VSP1_HGO_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_hgt.c b/drivers/media/platform/vsp1/vsp1_hgt.c new file mode 100644 index 000000000000..b5ce305e3e6f --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_hgt.c @@ -0,0 +1,222 @@ +/* + * vsp1_hgt.c -- R-Car VSP1 Histogram Generator 2D + * + * Copyright (C) 2016 Renesas Electronics Corporation + * + * Contact: Niklas Söderlund (niklas.soderlund@ragnatech.se) + * + * 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. + */ + +#include <linux/device.h> +#include <linux/gfp.h> + +#include <media/v4l2-subdev.h> +#include <media/videobuf2-vmalloc.h> + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_hgt.h" + +#define HGT_DATA_SIZE ((2 + 6 * 32) * 4) + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_hgt_read(struct vsp1_hgt *hgt, u32 reg) +{ + return vsp1_read(hgt->histo.entity.vsp1, reg); +} + +static inline void vsp1_hgt_write(struct vsp1_hgt *hgt, struct vsp1_dl_list *dl, + u32 reg, u32 data) +{ + vsp1_dl_list_write(dl, reg, data); +} + +/* ----------------------------------------------------------------------------- + * Frame End Handler + */ + +void vsp1_hgt_frame_end(struct vsp1_entity *entity) +{ + struct vsp1_hgt *hgt = to_hgt(&entity->subdev); + struct vsp1_histogram_buffer *buf; + unsigned int m; + unsigned int n; + u32 *data; + + buf = vsp1_histogram_buffer_get(&hgt->histo); + if (!buf) + return; + + data = buf->addr; + + *data++ = vsp1_hgt_read(hgt, VI6_HGT_MAXMIN); + *data++ = vsp1_hgt_read(hgt, VI6_HGT_SUM); + + for (m = 0; m < 6; ++m) + for (n = 0; n < 32; ++n) + *data++ = vsp1_hgt_read(hgt, VI6_HGT_HISTO(m, n)); + + vsp1_histogram_buffer_complete(&hgt->histo, buf, HGT_DATA_SIZE); +} + +/* ----------------------------------------------------------------------------- + * Controls + */ + +#define V4L2_CID_VSP1_HGT_HUE_AREAS (V4L2_CID_USER_BASE | 0x1001) + +static int hgt_hue_areas_try_ctrl(struct v4l2_ctrl *ctrl) +{ + const u8 *values = ctrl->p_new.p_u8; + unsigned int i; + + /* + * The hardware has constraints on the hue area boundaries beyond the + * control min, max and step. The values must match one of the following + * expressions. + * + * 0L <= 0U <= 1L <= 1U <= 2L <= 2U <= 3L <= 3U <= 4L <= 4U <= 5L <= 5U + * 0U <= 1L <= 1U <= 2L <= 2U <= 3L <= 3U <= 4L <= 4U <= 5L <= 5U <= 0L + * + * Start by verifying the common part... + */ + for (i = 1; i < (HGT_NUM_HUE_AREAS * 2) - 1; ++i) { + if (values[i] > values[i+1]) + return -EINVAL; + } + + /* ... and handle 0L separately. */ + if (values[0] > values[1] && values[11] > values[0]) + return -EINVAL; + + return 0; +} + +static int hgt_hue_areas_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vsp1_hgt *hgt = container_of(ctrl->handler, struct vsp1_hgt, + ctrls); + + memcpy(hgt->hue_areas, ctrl->p_new.p_u8, sizeof(hgt->hue_areas)); + return 0; +} + +static const struct v4l2_ctrl_ops hgt_hue_areas_ctrl_ops = { + .try_ctrl = hgt_hue_areas_try_ctrl, + .s_ctrl = hgt_hue_areas_s_ctrl, +}; + +static const struct v4l2_ctrl_config hgt_hue_areas = { + .ops = &hgt_hue_areas_ctrl_ops, + .id = V4L2_CID_VSP1_HGT_HUE_AREAS, + .name = "Boundary Values for Hue Area", + .type = V4L2_CTRL_TYPE_U8, + .min = 0, + .max = 255, + .def = 0, + .step = 1, + .dims = { 12 }, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void hgt_configure(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + enum vsp1_entity_params params) +{ + struct vsp1_hgt *hgt = to_hgt(&entity->subdev); + struct v4l2_rect *compose; + struct v4l2_rect *crop; + unsigned int hratio; + unsigned int vratio; + u8 lower; + u8 upper; + unsigned int i; + + if (params != VSP1_ENTITY_PARAMS_INIT) + return; + + crop = vsp1_entity_get_pad_selection(entity, entity->config, + HISTO_PAD_SINK, V4L2_SEL_TGT_CROP); + compose = vsp1_entity_get_pad_selection(entity, entity->config, + HISTO_PAD_SINK, + V4L2_SEL_TGT_COMPOSE); + + vsp1_hgt_write(hgt, dl, VI6_HGT_REGRST, VI6_HGT_REGRST_RCLEA); + + vsp1_hgt_write(hgt, dl, VI6_HGT_OFFSET, + (crop->left << VI6_HGT_OFFSET_HOFFSET_SHIFT) | + (crop->top << VI6_HGT_OFFSET_VOFFSET_SHIFT)); + vsp1_hgt_write(hgt, dl, VI6_HGT_SIZE, + (crop->width << VI6_HGT_SIZE_HSIZE_SHIFT) | + (crop->height << VI6_HGT_SIZE_VSIZE_SHIFT)); + + mutex_lock(hgt->ctrls.lock); + for (i = 0; i < HGT_NUM_HUE_AREAS; ++i) { + lower = hgt->hue_areas[i*2 + 0]; + upper = hgt->hue_areas[i*2 + 1]; + vsp1_hgt_write(hgt, dl, VI6_HGT_HUE_AREA(i), + (lower << VI6_HGT_HUE_AREA_LOWER_SHIFT) | + (upper << VI6_HGT_HUE_AREA_UPPER_SHIFT)); + } + mutex_unlock(hgt->ctrls.lock); + + hratio = crop->width * 2 / compose->width / 3; + vratio = crop->height * 2 / compose->height / 3; + vsp1_hgt_write(hgt, dl, VI6_HGT_MODE, + (hratio << VI6_HGT_MODE_HRATIO_SHIFT) | + (vratio << VI6_HGT_MODE_VRATIO_SHIFT)); +} + +static const struct vsp1_entity_operations hgt_entity_ops = { + .configure = hgt_configure, + .destroy = vsp1_histogram_destroy, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +static const unsigned int hgt_mbus_formats[] = { + MEDIA_BUS_FMT_AHSV8888_1X32, +}; + +struct vsp1_hgt *vsp1_hgt_create(struct vsp1_device *vsp1) +{ + struct vsp1_hgt *hgt; + int ret; + + hgt = devm_kzalloc(vsp1->dev, sizeof(*hgt), GFP_KERNEL); + if (hgt == NULL) + return ERR_PTR(-ENOMEM); + + /* Initialize the control handler. */ + v4l2_ctrl_handler_init(&hgt->ctrls, 1); + v4l2_ctrl_new_custom(&hgt->ctrls, &hgt_hue_areas, NULL); + + hgt->histo.entity.subdev.ctrl_handler = &hgt->ctrls; + + /* Initialize the video device and queue for statistics data. */ + ret = vsp1_histogram_init(vsp1, &hgt->histo, VSP1_ENTITY_HGT, "hgt", + &hgt_entity_ops, hgt_mbus_formats, + ARRAY_SIZE(hgt_mbus_formats), + HGT_DATA_SIZE, V4L2_META_FMT_VSP1_HGT); + if (ret < 0) { + vsp1_entity_destroy(&hgt->histo.entity); + return ERR_PTR(ret); + } + + v4l2_ctrl_handler_setup(&hgt->ctrls); + + return hgt; +} diff --git a/drivers/media/platform/vsp1/vsp1_hgt.h b/drivers/media/platform/vsp1/vsp1_hgt.h new file mode 100644 index 000000000000..83f2e130942a --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_hgt.h @@ -0,0 +1,42 @@ +/* + * vsp1_hgt.h -- R-Car VSP1 Histogram Generator 2D + * + * Copyright (C) 2016 Renesas Electronics Corporation + * + * Contact: Niklas Söderlund (niklas.soderlund@ragnatech.se) + * + * 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. + */ +#ifndef __VSP1_HGT_H__ +#define __VSP1_HGT_H__ + +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-subdev.h> + +#include "vsp1_histo.h" + +struct vsp1_device; + +#define HGT_NUM_HUE_AREAS 6 + +struct vsp1_hgt { + struct vsp1_histogram histo; + + struct v4l2_ctrl_handler ctrls; + + u8 hue_areas[HGT_NUM_HUE_AREAS * 2]; +}; + +static inline struct vsp1_hgt *to_hgt(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_hgt, histo.entity.subdev); +} + +struct vsp1_hgt *vsp1_hgt_create(struct vsp1_device *vsp1); +void vsp1_hgt_frame_end(struct vsp1_entity *hgt); + +#endif /* __VSP1_HGT_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_histo.c b/drivers/media/platform/vsp1/vsp1_histo.c new file mode 100644 index 000000000000..afab77cf4fa5 --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_histo.c @@ -0,0 +1,646 @@ +/* + * vsp1_histo.c -- R-Car VSP1 Histogram API + * + * Copyright (C) 2016 Renesas Electronics Corporation + * Copyright (C) 2016 Laurent Pinchart + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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. + */ + +#include <linux/device.h> +#include <linux/gfp.h> + +#include <media/v4l2-ioctl.h> +#include <media/v4l2-subdev.h> +#include <media/videobuf2-vmalloc.h> + +#include "vsp1.h" +#include "vsp1_histo.h" +#include "vsp1_pipe.h" + +#define HISTO_MIN_SIZE 4U +#define HISTO_MAX_SIZE 8192U + +/* ----------------------------------------------------------------------------- + * Buffer Operations + */ + +static inline struct vsp1_histogram_buffer * +to_vsp1_histogram_buffer(struct vb2_v4l2_buffer *vbuf) +{ + return container_of(vbuf, struct vsp1_histogram_buffer, buf); +} + +struct vsp1_histogram_buffer * +vsp1_histogram_buffer_get(struct vsp1_histogram *histo) +{ + struct vsp1_histogram_buffer *buf = NULL; + unsigned long flags; + + spin_lock_irqsave(&histo->irqlock, flags); + + if (list_empty(&histo->irqqueue)) + goto done; + + buf = list_first_entry(&histo->irqqueue, struct vsp1_histogram_buffer, + queue); + list_del(&buf->queue); + histo->readout = true; + +done: + spin_unlock_irqrestore(&histo->irqlock, flags); + return buf; +} + +void vsp1_histogram_buffer_complete(struct vsp1_histogram *histo, + struct vsp1_histogram_buffer *buf, + size_t size) +{ + struct vsp1_pipeline *pipe = histo->pipe; + unsigned long flags; + + /* + * The pipeline pointer is guaranteed to be valid as this function is + * called from the frame completion interrupt handler, which can only + * occur when video streaming is active. + */ + buf->buf.sequence = pipe->sequence; + buf->buf.vb2_buf.timestamp = ktime_get_ns(); + vb2_set_plane_payload(&buf->buf.vb2_buf, 0, size); + vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_DONE); + + spin_lock_irqsave(&histo->irqlock, flags); + histo->readout = false; + wake_up(&histo->wait_queue); + spin_unlock_irqrestore(&histo->irqlock, flags); +} + +/* ----------------------------------------------------------------------------- + * videobuf2 Queue Operations + */ + +static int histo_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct vsp1_histogram *histo = vb2_get_drv_priv(vq); + + if (*nplanes) { + if (*nplanes != 1) + return -EINVAL; + + if (sizes[0] < histo->data_size) + return -EINVAL; + + return 0; + } + + *nplanes = 1; + sizes[0] = histo->data_size; + + return 0; +} + +static int histo_buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vsp1_histogram *histo = vb2_get_drv_priv(vb->vb2_queue); + struct vsp1_histogram_buffer *buf = to_vsp1_histogram_buffer(vbuf); + + if (vb->num_planes != 1) + return -EINVAL; + + if (vb2_plane_size(vb, 0) < histo->data_size) + return -EINVAL; + + buf->addr = vb2_plane_vaddr(vb, 0); + + return 0; +} + +static void histo_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vsp1_histogram *histo = vb2_get_drv_priv(vb->vb2_queue); + struct vsp1_histogram_buffer *buf = to_vsp1_histogram_buffer(vbuf); + unsigned long flags; + + spin_lock_irqsave(&histo->irqlock, flags); + list_add_tail(&buf->queue, &histo->irqqueue); + spin_unlock_irqrestore(&histo->irqlock, flags); +} + +static int histo_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + return 0; +} + +static void histo_stop_streaming(struct vb2_queue *vq) +{ + struct vsp1_histogram *histo = vb2_get_drv_priv(vq); + struct vsp1_histogram_buffer *buffer; + unsigned long flags; + + spin_lock_irqsave(&histo->irqlock, flags); + + /* Remove all buffers from the IRQ queue. */ + list_for_each_entry(buffer, &histo->irqqueue, queue) + vb2_buffer_done(&buffer->buf.vb2_buf, VB2_BUF_STATE_ERROR); + INIT_LIST_HEAD(&histo->irqqueue); + + /* Wait for the buffer being read out (if any) to complete. */ + wait_event_lock_irq(histo->wait_queue, !histo->readout, histo->irqlock); + + spin_unlock_irqrestore(&histo->irqlock, flags); +} + +static const struct vb2_ops histo_video_queue_qops = { + .queue_setup = histo_queue_setup, + .buf_prepare = histo_buffer_prepare, + .buf_queue = histo_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = histo_start_streaming, + .stop_streaming = histo_stop_streaming, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static int histo_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct vsp1_histogram *histo = subdev_to_histo(subdev); + + if (code->pad == HISTO_PAD_SOURCE) { + code->code = MEDIA_BUS_FMT_FIXED; + return 0; + } + + return vsp1_subdev_enum_mbus_code(subdev, cfg, code, histo->formats, + histo->num_formats); +} + +static int histo_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->pad != HISTO_PAD_SINK) + return -EINVAL; + + return vsp1_subdev_enum_frame_size(subdev, cfg, fse, HISTO_MIN_SIZE, + HISTO_MIN_SIZE, HISTO_MAX_SIZE, + HISTO_MAX_SIZE); +} + +static int histo_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_histogram *histo = subdev_to_histo(subdev); + struct v4l2_subdev_pad_config *config; + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + int ret = 0; + + if (sel->pad != HISTO_PAD_SINK) + return -EINVAL; + + mutex_lock(&histo->entity.lock); + + config = vsp1_entity_get_pad_config(&histo->entity, cfg, sel->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + crop = vsp1_entity_get_pad_selection(&histo->entity, config, + HISTO_PAD_SINK, + V4L2_SEL_TGT_CROP); + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = crop->width; + sel->r.height = crop->height; + break; + + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + format = vsp1_entity_get_pad_format(&histo->entity, config, + HISTO_PAD_SINK); + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = format->width; + sel->r.height = format->height; + break; + + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_CROP: + sel->r = *vsp1_entity_get_pad_selection(&histo->entity, config, + sel->pad, sel->target); + break; + + default: + ret = -EINVAL; + break; + } + +done: + mutex_unlock(&histo->entity.lock); + return ret; +} + +static int histo_set_crop(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *config, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_histogram *histo = subdev_to_histo(subdev); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *selection; + + /* The crop rectangle must be inside the input frame. */ + format = vsp1_entity_get_pad_format(&histo->entity, config, + HISTO_PAD_SINK); + sel->r.left = clamp_t(unsigned int, sel->r.left, 0, format->width - 1); + sel->r.top = clamp_t(unsigned int, sel->r.top, 0, format->height - 1); + sel->r.width = clamp_t(unsigned int, sel->r.width, HISTO_MIN_SIZE, + format->width - sel->r.left); + sel->r.height = clamp_t(unsigned int, sel->r.height, HISTO_MIN_SIZE, + format->height - sel->r.top); + + /* Set the crop rectangle and reset the compose rectangle. */ + selection = vsp1_entity_get_pad_selection(&histo->entity, config, + sel->pad, V4L2_SEL_TGT_CROP); + *selection = sel->r; + + selection = vsp1_entity_get_pad_selection(&histo->entity, config, + sel->pad, + V4L2_SEL_TGT_COMPOSE); + *selection = sel->r; + + return 0; +} + +static int histo_set_compose(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *config, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_histogram *histo = subdev_to_histo(subdev); + struct v4l2_rect *compose; + struct v4l2_rect *crop; + unsigned int ratio; + + /* + * The compose rectangle is used to configure downscaling, the top left + * corner is fixed to (0,0) and the size to 1/2 or 1/4 of the crop + * rectangle. + */ + sel->r.left = 0; + sel->r.top = 0; + + crop = vsp1_entity_get_pad_selection(&histo->entity, config, sel->pad, + V4L2_SEL_TGT_CROP); + + /* + * Clamp the width and height to acceptable values first and then + * compute the closest rounded dividing ratio. + * + * Ratio Rounded ratio + * -------------------------- + * [1.0 1.5[ 1 + * [1.5 3.0[ 2 + * [3.0 4.0] 4 + * + * The rounded ratio can be computed using + * + * 1 << (ceil(ratio * 2) / 3) + */ + sel->r.width = clamp(sel->r.width, crop->width / 4, crop->width); + ratio = 1 << (crop->width * 2 / sel->r.width / 3); + sel->r.width = crop->width / ratio; + + + sel->r.height = clamp(sel->r.height, crop->height / 4, crop->height); + ratio = 1 << (crop->height * 2 / sel->r.height / 3); + sel->r.height = crop->height / ratio; + + compose = vsp1_entity_get_pad_selection(&histo->entity, config, + sel->pad, + V4L2_SEL_TGT_COMPOSE); + *compose = sel->r; + + return 0; +} + +static int histo_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_histogram *histo = subdev_to_histo(subdev); + struct v4l2_subdev_pad_config *config; + int ret; + + if (sel->pad != HISTO_PAD_SINK) + return -EINVAL; + + mutex_lock(&histo->entity.lock); + + config = vsp1_entity_get_pad_config(&histo->entity, cfg, sel->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + if (sel->target == V4L2_SEL_TGT_CROP) + ret = histo_set_crop(subdev, config, sel); + else if (sel->target == V4L2_SEL_TGT_COMPOSE) + ret = histo_set_compose(subdev, config, sel); + else + ret = -EINVAL; + +done: + mutex_unlock(&histo->entity.lock); + return ret; +} + +static int histo_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + if (fmt->pad == HISTO_PAD_SOURCE) { + fmt->format.code = MEDIA_BUS_FMT_FIXED; + fmt->format.width = 0; + fmt->format.height = 0; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_RAW; + return 0; + } + + return vsp1_subdev_get_pad_format(subdev, cfg, fmt); +} + +static int histo_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_histogram *histo = subdev_to_histo(subdev); + struct v4l2_subdev_pad_config *config; + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *selection; + unsigned int i; + int ret = 0; + + if (fmt->pad != HISTO_PAD_SINK) + return histo_get_format(subdev, cfg, fmt); + + mutex_lock(&histo->entity.lock); + + config = vsp1_entity_get_pad_config(&histo->entity, cfg, fmt->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + /* + * Default to the first format if the requested format is not + * supported. + */ + for (i = 0; i < histo->num_formats; ++i) { + if (fmt->format.code == histo->formats[i]) + break; + } + if (i == histo->num_formats) + fmt->format.code = histo->formats[0]; + + format = vsp1_entity_get_pad_format(&histo->entity, config, fmt->pad); + + format->code = fmt->format.code; + format->width = clamp_t(unsigned int, fmt->format.width, + HISTO_MIN_SIZE, HISTO_MAX_SIZE); + format->height = clamp_t(unsigned int, fmt->format.height, + HISTO_MIN_SIZE, HISTO_MAX_SIZE); + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + fmt->format = *format; + + /* Reset the crop and compose rectangles */ + selection = vsp1_entity_get_pad_selection(&histo->entity, config, + fmt->pad, V4L2_SEL_TGT_CROP); + selection->left = 0; + selection->top = 0; + selection->width = format->width; + selection->height = format->height; + + selection = vsp1_entity_get_pad_selection(&histo->entity, config, + fmt->pad, + V4L2_SEL_TGT_COMPOSE); + selection->left = 0; + selection->top = 0; + selection->width = format->width; + selection->height = format->height; + +done: + mutex_unlock(&histo->entity.lock); + return ret; +} + +static const struct v4l2_subdev_pad_ops histo_pad_ops = { + .enum_mbus_code = histo_enum_mbus_code, + .enum_frame_size = histo_enum_frame_size, + .get_fmt = histo_get_format, + .set_fmt = histo_set_format, + .get_selection = histo_get_selection, + .set_selection = histo_set_selection, +}; + +static const struct v4l2_subdev_ops histo_ops = { + .pad = &histo_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 ioctls + */ + +static int histo_v4l2_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_histogram *histo = vdev_to_histo(vfh->vdev); + + cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING + | V4L2_CAP_VIDEO_CAPTURE_MPLANE + | V4L2_CAP_VIDEO_OUTPUT_MPLANE + | V4L2_CAP_META_CAPTURE; + cap->device_caps = V4L2_CAP_META_CAPTURE + | V4L2_CAP_STREAMING; + + strlcpy(cap->driver, "vsp1", sizeof(cap->driver)); + strlcpy(cap->card, histo->video.name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + dev_name(histo->entity.vsp1->dev)); + + return 0; +} + +static int histo_v4l2_enum_format(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_histogram *histo = vdev_to_histo(vfh->vdev); + + if (f->index > 0 || f->type != histo->queue.type) + return -EINVAL; + + f->pixelformat = histo->meta_format; + + return 0; +} + +static int histo_v4l2_get_format(struct file *file, void *fh, + struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_histogram *histo = vdev_to_histo(vfh->vdev); + struct v4l2_meta_format *meta = &format->fmt.meta; + + if (format->type != histo->queue.type) + return -EINVAL; + + memset(meta, 0, sizeof(*meta)); + + meta->dataformat = histo->meta_format; + meta->buffersize = histo->data_size; + + return 0; +} + +static const struct v4l2_ioctl_ops histo_v4l2_ioctl_ops = { + .vidioc_querycap = histo_v4l2_querycap, + .vidioc_enum_fmt_meta_cap = histo_v4l2_enum_format, + .vidioc_g_fmt_meta_cap = histo_v4l2_get_format, + .vidioc_s_fmt_meta_cap = histo_v4l2_get_format, + .vidioc_try_fmt_meta_cap = histo_v4l2_get_format, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 File Operations + */ + +static const struct v4l2_file_operations histo_v4l2_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +}; + +static void vsp1_histogram_cleanup(struct vsp1_histogram *histo) +{ + if (video_is_registered(&histo->video)) + video_unregister_device(&histo->video); + + media_entity_cleanup(&histo->video.entity); +} + +void vsp1_histogram_destroy(struct vsp1_entity *entity) +{ + struct vsp1_histogram *histo = subdev_to_histo(&entity->subdev); + + vsp1_histogram_cleanup(histo); +} + +int vsp1_histogram_init(struct vsp1_device *vsp1, struct vsp1_histogram *histo, + enum vsp1_entity_type type, const char *name, + const struct vsp1_entity_operations *ops, + const unsigned int *formats, unsigned int num_formats, + size_t data_size, u32 meta_format) +{ + int ret; + + histo->formats = formats; + histo->num_formats = num_formats; + histo->data_size = data_size; + histo->meta_format = meta_format; + + histo->pad.flags = MEDIA_PAD_FL_SINK; + histo->video.vfl_dir = VFL_DIR_RX; + + mutex_init(&histo->lock); + spin_lock_init(&histo->irqlock); + INIT_LIST_HEAD(&histo->irqqueue); + init_waitqueue_head(&histo->wait_queue); + + /* Initialize the VSP entity... */ + histo->entity.ops = ops; + histo->entity.type = type; + + ret = vsp1_entity_init(vsp1, &histo->entity, name, 2, &histo_ops, + MEDIA_ENT_F_PROC_VIDEO_STATISTICS); + if (ret < 0) + return ret; + + /* ... and the media entity... */ + ret = media_entity_pads_init(&histo->video.entity, 1, &histo->pad); + if (ret < 0) + return ret; + + /* ... and the video node... */ + histo->video.v4l2_dev = &vsp1->v4l2_dev; + histo->video.fops = &histo_v4l2_fops; + snprintf(histo->video.name, sizeof(histo->video.name), + "%s histo", histo->entity.subdev.name); + histo->video.vfl_type = VFL_TYPE_GRABBER; + histo->video.release = video_device_release_empty; + histo->video.ioctl_ops = &histo_v4l2_ioctl_ops; + + video_set_drvdata(&histo->video, histo); + + /* ... and the buffers queue... */ + histo->queue.type = V4L2_BUF_TYPE_META_CAPTURE; + histo->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + histo->queue.lock = &histo->lock; + histo->queue.drv_priv = histo; + histo->queue.buf_struct_size = sizeof(struct vsp1_histogram_buffer); + histo->queue.ops = &histo_video_queue_qops; + histo->queue.mem_ops = &vb2_vmalloc_memops; + histo->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + histo->queue.dev = vsp1->dev; + ret = vb2_queue_init(&histo->queue); + if (ret < 0) { + dev_err(vsp1->dev, "failed to initialize vb2 queue\n"); + goto error; + } + + /* ... and register the video device. */ + histo->video.queue = &histo->queue; + ret = video_register_device(&histo->video, VFL_TYPE_GRABBER, -1); + if (ret < 0) { + dev_err(vsp1->dev, "failed to register video device\n"); + goto error; + } + + return 0; + +error: + vsp1_histogram_cleanup(histo); + return ret; +} diff --git a/drivers/media/platform/vsp1/vsp1_histo.h b/drivers/media/platform/vsp1/vsp1_histo.h new file mode 100644 index 000000000000..af2874f6031d --- /dev/null +++ b/drivers/media/platform/vsp1/vsp1_histo.h @@ -0,0 +1,84 @@ +/* + * vsp1_histo.h -- R-Car VSP1 Histogram API + * + * Copyright (C) 2016 Renesas Electronics Corporation + * Copyright (C) 2016 Laurent Pinchart + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.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. + */ +#ifndef __VSP1_HISTO_H__ +#define __VSP1_HISTO_H__ + +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> + +#include <media/media-entity.h> +#include <media/v4l2-dev.h> +#include <media/videobuf2-v4l2.h> + +#include "vsp1_entity.h" + +struct vsp1_device; +struct vsp1_pipeline; + +#define HISTO_PAD_SINK 0 +#define HISTO_PAD_SOURCE 1 + +struct vsp1_histogram_buffer { + struct vb2_v4l2_buffer buf; + struct list_head queue; + void *addr; +}; + +struct vsp1_histogram { + struct vsp1_pipeline *pipe; + + struct vsp1_entity entity; + struct video_device video; + struct media_pad pad; + + const u32 *formats; + unsigned int num_formats; + size_t data_size; + u32 meta_format; + + struct mutex lock; + struct vb2_queue queue; + + spinlock_t irqlock; + struct list_head irqqueue; + + wait_queue_head_t wait_queue; + bool readout; +}; + +static inline struct vsp1_histogram *vdev_to_histo(struct video_device *vdev) +{ + return container_of(vdev, struct vsp1_histogram, video); +} + +static inline struct vsp1_histogram *subdev_to_histo(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_histogram, entity.subdev); +} + +int vsp1_histogram_init(struct vsp1_device *vsp1, struct vsp1_histogram *histo, + enum vsp1_entity_type type, const char *name, + const struct vsp1_entity_operations *ops, + const unsigned int *formats, unsigned int num_formats, + size_t data_size, u32 meta_format); +void vsp1_histogram_destroy(struct vsp1_entity *entity); + +struct vsp1_histogram_buffer * +vsp1_histogram_buffer_get(struct vsp1_histogram *histo); +void vsp1_histogram_buffer_complete(struct vsp1_histogram *histo, + struct vsp1_histogram_buffer *buf, + size_t size); + +#endif /* __VSP1_HISTO_H__ */ diff --git a/drivers/media/platform/vsp1/vsp1_hsit.c b/drivers/media/platform/vsp1/vsp1_hsit.c index 94316afc54ff..764d405345ee 100644 --- a/drivers/media/platform/vsp1/vsp1_hsit.c +++ b/drivers/media/platform/vsp1/vsp1_hsit.c @@ -84,7 +84,8 @@ static int hsit_set_format(struct v4l2_subdev *subdev, format = vsp1_entity_get_pad_format(&hsit->entity, config, fmt->pad); if (fmt->pad == HSIT_PAD_SOURCE) { - /* The HST and HSI output format code and resolution can't be + /* + * The HST and HSI output format code and resolution can't be * modified. */ fmt->format = *format; diff --git a/drivers/media/platform/vsp1/vsp1_lif.c b/drivers/media/platform/vsp1/vsp1_lif.c index e32acae1fc6e..702487f895b3 100644 --- a/drivers/media/platform/vsp1/vsp1_lif.c +++ b/drivers/media/platform/vsp1/vsp1_lif.c @@ -84,7 +84,8 @@ static int lif_set_format(struct v4l2_subdev *subdev, format = vsp1_entity_get_pad_format(&lif->entity, config, fmt->pad); if (fmt->pad == LIF_PAD_SOURCE) { - /* The LIF source format is always identical to its sink + /* + * The LIF source format is always identical to its sink * format. */ fmt->format = *format; @@ -176,7 +177,8 @@ struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1) lif->entity.ops = &lif_entity_ops; lif->entity.type = VSP1_ENTITY_LIF; - /* The LIF is never exposed to userspace, but media entity registration + /* + * The LIF is never exposed to userspace, but media entity registration * requires a function to be set. Use PROC_VIDEO_PIXEL_FORMATTER just to * avoid triggering a WARN_ON(), the value won't be seen anywhere. */ diff --git a/drivers/media/platform/vsp1/vsp1_pipe.c b/drivers/media/platform/vsp1/vsp1_pipe.c index 280ba0804699..edebf3fa926f 100644 --- a/drivers/media/platform/vsp1/vsp1_pipe.c +++ b/drivers/media/platform/vsp1/vsp1_pipe.c @@ -23,6 +23,8 @@ #include "vsp1_bru.h" #include "vsp1_dl.h" #include "vsp1_entity.h" +#include "vsp1_hgo.h" +#include "vsp1_hgt.h" #include "vsp1_pipe.h" #include "vsp1_rwpf.h" #include "vsp1_uds.h" @@ -157,9 +159,15 @@ const struct vsp1_format_info *vsp1_get_format_info(struct vsp1_device *vsp1, { unsigned int i; - /* Special case, the VYUY format is supported on Gen2 only. */ - if (vsp1->info->gen != 2 && fourcc == V4L2_PIX_FMT_VYUY) - return NULL; + /* Special case, the VYUY and HSV formats are supported on Gen2 only. */ + if (vsp1->info->gen != 2) { + switch (fourcc) { + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_HSV24: + case V4L2_PIX_FMT_HSV32: + return NULL; + } + } for (i = 0; i < ARRAY_SIZE(vsp1_video_formats); ++i) { const struct vsp1_format_info *info = &vsp1_video_formats[i]; @@ -198,11 +206,25 @@ void vsp1_pipeline_reset(struct vsp1_pipeline *pipe) pipe->output = NULL; } + if (pipe->hgo) { + struct vsp1_hgo *hgo = to_hgo(&pipe->hgo->subdev); + + hgo->histo.pipe = NULL; + } + + if (pipe->hgt) { + struct vsp1_hgt *hgt = to_hgt(&pipe->hgt->subdev); + + hgt->histo.pipe = NULL; + } + INIT_LIST_HEAD(&pipe->entities); pipe->state = VSP1_PIPELINE_STOPPED; pipe->buffers_ready = 0; pipe->num_inputs = 0; pipe->bru = NULL; + pipe->hgo = NULL; + pipe->hgt = NULL; pipe->lif = NULL; pipe->uds = NULL; } @@ -246,16 +268,17 @@ bool vsp1_pipeline_stopped(struct vsp1_pipeline *pipe) int vsp1_pipeline_stop(struct vsp1_pipeline *pipe) { + struct vsp1_device *vsp1 = pipe->output->entity.vsp1; struct vsp1_entity *entity; unsigned long flags; int ret; if (pipe->lif) { - /* When using display lists in continuous frame mode the only + /* + * When using display lists in continuous frame mode the only * way to stop the pipeline is to reset the hardware. */ - ret = vsp1_reset_wpf(pipe->output->entity.vsp1, - pipe->output->entity.index); + ret = vsp1_reset_wpf(vsp1, pipe->output->entity.index); if (ret == 0) { spin_lock_irqsave(&pipe->irqlock, flags); pipe->state = VSP1_PIPELINE_STOPPED; @@ -275,10 +298,20 @@ int vsp1_pipeline_stop(struct vsp1_pipeline *pipe) list_for_each_entry(entity, &pipe->entities, list_pipe) { if (entity->route && entity->route->reg) - vsp1_write(entity->vsp1, entity->route->reg, + vsp1_write(vsp1, entity->route->reg, VI6_DPR_NODE_UNUSED); } + if (pipe->hgo) + vsp1_write(vsp1, VI6_DPR_HGO_SMPPT, + (7 << VI6_DPR_SMPPT_TGW_SHIFT) | + (VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT)); + + if (pipe->hgt) + vsp1_write(vsp1, VI6_DPR_HGT_SMPPT, + (7 << VI6_DPR_SMPPT_TGW_SHIFT) | + (VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT)); + v4l2_subdev_call(&pipe->output->entity.subdev, video, s_stream, 0); return ret; @@ -302,6 +335,12 @@ void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe) vsp1_dlm_irq_frame_end(pipe->output->dlm); + if (pipe->hgo) + vsp1_hgo_frame_end(pipe->hgo); + + if (pipe->hgt) + vsp1_hgt_frame_end(pipe->hgt); + if (pipe->frame_end) pipe->frame_end(pipe); @@ -322,7 +361,8 @@ void vsp1_pipeline_propagate_alpha(struct vsp1_pipeline *pipe, if (!pipe->uds) return; - /* The BRU background color has a fixed alpha value set to 255, the + /* + * The BRU background color has a fixed alpha value set to 255, the * output alpha value is thus always equal to 255. */ if (pipe->uds_input->type == VSP1_ENTITY_BRU) @@ -337,7 +377,8 @@ void vsp1_pipelines_suspend(struct vsp1_device *vsp1) unsigned int i; int ret; - /* To avoid increasing the system suspend time needlessly, loop over the + /* + * To avoid increasing the system suspend time needlessly, loop over the * pipelines twice, first to set them all to the stopping state, and * then to wait for the stop to complete. */ diff --git a/drivers/media/platform/vsp1/vsp1_pipe.h b/drivers/media/platform/vsp1/vsp1_pipe.h index ac4ad2655551..91a784a13422 100644 --- a/drivers/media/platform/vsp1/vsp1_pipe.h +++ b/drivers/media/platform/vsp1/vsp1_pipe.h @@ -25,11 +25,12 @@ struct vsp1_rwpf; /* * struct vsp1_format_info - VSP1 video format description - * @mbus: media bus format code * @fourcc: V4L2 pixel format FCC identifier + * @mbus: media bus format code + * @hwfmt: VSP1 hardware format + * @swap: swap register control * @planes: number of planes * @bpp: bits per pixel - * @hwfmt: VSP1 hardware format * @swap_yc: the Y and C components are swapped (Y comes before C) * @swap_uv: the U and V components are swapped (V comes before U) * @hsub: horizontal subsampling factor @@ -72,6 +73,8 @@ enum vsp1_pipeline_state { * @inputs: array of RPFs in the pipeline (indexed by RPF index) * @output: WPF at the output of the pipeline * @bru: BRU entity, if present + * @hgo: HGO entity, if present + * @hgt: HGT entity, if present * @lif: LIF entity, if present * @uds: UDS entity, if present * @uds_input: entity at the input of the UDS, if the UDS is present @@ -100,6 +103,8 @@ struct vsp1_pipeline { struct vsp1_rwpf *inputs[VSP1_MAX_RPF]; struct vsp1_rwpf *output; struct vsp1_entity *bru; + struct vsp1_entity *hgo; + struct vsp1_entity *hgt; struct vsp1_entity *lif; struct vsp1_entity *uds; struct vsp1_entity *uds_input; diff --git a/drivers/media/platform/vsp1/vsp1_regs.h b/drivers/media/platform/vsp1/vsp1_regs.h index 47b1dee044fb..cd3e32af6e3b 100644 --- a/drivers/media/platform/vsp1/vsp1_regs.h +++ b/drivers/media/platform/vsp1/vsp1_regs.h @@ -328,8 +328,8 @@ #define VI6_DPR_ROUTE_RT_MASK (0x3f << 0) #define VI6_DPR_ROUTE_RT_SHIFT 0 -#define VI6_DPR_HGO_SMPPT 0x2050 -#define VI6_DPR_HGT_SMPPT 0x2054 +#define VI6_DPR_HGO_SMPPT 0x2054 +#define VI6_DPR_HGT_SMPPT 0x2058 #define VI6_DPR_SMPPT_TGW_MASK (7 << 8) #define VI6_DPR_SMPPT_TGW_SHIFT 8 #define VI6_DPR_SMPPT_PT_MASK (0x3f << 0) @@ -590,33 +590,55 @@ */ #define VI6_HGO_OFFSET 0x3000 +#define VI6_HGO_OFFSET_HOFFSET_SHIFT 16 +#define VI6_HGO_OFFSET_VOFFSET_SHIFT 0 #define VI6_HGO_SIZE 0x3004 +#define VI6_HGO_SIZE_HSIZE_SHIFT 16 +#define VI6_HGO_SIZE_VSIZE_SHIFT 0 #define VI6_HGO_MODE 0x3008 +#define VI6_HGO_MODE_STEP (1 << 10) +#define VI6_HGO_MODE_MAXRGB (1 << 7) +#define VI6_HGO_MODE_OFSB_R (1 << 6) +#define VI6_HGO_MODE_OFSB_G (1 << 5) +#define VI6_HGO_MODE_OFSB_B (1 << 4) +#define VI6_HGO_MODE_HRATIO_SHIFT 2 +#define VI6_HGO_MODE_VRATIO_SHIFT 0 #define VI6_HGO_LB_TH 0x300c #define VI6_HGO_LBn_H(n) (0x3010 + (n) * 8) #define VI6_HGO_LBn_V(n) (0x3014 + (n) * 8) -#define VI6_HGO_R_HISTO 0x3030 +#define VI6_HGO_R_HISTO(n) (0x3030 + (n) * 4) #define VI6_HGO_R_MAXMIN 0x3130 #define VI6_HGO_R_SUM 0x3134 #define VI6_HGO_R_LB_DET 0x3138 -#define VI6_HGO_G_HISTO 0x3140 +#define VI6_HGO_G_HISTO(n) (0x3140 + (n) * 4) #define VI6_HGO_G_MAXMIN 0x3240 #define VI6_HGO_G_SUM 0x3244 #define VI6_HGO_G_LB_DET 0x3248 -#define VI6_HGO_B_HISTO 0x3250 +#define VI6_HGO_B_HISTO(n) (0x3250 + (n) * 4) #define VI6_HGO_B_MAXMIN 0x3350 #define VI6_HGO_B_SUM 0x3354 #define VI6_HGO_B_LB_DET 0x3358 +#define VI6_HGO_EXT_HIST_ADDR 0x335c +#define VI6_HGO_EXT_HIST_DATA 0x3360 #define VI6_HGO_REGRST 0x33fc +#define VI6_HGO_REGRST_RCLEA (1 << 0) /* ----------------------------------------------------------------------------- * HGT Control Registers */ #define VI6_HGT_OFFSET 0x3400 +#define VI6_HGT_OFFSET_HOFFSET_SHIFT 16 +#define VI6_HGT_OFFSET_VOFFSET_SHIFT 0 #define VI6_HGT_SIZE 0x3404 +#define VI6_HGT_SIZE_HSIZE_SHIFT 16 +#define VI6_HGT_SIZE_VSIZE_SHIFT 0 #define VI6_HGT_MODE 0x3408 +#define VI6_HGT_MODE_HRATIO_SHIFT 2 +#define VI6_HGT_MODE_VRATIO_SHIFT 0 #define VI6_HGT_HUE_AREA(n) (0x340c + (n) * 4) +#define VI6_HGT_HUE_AREA_LOWER_SHIFT 16 +#define VI6_HGT_HUE_AREA_UPPER_SHIFT 0 #define VI6_HGT_LB_TH 0x3424 #define VI6_HGT_LBn_H(n) (0x3438 + (n) * 8) #define VI6_HGT_LBn_V(n) (0x342c + (n) * 8) @@ -625,6 +647,7 @@ #define VI6_HGT_SUM 0x3754 #define VI6_HGT_LB_DET 0x3758 #define VI6_HGT_REGRST 0x37fc +#define VI6_HGT_REGRST_RCLEA (1 << 0) /* ----------------------------------------------------------------------------- * LIF Control Registers diff --git a/drivers/media/platform/vsp1/vsp1_rpf.c b/drivers/media/platform/vsp1/vsp1_rpf.c index b2e34a800ffa..8feddd59cf8d 100644 --- a/drivers/media/platform/vsp1/vsp1_rpf.c +++ b/drivers/media/platform/vsp1/vsp1_rpf.c @@ -72,7 +72,8 @@ static void rpf_configure(struct vsp1_entity *entity, } if (params == VSP1_ENTITY_PARAMS_PARTITION) { - unsigned int offsets[2]; + struct vsp1_device *vsp1 = rpf->entity.vsp1; + struct vsp1_rwpf_memory mem = rpf->mem; struct v4l2_rect crop; /* @@ -105,7 +106,7 @@ static void rpf_configure(struct vsp1_entity *entity, * of the pipeline. */ output = vsp1_entity_get_pad_format(wpf, wpf->config, - RWPF_PAD_SOURCE); + RWPF_PAD_SINK); crop.width = pipe->partition.width * input_width / output->width; @@ -120,22 +121,30 @@ static void rpf_configure(struct vsp1_entity *entity, (crop.width << VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT) | (crop.height << VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT)); - offsets[0] = crop.top * format->plane_fmt[0].bytesperline - + crop.left * fmtinfo->bpp[0] / 8; - - if (format->num_planes > 1) - offsets[1] = crop.top * format->plane_fmt[1].bytesperline - + crop.left / fmtinfo->hsub - * fmtinfo->bpp[1] / 8; - else - offsets[1] = 0; - - vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_Y, - rpf->mem.addr[0] + offsets[0]); - vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_C0, - rpf->mem.addr[1] + offsets[1]); - vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_C1, - rpf->mem.addr[2] + offsets[1]); + mem.addr[0] += crop.top * format->plane_fmt[0].bytesperline + + crop.left * fmtinfo->bpp[0] / 8; + + if (format->num_planes > 1) { + unsigned int offset; + + offset = crop.top * format->plane_fmt[1].bytesperline + + crop.left / fmtinfo->hsub + * fmtinfo->bpp[1] / 8; + mem.addr[1] += offset; + mem.addr[2] += offset; + } + + /* + * On Gen3 hardware the SPUVS bit has no effect on 3-planar + * formats. Swap the U and V planes manually in that case. + */ + if (vsp1->info->gen == 3 && format->num_planes == 3 && + fmtinfo->swap_uv) + swap(mem.addr[1], mem.addr[2]); + + vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_Y, mem.addr[0]); + vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_C0, mem.addr[1]); + vsp1_rpf_write(rpf, dl, VI6_RPF_SRCM_ADDR_C1, mem.addr[2]); return; } @@ -186,7 +195,8 @@ static void rpf_configure(struct vsp1_entity *entity, (left << VI6_RPF_LOC_HCOORD_SHIFT) | (top << VI6_RPF_LOC_VCOORD_SHIFT)); - /* On Gen2 use the alpha channel (extended to 8 bits) when available or + /* + * On Gen2 use the alpha channel (extended to 8 bits) when available or * a fixed alpha value set through the V4L2_CID_ALPHA_COMPONENT control * otherwise. * @@ -216,7 +226,8 @@ static void rpf_configure(struct vsp1_entity *entity, u32 mult; if (fmtinfo->alpha) { - /* When the input contains an alpha channel enable the + /* + * When the input contains an alpha channel enable the * alpha multiplier. If the input is premultiplied we * need to multiply both the alpha channel and the pixel * components by the global alpha value to keep them @@ -231,7 +242,8 @@ static void rpf_configure(struct vsp1_entity *entity, VI6_RPF_MULT_ALPHA_P_MMD_RATIO : VI6_RPF_MULT_ALPHA_P_MMD_NONE); } else { - /* When the input doesn't contain an alpha channel the + /* + * When the input doesn't contain an alpha channel the * global alpha value is applied in the unpacking unit, * the alpha multiplier isn't needed and must be * disabled. diff --git a/drivers/media/platform/vsp1/vsp1_rwpf.c b/drivers/media/platform/vsp1/vsp1_rwpf.c index 04104ef28fb5..cfd8f1904fa6 100644 --- a/drivers/media/platform/vsp1/vsp1_rwpf.c +++ b/drivers/media/platform/vsp1/vsp1_rwpf.c @@ -86,7 +86,8 @@ static int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, format = vsp1_entity_get_pad_format(&rwpf->entity, config, fmt->pad); if (fmt->pad == RWPF_PAD_SOURCE) { - /* The RWPF performs format conversion but can't scale, only the + /* + * The RWPF performs format conversion but can't scale, only the * format code can be changed on the source pad. */ format->code = fmt->format.code; @@ -120,6 +121,11 @@ static int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, RWPF_PAD_SOURCE); *format = fmt->format; + if (rwpf->flip.rotate) { + format->width = fmt->format.height; + format->height = fmt->format.width; + } + done: mutex_unlock(&rwpf->entity.lock); return ret; @@ -205,7 +211,8 @@ static int vsp1_rwpf_set_selection(struct v4l2_subdev *subdev, format = vsp1_entity_get_pad_format(&rwpf->entity, config, RWPF_PAD_SINK); - /* Restrict the crop rectangle coordinates to multiples of 2 to avoid + /* + * Restrict the crop rectangle coordinates to multiples of 2 to avoid * shifting the color plane. */ if (format->code == MEDIA_BUS_FMT_AYUV8_1X32) { diff --git a/drivers/media/platform/vsp1/vsp1_rwpf.h b/drivers/media/platform/vsp1/vsp1_rwpf.h index 1c98aff3da5d..58215a7ab631 100644 --- a/drivers/media/platform/vsp1/vsp1_rwpf.h +++ b/drivers/media/platform/vsp1/vsp1_rwpf.h @@ -56,9 +56,14 @@ struct vsp1_rwpf { struct { spinlock_t lock; - struct v4l2_ctrl *ctrls[2]; + struct { + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *rotate; + } ctrls; unsigned int pending; unsigned int active; + bool rotate; } flip; struct vsp1_rwpf_memory mem; diff --git a/drivers/media/platform/vsp1/vsp1_sru.c b/drivers/media/platform/vsp1/vsp1_sru.c index b4e568a3b4ed..30142793dfcd 100644 --- a/drivers/media/platform/vsp1/vsp1_sru.c +++ b/drivers/media/platform/vsp1/vsp1_sru.c @@ -191,7 +191,8 @@ static void sru_try_format(struct vsp1_sru *sru, SRU_PAD_SINK); fmt->code = format->code; - /* We can upscale by 2 in both direction, but not independently. + /* + * We can upscale by 2 in both direction, but not independently. * Compare the input and output rectangles areas (avoiding * integer overflows on the output): if the requested output * area is larger than 1.5^2 the input area upscale by two, diff --git a/drivers/media/platform/vsp1/vsp1_uds.c b/drivers/media/platform/vsp1/vsp1_uds.c index da8f89a31ea4..4226403ad235 100644 --- a/drivers/media/platform/vsp1/vsp1_uds.c +++ b/drivers/media/platform/vsp1/vsp1_uds.c @@ -293,7 +293,8 @@ static void uds_configure(struct vsp1_entity *entity, dev_dbg(uds->entity.vsp1->dev, "hscale %u vscale %u\n", hscale, vscale); - /* Multi-tap scaling can't be enabled along with alpha scaling when + /* + * Multi-tap scaling can't be enabled along with alpha scaling when * scaling down with a factor lower than or equal to 1/2 in either * direction. */ diff --git a/drivers/media/platform/vsp1/vsp1_video.c b/drivers/media/platform/vsp1/vsp1_video.c index 3eaadbf7a876..eab3c3ea85d7 100644 --- a/drivers/media/platform/vsp1/vsp1_video.c +++ b/drivers/media/platform/vsp1/vsp1_video.c @@ -31,6 +31,8 @@ #include "vsp1_bru.h" #include "vsp1_dl.h" #include "vsp1_entity.h" +#include "vsp1_hgo.h" +#include "vsp1_hgt.h" #include "vsp1_pipe.h" #include "vsp1_rwpf.h" #include "vsp1_uds.h" @@ -103,7 +105,8 @@ static int __vsp1_video_try_format(struct vsp1_video *video, unsigned int height = pix->height; unsigned int i; - /* Backward compatibility: replace deprecated RGB formats by their XRGB + /* + * Backward compatibility: replace deprecated RGB formats by their XRGB * equivalent. This selects the format older userspace applications want * while still exposing the new format. */ @@ -114,7 +117,8 @@ static int __vsp1_video_try_format(struct vsp1_video *video, } } - /* Retrieve format information and select the default format if the + /* + * Retrieve format information and select the default format if the * requested format isn't supported. */ info = vsp1_get_format_info(video->vsp1, pix->pixelformat); @@ -140,7 +144,8 @@ static int __vsp1_video_try_format(struct vsp1_video *video, pix->height = clamp(height, VSP1_VIDEO_MIN_HEIGHT, VSP1_VIDEO_MAX_HEIGHT); - /* Compute and clamp the stride and image size. While not documented in + /* + * Compute and clamp the stride and image size. While not documented in * the datasheet, strides not aligned to a multiple of 128 bytes result * in image corruption. */ @@ -184,9 +189,13 @@ static void vsp1_video_pipeline_setup_partitions(struct vsp1_pipeline *pipe) struct vsp1_entity *entity; unsigned int div_size; + /* + * Partitions are computed on the size before rotation, use the format + * at the WPF sink. + */ format = vsp1_entity_get_pad_format(&pipe->output->entity, pipe->output->entity.config, - RWPF_PAD_SOURCE); + RWPF_PAD_SINK); div_size = format->width; /* Gen2 hardware doesn't require image partitioning. */ @@ -226,9 +235,13 @@ static struct v4l2_rect vsp1_video_partition(struct vsp1_pipeline *pipe, struct v4l2_rect partition; unsigned int modulus; + /* + * Partitions are computed on the size before rotation, use the format + * at the WPF sink. + */ format = vsp1_entity_get_pad_format(&pipe->output->entity, pipe->output->entity.config, - RWPF_PAD_SOURCE); + RWPF_PAD_SINK); /* A single partition simply processes the output size in full. */ if (pipe->partitions <= 1) { @@ -449,7 +462,8 @@ static void vsp1_video_pipeline_frame_end(struct vsp1_pipeline *pipe) state = pipe->state; pipe->state = VSP1_PIPELINE_STOPPED; - /* If a stop has been requested, mark the pipeline as stopped and + /* + * If a stop has been requested, mark the pipeline as stopped and * return. Otherwise restart the pipeline if ready. */ if (state == VSP1_PIPELINE_STOPPING) @@ -474,7 +488,12 @@ static int vsp1_video_pipeline_build_branch(struct vsp1_pipeline *pipe, if (ret < 0) return ret; - pad = media_entity_remote_pad(&input->entity.pads[RWPF_PAD_SOURCE]); + /* + * The main data path doesn't include the HGO or HGT, use + * vsp1_entity_remote_pad() to traverse the graph. + */ + + pad = vsp1_entity_remote_pad(&input->entity.pads[RWPF_PAD_SOURCE]); while (1) { if (pad == NULL) { @@ -491,7 +510,8 @@ static int vsp1_video_pipeline_build_branch(struct vsp1_pipeline *pipe, entity = to_vsp1_entity( media_entity_to_v4l2_subdev(pad->entity)); - /* A BRU is present in the pipeline, store the BRU input pad + /* + * A BRU is present in the pipeline, store the BRU input pad * number in the input RPF for use when configuring the RPF. */ if (entity->type == VSP1_ENTITY_BRU) { @@ -526,13 +546,9 @@ static int vsp1_video_pipeline_build_branch(struct vsp1_pipeline *pipe, : &input->entity; } - /* Follow the source link. The link setup operations ensure - * that the output fan-out can't be more than one, there is thus - * no need to verify here that only a single source link is - * activated. - */ + /* Follow the source link, ignoring any HGO or HGT. */ pad = &entity->pads[entity->source_pad]; - pad = media_entity_remote_pad(pad); + pad = vsp1_entity_remote_pad(pad); } /* The last entity must be the output WPF. */ @@ -587,6 +603,16 @@ static int vsp1_video_pipeline_build(struct vsp1_pipeline *pipe, pipe->lif = e; } else if (e->type == VSP1_ENTITY_BRU) { pipe->bru = e; + } else if (e->type == VSP1_ENTITY_HGO) { + struct vsp1_hgo *hgo = to_hgo(subdev); + + pipe->hgo = e; + hgo->histo.pipe = pipe; + } else if (e->type == VSP1_ENTITY_HGT) { + struct vsp1_hgt *hgt = to_hgt(subdev); + + pipe->hgt = e; + hgt->histo.pipe = pipe; } } @@ -596,7 +622,8 @@ static int vsp1_video_pipeline_build(struct vsp1_pipeline *pipe, if (pipe->num_inputs == 0 || !pipe->output) return -EPIPE; - /* Follow links downstream for each input and make sure the graph + /* + * Follow links downstream for each input and make sure the graph * contains no loop and that all branches end at the output WPF. */ for (i = 0; i < video->vsp1->info->rpf_count; ++i) { @@ -627,7 +654,8 @@ static struct vsp1_pipeline *vsp1_video_pipeline_get(struct vsp1_video *video) struct vsp1_pipeline *pipe; int ret; - /* Get a pipeline object for the video node. If a pipeline has already + /* + * Get a pipeline object for the video node. If a pipeline has already * been allocated just increment its reference count and return it. * Otherwise allocate a new pipeline and initialize it, it will be freed * when the last reference is released. @@ -767,7 +795,8 @@ static int vsp1_video_setup_pipeline(struct vsp1_pipeline *pipe) if (pipe->uds) { struct vsp1_uds *uds = to_uds(&pipe->uds->subdev); - /* If a BRU is present in the pipeline before the UDS, the alpha + /* + * If a BRU is present in the pipeline before the UDS, the alpha * component doesn't need to be scaled as the BRU output alpha * value is fixed to 255. Otherwise we need to scale the alpha * component only when available at the input RPF. @@ -783,7 +812,7 @@ static int vsp1_video_setup_pipeline(struct vsp1_pipeline *pipe) } list_for_each_entry(entity, &pipe->entities, list_pipe) { - vsp1_entity_route_setup(entity, pipe->dl); + vsp1_entity_route_setup(entity, pipe, pipe->dl); if (entity->ops->configure) entity->ops->configure(entity, pipe, pipe->dl, @@ -797,6 +826,7 @@ static int vsp1_video_start_streaming(struct vb2_queue *vq, unsigned int count) { struct vsp1_video *video = vb2_get_drv_priv(vq); struct vsp1_pipeline *pipe = video->rwpf->pipe; + bool start_pipeline = false; unsigned long flags; int ret; @@ -807,11 +837,23 @@ static int vsp1_video_start_streaming(struct vb2_queue *vq, unsigned int count) mutex_unlock(&pipe->lock); return ret; } + + start_pipeline = true; } pipe->stream_count++; mutex_unlock(&pipe->lock); + /* + * vsp1_pipeline_ready() is not sufficient to establish that all streams + * are prepared and the pipeline is configured, as multiple streams + * can race through streamon with buffers already queued; Therefore we + * don't even attempt to start the pipeline until the last stream has + * called through here. + */ + if (!start_pipeline) + return 0; + spin_lock_irqsave(&pipe->irqlock, flags); if (vsp1_pipeline_ready(pipe)) vsp1_video_pipeline_run(pipe); @@ -968,7 +1010,8 @@ vsp1_video_streamon(struct file *file, void *fh, enum v4l2_buf_type type) if (video->queue.owner && video->queue.owner != file->private_data) return -EBUSY; - /* Get a pipeline for the video node and start streaming on it. No link + /* + * Get a pipeline for the video node and start streaming on it. No link * touching an entity in the pipeline can be activated or deactivated * once streaming is started. */ @@ -988,7 +1031,8 @@ vsp1_video_streamon(struct file *file, void *fh, enum v4l2_buf_type type) mutex_unlock(&mdev->graph_mutex); - /* Verify that the configured format matches the output of the connected + /* + * Verify that the configured format matches the output of the connected * subdev. */ ret = vsp1_video_verify_format(video); @@ -1050,6 +1094,7 @@ static int vsp1_video_open(struct file *file) ret = vsp1_device_get(video->vsp1); if (ret < 0) { v4l2_fh_del(vfh); + v4l2_fh_exit(vfh); kfree(vfh); } diff --git a/drivers/media/platform/vsp1/vsp1_wpf.c b/drivers/media/platform/vsp1/vsp1_wpf.c index 7c48f81cd5c1..32df109b119f 100644 --- a/drivers/media/platform/vsp1/vsp1_wpf.c +++ b/drivers/media/platform/vsp1/vsp1_wpf.c @@ -43,32 +43,90 @@ static inline void vsp1_wpf_write(struct vsp1_rwpf *wpf, enum wpf_flip_ctrl { WPF_CTRL_VFLIP = 0, WPF_CTRL_HFLIP = 1, - WPF_CTRL_MAX, }; +static int vsp1_wpf_set_rotation(struct vsp1_rwpf *wpf, unsigned int rotation) +{ + struct vsp1_video *video = wpf->video; + struct v4l2_mbus_framefmt *sink_format; + struct v4l2_mbus_framefmt *source_format; + bool rotate; + int ret = 0; + + /* + * Only consider the 0°/180° from/to 90°/270° modifications, the rest + * is taken care of by the flipping configuration. + */ + rotate = rotation == 90 || rotation == 270; + if (rotate == wpf->flip.rotate) + return 0; + + /* Changing rotation isn't allowed when buffers are allocated. */ + mutex_lock(&video->lock); + + if (vb2_is_busy(&video->queue)) { + ret = -EBUSY; + goto done; + } + + sink_format = vsp1_entity_get_pad_format(&wpf->entity, + wpf->entity.config, + RWPF_PAD_SINK); + source_format = vsp1_entity_get_pad_format(&wpf->entity, + wpf->entity.config, + RWPF_PAD_SOURCE); + + mutex_lock(&wpf->entity.lock); + + if (rotate) { + source_format->width = sink_format->height; + source_format->height = sink_format->width; + } else { + source_format->width = sink_format->width; + source_format->height = sink_format->height; + } + + wpf->flip.rotate = rotate; + + mutex_unlock(&wpf->entity.lock); + +done: + mutex_unlock(&video->lock); + return ret; +} + static int vsp1_wpf_s_ctrl(struct v4l2_ctrl *ctrl) { struct vsp1_rwpf *wpf = container_of(ctrl->handler, struct vsp1_rwpf, ctrls); - unsigned int i; + unsigned int rotation; u32 flip = 0; + int ret; - switch (ctrl->id) { - case V4L2_CID_HFLIP: - case V4L2_CID_VFLIP: - for (i = 0; i < WPF_CTRL_MAX; ++i) { - if (wpf->flip.ctrls[i]) - flip |= wpf->flip.ctrls[i]->val ? BIT(i) : 0; - } + /* Update the rotation. */ + rotation = wpf->flip.ctrls.rotate ? wpf->flip.ctrls.rotate->val : 0; + ret = vsp1_wpf_set_rotation(wpf, rotation); + if (ret < 0) + return ret; - spin_lock_irq(&wpf->flip.lock); - wpf->flip.pending = flip; - spin_unlock_irq(&wpf->flip.lock); - break; + /* + * Compute the flip value resulting from all three controls, with + * rotation by 180° flipping the image in both directions. Store the + * result in the pending flip field for the next frame that will be + * processed. + */ + if (wpf->flip.ctrls.vflip->val) + flip |= BIT(WPF_CTRL_VFLIP); - default: - return -EINVAL; - } + if (wpf->flip.ctrls.hflip && wpf->flip.ctrls.hflip->val) + flip |= BIT(WPF_CTRL_HFLIP); + + if (rotation == 180 || rotation == 270) + flip ^= BIT(WPF_CTRL_VFLIP) | BIT(WPF_CTRL_HFLIP); + + spin_lock_irq(&wpf->flip.lock); + wpf->flip.pending = flip; + spin_unlock_irq(&wpf->flip.lock); return 0; } @@ -88,12 +146,14 @@ static int wpf_init_controls(struct vsp1_rwpf *wpf) /* Only WPF0 supports flipping. */ num_flip_ctrls = 0; } else if (vsp1->info->features & VSP1_HAS_WPF_HFLIP) { - /* When horizontal flip is supported the WPF implements two - * controls (horizontal flip and vertical flip). + /* + * When horizontal flip is supported the WPF implements three + * controls (horizontal flip, vertical flip and rotation). */ - num_flip_ctrls = 2; + num_flip_ctrls = 3; } else if (vsp1->info->features & VSP1_HAS_WPF_VFLIP) { - /* When only vertical flip is supported the WPF implements a + /* + * When only vertical flip is supported the WPF implements a * single control (vertical flip). */ num_flip_ctrls = 1; @@ -105,17 +165,19 @@ static int wpf_init_controls(struct vsp1_rwpf *wpf) vsp1_rwpf_init_ctrls(wpf, num_flip_ctrls); if (num_flip_ctrls >= 1) { - wpf->flip.ctrls[WPF_CTRL_VFLIP] = + wpf->flip.ctrls.vflip = v4l2_ctrl_new_std(&wpf->ctrls, &vsp1_wpf_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); } - if (num_flip_ctrls == 2) { - wpf->flip.ctrls[WPF_CTRL_HFLIP] = + if (num_flip_ctrls == 3) { + wpf->flip.ctrls.hflip = v4l2_ctrl_new_std(&wpf->ctrls, &vsp1_wpf_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); - - v4l2_ctrl_cluster(2, wpf->flip.ctrls); + wpf->flip.ctrls.rotate = + v4l2_ctrl_new_std(&wpf->ctrls, &vsp1_wpf_ctrl_ops, + V4L2_CID_ROTATE, 0, 270, 90, 0); + v4l2_ctrl_cluster(3, &wpf->flip.ctrls.vflip); } if (wpf->ctrls.error) { @@ -139,7 +201,8 @@ static int wpf_s_stream(struct v4l2_subdev *subdev, int enable) if (enable) return 0; - /* Write to registers directly when stopping the stream as there will be + /* + * Write to registers directly when stopping the stream as there will be * no pipeline run to apply the display list. */ vsp1_write(vsp1, VI6_WPF_IRQ_ENB(wpf->entity.index), 0); @@ -216,10 +279,11 @@ static void wpf_configure(struct vsp1_entity *entity, if (params == VSP1_ENTITY_PARAMS_PARTITION) { const struct v4l2_pix_format_mplane *format = &wpf->format; + const struct vsp1_format_info *fmtinfo = wpf->fmtinfo; struct vsp1_rwpf_memory mem = wpf->mem; unsigned int flip = wpf->flip.active; - unsigned int width = source_format->width; - unsigned int height = source_format->height; + unsigned int width = sink_format->width; + unsigned int height = sink_format->height; unsigned int offset; /* @@ -242,45 +306,86 @@ static void wpf_configure(struct vsp1_entity *entity, /* * Update the memory offsets based on flipping configuration. * The destination addresses point to the locations where the - * VSP starts writing to memory, which can be different corners - * of the image depending on vertical flipping. + * VSP starts writing to memory, which can be any corner of the + * image depending on the combination of flipping and rotation. */ - if (pipe->partitions > 1) { - const struct vsp1_format_info *fmtinfo = wpf->fmtinfo; - /* - * Horizontal flipping is handled through a line buffer - * and doesn't modify the start address, but still needs - * to be handled when image partitioning is in effect to - * order the partitions correctly. - */ - if (flip & BIT(WPF_CTRL_HFLIP)) - offset = format->width - pipe->partition.left - - pipe->partition.width; + /* + * First take the partition left coordinate into account. + * Compute the offset to order the partitions correctly on the + * output based on whether flipping is enabled. Consider + * horizontal flipping when rotation is disabled but vertical + * flipping when rotation is enabled, as rotating the image + * switches the horizontal and vertical directions. The offset + * is applied horizontally or vertically accordingly. + */ + if (flip & BIT(WPF_CTRL_HFLIP) && !wpf->flip.rotate) + offset = format->width - pipe->partition.left + - pipe->partition.width; + else if (flip & BIT(WPF_CTRL_VFLIP) && wpf->flip.rotate) + offset = format->height - pipe->partition.left + - pipe->partition.width; + else + offset = pipe->partition.left; + + for (i = 0; i < format->num_planes; ++i) { + unsigned int hsub = i > 0 ? fmtinfo->hsub : 1; + unsigned int vsub = i > 0 ? fmtinfo->vsub : 1; + + if (wpf->flip.rotate) + mem.addr[i] += offset / vsub + * format->plane_fmt[i].bytesperline; else - offset = pipe->partition.left; - - mem.addr[0] += offset * fmtinfo->bpp[0] / 8; - if (format->num_planes > 1) { - mem.addr[1] += offset / fmtinfo->hsub - * fmtinfo->bpp[1] / 8; - mem.addr[2] += offset / fmtinfo->hsub - * fmtinfo->bpp[2] / 8; - } + mem.addr[i] += offset / hsub + * fmtinfo->bpp[i] / 8; } if (flip & BIT(WPF_CTRL_VFLIP)) { - mem.addr[0] += (format->height - 1) + /* + * When rotating the output (after rotation) image + * height is equal to the partition width (before + * rotation). Otherwise it is equal to the output + * image height. + */ + if (wpf->flip.rotate) + height = pipe->partition.width; + else + height = format->height; + + mem.addr[0] += (height - 1) * format->plane_fmt[0].bytesperline; if (format->num_planes > 1) { - offset = (format->height / wpf->fmtinfo->vsub - 1) + offset = (height / fmtinfo->vsub - 1) * format->plane_fmt[1].bytesperline; mem.addr[1] += offset; mem.addr[2] += offset; } } + if (wpf->flip.rotate && !(flip & BIT(WPF_CTRL_HFLIP))) { + unsigned int hoffset = max(0, (int)format->width - 16); + + /* + * Compute the output coordinate. The partition + * horizontal (left) offset becomes a vertical offset. + */ + for (i = 0; i < format->num_planes; ++i) { + unsigned int hsub = i > 0 ? fmtinfo->hsub : 1; + + mem.addr[i] += hoffset / hsub + * fmtinfo->bpp[i] / 8; + } + } + + /* + * On Gen3 hardware the SPUVS bit has no effect on 3-planar + * formats. Swap the U and V planes manually in that case. + */ + if (vsp1->info->gen == 3 && format->num_planes == 3 && + fmtinfo->swap_uv) + swap(mem.addr[1], mem.addr[2]); + vsp1_wpf_write(wpf, dl, VI6_WPF_DSTM_ADDR_Y, mem.addr[0]); vsp1_wpf_write(wpf, dl, VI6_WPF_DSTM_ADDR_C0, mem.addr[1]); vsp1_wpf_write(wpf, dl, VI6_WPF_DSTM_ADDR_C1, mem.addr[2]); @@ -294,6 +399,9 @@ static void wpf_configure(struct vsp1_entity *entity, outfmt = fmtinfo->hwfmt << VI6_WPF_OUTFMT_WRFMT_SHIFT; + if (wpf->flip.rotate) + outfmt |= VI6_WPF_OUTFMT_ROT; + if (fmtinfo->alpha) outfmt |= VI6_WPF_OUTFMT_PXA; if (fmtinfo->swap_yc) @@ -327,7 +435,8 @@ static void wpf_configure(struct vsp1_entity *entity, vsp1_dl_list_write(dl, VI6_WPF_WRBCK_CTRL, 0); - /* Sources. If the pipeline has a single input and BRU is not used, + /* + * Sources. If the pipeline has a single input and BRU is not used, * configure it as the master layer. Otherwise configure all * inputs as sub-layers and select the virtual RPF as the master * layer. @@ -354,9 +463,18 @@ static void wpf_configure(struct vsp1_entity *entity, VI6_WFP_IRQ_ENB_DFEE); } +static unsigned int wpf_max_width(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe) +{ + struct vsp1_rwpf *wpf = to_rwpf(&entity->subdev); + + return wpf->flip.rotate ? 256 : wpf->max_width; +} + static const struct vsp1_entity_operations wpf_entity_ops = { .destroy = vsp1_wpf_destroy, .configure = wpf_configure, + .max_width = wpf_max_width, }; /* ----------------------------------------------------------------------------- |