#include <linux/minmax.h>
#include <linux/pm_runtime.h>
#include <linux/string.h>
#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <media/v4l2-subdev.h>
#include "mali-c55-common.h"
#include "mali-c55-registers.h"
#define MALI_C55_TPG_SRC_PAD 0
#define MALI_C55_TPG_FIXED_HBLANK 0x20
#define MALI_C55_TPG_DEFAULT_MIN_VBLANK 66
#define MALI_C55_TPG_DEFAULT_DEF_VBLANK 626
#define MALI_C55_TPG_MAX_VBLANK 0xffff
#define MALI_C55_TPG_PIXEL_RATE 100000000
static const char * const mali_c55_tpg_test_pattern_menu[] = {
"Flat field",
"Horizontal gradient",
"Vertical gradient",
"Vertical bars",
"Arbitrary rectangle",
"White frame on black field"
};
static const u32 mali_c55_tpg_mbus_codes[] = {
MEDIA_BUS_FMT_SRGGB20_1X20,
MEDIA_BUS_FMT_RGB202020_1X60,
};
static void mali_c55_tpg_update_vblank(struct mali_c55_tpg *tpg,
struct v4l2_mbus_framefmt *format)
{
unsigned int def_vblank;
unsigned int min_vblank;
unsigned int hts;
int tgt_fps;
hts = format->width + MALI_C55_TPG_FIXED_HBLANK;
min_vblank = 15 + (120501 / hts);
tgt_fps = MALI_C55_TPG_PIXEL_RATE / hts / (format->height + min_vblank);
if (tgt_fps < 5)
def_vblank = min_vblank;
else
def_vblank = (MALI_C55_TPG_PIXEL_RATE / hts
/ max(rounddown(tgt_fps, 15), 5)) - format->height;
def_vblank = ALIGN_DOWN(def_vblank, 2);
__v4l2_ctrl_modify_range(tpg->ctrls.vblank, min_vblank,
MALI_C55_TPG_MAX_VBLANK, 1, def_vblank);
__v4l2_ctrl_s_ctrl(tpg->ctrls.vblank, def_vblank);
}
static int mali_c55_tpg_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct mali_c55_tpg *tpg = container_of(ctrl->handler,
struct mali_c55_tpg,
ctrls.handler);
struct mali_c55 *mali_c55 = container_of(tpg, struct mali_c55, tpg);
int ret = 0;
if (!pm_runtime_get_if_in_use(mali_c55->dev))
return 0;
switch (ctrl->id) {
case V4L2_CID_TEST_PATTERN:
mali_c55_ctx_write(mali_c55,
MALI_C55_REG_TEST_GEN_CH0_PATTERN_TYPE,
ctrl->val);
break;
case V4L2_CID_VBLANK:
mali_c55_update_bits(mali_c55, MALI_C55_REG_BLANKING,
MALI_C55_REG_VBLANK_MASK,
MALI_C55_VBLANK(ctrl->val));
break;
default:
ret = -EINVAL;
break;
}
pm_runtime_put_autosuspend(mali_c55->dev);
return ret;
}
static const struct v4l2_ctrl_ops mali_c55_tpg_ctrl_ops = {
.s_ctrl = &mali_c55_tpg_s_ctrl,
};
static void mali_c55_tpg_configure(struct mali_c55_tpg *tpg,
struct v4l2_subdev_state *state)
{
struct v4l2_mbus_framefmt *fmt;
u32 test_pattern_format;
mali_c55_update_bits(tpg->mali_c55, MALI_C55_REG_BLANKING,
MALI_C55_REG_HBLANK_MASK,
MALI_C55_TPG_FIXED_HBLANK);
mali_c55_update_bits(tpg->mali_c55, MALI_C55_REG_GEN_VIDEO,
MALI_C55_REG_GEN_VIDEO_MULTI_MASK,
MALI_C55_REG_GEN_VIDEO_MULTI_MASK);
fmt = v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD);
test_pattern_format = fmt->code == MEDIA_BUS_FMT_RGB202020_1X60 ?
0x01 : 0x0;
mali_c55_ctx_update_bits(tpg->mali_c55, MALI_C55_REG_TPG_CH0,
MALI_C55_TEST_PATTERN_RGB_MASK,
MALI_C55_TEST_PATTERN_RGB(test_pattern_format));
}
static int mali_c55_tpg_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_mbus_code_enum *code)
{
if (code->index >= ARRAY_SIZE(mali_c55_tpg_mbus_codes))
return -EINVAL;
code->code = mali_c55_tpg_mbus_codes[code->index];
return 0;
}
static int mali_c55_tpg_enum_frame_size(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_frame_size_enum *fse)
{
unsigned int i;
if (fse->index > 0)
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(mali_c55_tpg_mbus_codes); i++) {
if (fse->code == mali_c55_tpg_mbus_codes[i])
break;
}
if (i == ARRAY_SIZE(mali_c55_tpg_mbus_codes))
return -EINVAL;
fse->min_width = MALI_C55_MIN_WIDTH;
fse->max_width = MALI_C55_MAX_WIDTH;
fse->min_height = MALI_C55_MIN_HEIGHT;
fse->max_height = MALI_C55_MAX_HEIGHT;
return 0;
}
static int mali_c55_tpg_set_fmt(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state,
struct v4l2_subdev_format *format)
{
struct mali_c55_tpg *tpg = container_of(sd, struct mali_c55_tpg, sd);
struct v4l2_mbus_framefmt *fmt;
unsigned int i;
fmt = v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD);
fmt->code = format->format.code;
for (i = 0; i < ARRAY_SIZE(mali_c55_tpg_mbus_codes); i++) {
if (fmt->code == mali_c55_tpg_mbus_codes[i])
break;
}
if (i == ARRAY_SIZE(mali_c55_tpg_mbus_codes))
fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
fmt->width = clamp(format->format.width, MALI_C55_MIN_WIDTH,
MALI_C55_MAX_WIDTH);
fmt->height = clamp(format->format.height, MALI_C55_MIN_HEIGHT,
MALI_C55_MAX_HEIGHT);
format->format = *fmt;
if (format->which == V4L2_SUBDEV_FORMAT_TRY)
return 0;
mali_c55_tpg_update_vblank(tpg, fmt);
return 0;
}
static int mali_c55_tpg_enable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state, u32 pad,
u64 streams_mask)
{
struct mali_c55_tpg *tpg = container_of(sd, struct mali_c55_tpg, sd);
struct mali_c55 *mali_c55 = container_of(tpg, struct mali_c55, tpg);
mali_c55_tpg_configure(tpg, state);
__v4l2_ctrl_handler_setup(sd->ctrl_handler);
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_TPG_CH0,
MALI_C55_TEST_PATTERN_ON_OFF,
MALI_C55_TEST_PATTERN_ON_OFF);
mali_c55_update_bits(mali_c55, MALI_C55_REG_GEN_VIDEO,
MALI_C55_REG_GEN_VIDEO_ON_MASK,
MALI_C55_REG_GEN_VIDEO_ON_MASK);
return 0;
}
static int mali_c55_tpg_disable_streams(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state, u32 pad,
u64 streams_mask)
{
struct mali_c55_tpg *tpg = container_of(sd, struct mali_c55_tpg, sd);
struct mali_c55 *mali_c55 = container_of(tpg, struct mali_c55, tpg);
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_TPG_CH0,
MALI_C55_TEST_PATTERN_ON_OFF, 0x00);
mali_c55_update_bits(mali_c55, MALI_C55_REG_GEN_VIDEO,
MALI_C55_REG_GEN_VIDEO_ON_MASK, 0x00);
return 0;
}
static const struct v4l2_subdev_pad_ops mali_c55_tpg_pad_ops = {
.enum_mbus_code = mali_c55_tpg_enum_mbus_code,
.enum_frame_size = mali_c55_tpg_enum_frame_size,
.get_fmt = v4l2_subdev_get_fmt,
.set_fmt = mali_c55_tpg_set_fmt,
.enable_streams = mali_c55_tpg_enable_streams,
.disable_streams = mali_c55_tpg_disable_streams,
};
static const struct v4l2_subdev_core_ops mali_c55_isp_core_ops = {
.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
.unsubscribe_event = v4l2_event_subdev_unsubscribe,
};
static const struct v4l2_subdev_ops mali_c55_tpg_ops = {
.core = &mali_c55_isp_core_ops,
.pad = &mali_c55_tpg_pad_ops,
};
static int mali_c55_tpg_init_state(struct v4l2_subdev *sd,
struct v4l2_subdev_state *state)
{
struct v4l2_mbus_framefmt *fmt =
v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD);
fmt->width = MALI_C55_DEFAULT_WIDTH;
fmt->height = MALI_C55_DEFAULT_HEIGHT;
fmt->field = V4L2_FIELD_NONE;
fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
fmt->colorspace = V4L2_COLORSPACE_RAW;
fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(false,
fmt->colorspace,
fmt->ycbcr_enc);
return 0;
}
static const struct v4l2_subdev_internal_ops mali_c55_tpg_internal_ops = {
.init_state = mali_c55_tpg_init_state,
};
static int mali_c55_tpg_init_controls(struct mali_c55 *mali_c55)
{
struct mali_c55_tpg_ctrls *ctrls = &mali_c55->tpg.ctrls;
struct v4l2_ctrl *pixel_rate;
struct v4l2_ctrl *hblank;
int ret;
ret = v4l2_ctrl_handler_init(&ctrls->handler, 4);
if (ret)
return ret;
v4l2_ctrl_new_std_menu_items(&ctrls->handler, &mali_c55_tpg_ctrl_ops,
V4L2_CID_TEST_PATTERN,
ARRAY_SIZE(mali_c55_tpg_test_pattern_menu) - 1,
0, 3, mali_c55_tpg_test_pattern_menu);
hblank = v4l2_ctrl_new_std(&ctrls->handler, &mali_c55_tpg_ctrl_ops,
V4L2_CID_HBLANK, MALI_C55_TPG_FIXED_HBLANK,
MALI_C55_TPG_FIXED_HBLANK, 1,
MALI_C55_TPG_FIXED_HBLANK);
if (hblank)
hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
ctrls->vblank = v4l2_ctrl_new_std(&ctrls->handler,
&mali_c55_tpg_ctrl_ops,
V4L2_CID_VBLANK,
MALI_C55_TPG_DEFAULT_MIN_VBLANK,
MALI_C55_TPG_MAX_VBLANK, 1,
MALI_C55_TPG_DEFAULT_DEF_VBLANK);
pixel_rate = v4l2_ctrl_new_std(&ctrls->handler, &mali_c55_tpg_ctrl_ops,
V4L2_CID_PIXEL_RATE,
MALI_C55_TPG_PIXEL_RATE,
MALI_C55_TPG_PIXEL_RATE, 1,
MALI_C55_TPG_PIXEL_RATE);
if (pixel_rate)
pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
if (ctrls->handler.error) {
dev_err(mali_c55->dev, "Error during v4l2 controls init\n");
v4l2_ctrl_handler_free(&ctrls->handler);
return ctrls->handler.error;
}
mali_c55->tpg.sd.ctrl_handler = &ctrls->handler;
mali_c55->tpg.sd.state_lock = ctrls->handler.lock;
return 0;
}
int mali_c55_register_tpg(struct mali_c55 *mali_c55)
{
struct mali_c55_tpg *tpg = &mali_c55->tpg;
struct v4l2_subdev *sd = &tpg->sd;
struct media_pad *pad = &tpg->pad;
int ret;
v4l2_subdev_init(sd, &mali_c55_tpg_ops);
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
sd->internal_ops = &mali_c55_tpg_internal_ops;
strscpy(sd->name, MALI_C55_DRIVER_NAME " tpg", sizeof(sd->name));
pad->flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&sd->entity, 1, pad);
if (ret) {
dev_err(mali_c55->dev,
"Failed to initialize media entity pads\n");
return ret;
}
ret = mali_c55_tpg_init_controls(mali_c55);
if (ret) {
dev_err(mali_c55->dev,
"Error initialising controls\n");
goto err_cleanup_media_entity;
}
ret = v4l2_subdev_init_finalize(sd);
if (ret)
goto err_free_ctrl_handler;
ret = v4l2_device_register_subdev(&mali_c55->v4l2_dev, sd);
if (ret) {
dev_err(mali_c55->dev, "Failed to register tpg subdev\n");
goto err_cleanup_subdev;
}
mali_c55_ctx_write(mali_c55, MALI_C55_REG_TPG_R_BACKGROUND,
MALI_C55_TPG_BACKGROUND_MAX);
mali_c55_ctx_write(mali_c55, MALI_C55_REG_TPG_G_BACKGROUND,
MALI_C55_TPG_BACKGROUND_MAX);
mali_c55_ctx_write(mali_c55, MALI_C55_REG_TPG_B_BACKGROUND,
MALI_C55_TPG_BACKGROUND_MAX);
tpg->mali_c55 = mali_c55;
return 0;
err_cleanup_subdev:
v4l2_subdev_cleanup(sd);
err_free_ctrl_handler:
v4l2_ctrl_handler_free(&tpg->ctrls.handler);
err_cleanup_media_entity:
media_entity_cleanup(&sd->entity);
return ret;
}
void mali_c55_unregister_tpg(struct mali_c55 *mali_c55)
{
struct mali_c55_tpg *tpg = &mali_c55->tpg;
if (!tpg->mali_c55)
return;
v4l2_device_unregister_subdev(&tpg->sd);
v4l2_ctrl_handler_free(&tpg->ctrls.handler);
v4l2_subdev_cleanup(&tpg->sd);
media_entity_cleanup(&tpg->sd.entity);
}