// SPDX-License-Identifier: GPL-2.0+
/*
* vsp1_hgo.c -- R-Car VSP1 Histogram Generator 1D
*
* Copyright (C) 2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart ([email protected])
*/
#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_body *dlb, u32 reg, u32 data)
{
vsp1_dl_body_write(dlb, 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_stream(struct vsp1_entity *entity,
struct v4l2_subdev_state *state,
struct vsp1_pipeline *pipe,
struct vsp1_dl_list *dl,
struct vsp1_dl_body *dlb)
{
struct vsp1_hgo *hgo = to_hgo(&entity->subdev);
struct v4l2_rect *compose;
struct v4l2_rect *crop;
unsigned int hratio;
unsigned int vratio;
crop = v4l2_subdev_state_get_crop(state, HISTO_PAD_SINK);
compose = v4l2_subdev_state_get_compose(state, HISTO_PAD_SINK);
vsp1_hgo_write(hgo, dlb, VI6_HGO_REGRST, VI6_HGO_REGRST_RCLEA);
vsp1_hgo_write(hgo, dlb, VI6_HGO_OFFSET,
(crop->left << VI6_HGO_OFFSET_HOFFSET_SHIFT) |
(crop->top << VI6_HGO_OFFSET_VOFFSET_SHIFT));
vsp1_hgo_write(hgo, dlb, 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, dlb, 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_stream = hgo_configure_stream,
.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 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);
}
/* 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;
return hgo;
}