162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Driver for Renesas RZ/G2L CRU
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2022 Renesas Electronics Corp.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include "rzg2l-cru.h"
962306a36Sopenharmony_ci
1062306a36Sopenharmony_cistruct rzg2l_cru_ip_format {
1162306a36Sopenharmony_ci	u32 code;
1262306a36Sopenharmony_ci	unsigned int datatype;
1362306a36Sopenharmony_ci	unsigned int bpp;
1462306a36Sopenharmony_ci};
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistatic const struct rzg2l_cru_ip_format rzg2l_cru_ip_formats[] = {
1762306a36Sopenharmony_ci	{ .code = MEDIA_BUS_FMT_UYVY8_1X16,	.datatype = 0x1e, .bpp = 16 },
1862306a36Sopenharmony_ci};
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cienum rzg2l_csi2_pads {
2162306a36Sopenharmony_ci	RZG2L_CRU_IP_SINK = 0,
2262306a36Sopenharmony_ci	RZG2L_CRU_IP_SOURCE,
2362306a36Sopenharmony_ci};
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic const struct rzg2l_cru_ip_format *rzg2l_cru_ip_code_to_fmt(unsigned int code)
2662306a36Sopenharmony_ci{
2762306a36Sopenharmony_ci	unsigned int i;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(rzg2l_cru_ip_formats); i++)
3062306a36Sopenharmony_ci		if (rzg2l_cru_ip_formats[i].code == code)
3162306a36Sopenharmony_ci			return &rzg2l_cru_ip_formats[i];
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	return NULL;
3462306a36Sopenharmony_ci}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistruct v4l2_mbus_framefmt *rzg2l_cru_ip_get_src_fmt(struct rzg2l_cru_dev *cru)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	struct v4l2_subdev_state *state;
3962306a36Sopenharmony_ci	struct v4l2_mbus_framefmt *fmt;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	state = v4l2_subdev_lock_and_get_active_state(&cru->ip.subdev);
4262306a36Sopenharmony_ci	fmt = v4l2_subdev_get_pad_format(&cru->ip.subdev, state, 1);
4362306a36Sopenharmony_ci	v4l2_subdev_unlock_state(state);
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	return fmt;
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic int rzg2l_cru_ip_s_stream(struct v4l2_subdev *sd, int enable)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	struct rzg2l_cru_dev *cru;
5162306a36Sopenharmony_ci	int s_stream_ret = 0;
5262306a36Sopenharmony_ci	int ret;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	cru = v4l2_get_subdevdata(sd);
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	if (!enable) {
5762306a36Sopenharmony_ci		ret = v4l2_subdev_call(cru->ip.remote, video, s_stream, enable);
5862306a36Sopenharmony_ci		if (ret)
5962306a36Sopenharmony_ci			s_stream_ret = ret;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci		ret = v4l2_subdev_call(cru->ip.remote, video, post_streamoff);
6262306a36Sopenharmony_ci		if (ret == -ENOIOCTLCMD)
6362306a36Sopenharmony_ci			ret = 0;
6462306a36Sopenharmony_ci		if (ret && !s_stream_ret)
6562306a36Sopenharmony_ci			s_stream_ret = ret;
6662306a36Sopenharmony_ci		rzg2l_cru_stop_image_processing(cru);
6762306a36Sopenharmony_ci	} else {
6862306a36Sopenharmony_ci		ret = v4l2_subdev_call(cru->ip.remote, video, pre_streamon, 0);
6962306a36Sopenharmony_ci		if (ret == -ENOIOCTLCMD)
7062306a36Sopenharmony_ci			ret = 0;
7162306a36Sopenharmony_ci		if (ret)
7262306a36Sopenharmony_ci			return ret;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci		ret = rzg2l_cru_start_image_processing(cru);
7562306a36Sopenharmony_ci		if (ret) {
7662306a36Sopenharmony_ci			v4l2_subdev_call(cru->ip.remote, video, post_streamoff);
7762306a36Sopenharmony_ci			return ret;
7862306a36Sopenharmony_ci		}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci		rzg2l_cru_vclk_unprepare(cru);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci		ret = v4l2_subdev_call(cru->ip.remote, video, s_stream, enable);
8362306a36Sopenharmony_ci		if (ret == -ENOIOCTLCMD)
8462306a36Sopenharmony_ci			ret = 0;
8562306a36Sopenharmony_ci		if (!ret) {
8662306a36Sopenharmony_ci			ret = rzg2l_cru_vclk_prepare(cru);
8762306a36Sopenharmony_ci			if (!ret)
8862306a36Sopenharmony_ci				return 0;
8962306a36Sopenharmony_ci		} else {
9062306a36Sopenharmony_ci			/* enable back vclk so that s_stream in error path disables it */
9162306a36Sopenharmony_ci			if (rzg2l_cru_vclk_prepare(cru))
9262306a36Sopenharmony_ci				dev_err(cru->dev, "Failed to enable vclk\n");
9362306a36Sopenharmony_ci		}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci		s_stream_ret = ret;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci		v4l2_subdev_call(cru->ip.remote, video, post_streamoff);
9862306a36Sopenharmony_ci		rzg2l_cru_stop_image_processing(cru);
9962306a36Sopenharmony_ci	}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	return s_stream_ret;
10262306a36Sopenharmony_ci}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_cistatic int rzg2l_cru_ip_set_format(struct v4l2_subdev *sd,
10562306a36Sopenharmony_ci				   struct v4l2_subdev_state *state,
10662306a36Sopenharmony_ci				   struct v4l2_subdev_format *fmt)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	struct v4l2_mbus_framefmt *src_format;
10962306a36Sopenharmony_ci	struct v4l2_mbus_framefmt *sink_format;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CRU_IP_SOURCE);
11262306a36Sopenharmony_ci	if (fmt->pad == RZG2L_CRU_IP_SOURCE) {
11362306a36Sopenharmony_ci		fmt->format = *src_format;
11462306a36Sopenharmony_ci		return 0;
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	sink_format = v4l2_subdev_get_pad_format(sd, state, fmt->pad);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (!rzg2l_cru_ip_code_to_fmt(fmt->format.code))
12062306a36Sopenharmony_ci		sink_format->code = rzg2l_cru_ip_formats[0].code;
12162306a36Sopenharmony_ci	else
12262306a36Sopenharmony_ci		sink_format->code = fmt->format.code;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	sink_format->field = V4L2_FIELD_NONE;
12562306a36Sopenharmony_ci	sink_format->colorspace = fmt->format.colorspace;
12662306a36Sopenharmony_ci	sink_format->xfer_func = fmt->format.xfer_func;
12762306a36Sopenharmony_ci	sink_format->ycbcr_enc = fmt->format.ycbcr_enc;
12862306a36Sopenharmony_ci	sink_format->quantization = fmt->format.quantization;
12962306a36Sopenharmony_ci	sink_format->width = clamp_t(u32, fmt->format.width,
13062306a36Sopenharmony_ci				     RZG2L_CRU_MIN_INPUT_WIDTH, RZG2L_CRU_MAX_INPUT_WIDTH);
13162306a36Sopenharmony_ci	sink_format->height = clamp_t(u32, fmt->format.height,
13262306a36Sopenharmony_ci				      RZG2L_CRU_MIN_INPUT_HEIGHT, RZG2L_CRU_MAX_INPUT_HEIGHT);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	fmt->format = *sink_format;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	/* propagate format to source pad */
13762306a36Sopenharmony_ci	*src_format = *sink_format;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	return 0;
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic int rzg2l_cru_ip_enum_mbus_code(struct v4l2_subdev *sd,
14362306a36Sopenharmony_ci				       struct v4l2_subdev_state *state,
14462306a36Sopenharmony_ci				       struct v4l2_subdev_mbus_code_enum *code)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	if (code->index >= ARRAY_SIZE(rzg2l_cru_ip_formats))
14762306a36Sopenharmony_ci		return -EINVAL;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	code->code = rzg2l_cru_ip_formats[code->index].code;
15062306a36Sopenharmony_ci	return 0;
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_cistatic int rzg2l_cru_ip_enum_frame_size(struct v4l2_subdev *sd,
15462306a36Sopenharmony_ci					struct v4l2_subdev_state *state,
15562306a36Sopenharmony_ci					struct v4l2_subdev_frame_size_enum *fse)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	if (fse->index != 0)
15862306a36Sopenharmony_ci		return -EINVAL;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	if (fse->code != MEDIA_BUS_FMT_UYVY8_1X16)
16162306a36Sopenharmony_ci		return -EINVAL;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	fse->min_width = RZG2L_CRU_MIN_INPUT_WIDTH;
16462306a36Sopenharmony_ci	fse->min_height = RZG2L_CRU_MIN_INPUT_HEIGHT;
16562306a36Sopenharmony_ci	fse->max_width = RZG2L_CRU_MAX_INPUT_WIDTH;
16662306a36Sopenharmony_ci	fse->max_height = RZG2L_CRU_MAX_INPUT_HEIGHT;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	return 0;
16962306a36Sopenharmony_ci}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_cistatic int rzg2l_cru_ip_init_config(struct v4l2_subdev *sd,
17262306a36Sopenharmony_ci				    struct v4l2_subdev_state *sd_state)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	struct v4l2_subdev_format fmt = { .pad = RZG2L_CRU_IP_SINK, };
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	fmt.format.width = RZG2L_CRU_MIN_INPUT_WIDTH;
17762306a36Sopenharmony_ci	fmt.format.height = RZG2L_CRU_MIN_INPUT_HEIGHT;
17862306a36Sopenharmony_ci	fmt.format.field = V4L2_FIELD_NONE;
17962306a36Sopenharmony_ci	fmt.format.code = MEDIA_BUS_FMT_UYVY8_1X16;
18062306a36Sopenharmony_ci	fmt.format.colorspace = V4L2_COLORSPACE_SRGB;
18162306a36Sopenharmony_ci	fmt.format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
18262306a36Sopenharmony_ci	fmt.format.quantization = V4L2_QUANTIZATION_DEFAULT;
18362306a36Sopenharmony_ci	fmt.format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	return rzg2l_cru_ip_set_format(sd, sd_state, &fmt);
18662306a36Sopenharmony_ci}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_cistatic const struct v4l2_subdev_video_ops rzg2l_cru_ip_video_ops = {
18962306a36Sopenharmony_ci	.s_stream = rzg2l_cru_ip_s_stream,
19062306a36Sopenharmony_ci};
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic const struct v4l2_subdev_pad_ops rzg2l_cru_ip_pad_ops = {
19362306a36Sopenharmony_ci	.enum_mbus_code = rzg2l_cru_ip_enum_mbus_code,
19462306a36Sopenharmony_ci	.enum_frame_size = rzg2l_cru_ip_enum_frame_size,
19562306a36Sopenharmony_ci	.init_cfg = rzg2l_cru_ip_init_config,
19662306a36Sopenharmony_ci	.get_fmt = v4l2_subdev_get_fmt,
19762306a36Sopenharmony_ci	.set_fmt = rzg2l_cru_ip_set_format,
19862306a36Sopenharmony_ci};
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic const struct v4l2_subdev_ops rzg2l_cru_ip_subdev_ops = {
20162306a36Sopenharmony_ci	.video = &rzg2l_cru_ip_video_ops,
20262306a36Sopenharmony_ci	.pad = &rzg2l_cru_ip_pad_ops,
20362306a36Sopenharmony_ci};
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_cistatic const struct media_entity_operations rzg2l_cru_ip_entity_ops = {
20662306a36Sopenharmony_ci	.link_validate = v4l2_subdev_link_validate,
20762306a36Sopenharmony_ci};
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ciint rzg2l_cru_ip_subdev_register(struct rzg2l_cru_dev *cru)
21062306a36Sopenharmony_ci{
21162306a36Sopenharmony_ci	struct rzg2l_cru_ip *ip = &cru->ip;
21262306a36Sopenharmony_ci	int ret;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	ip->subdev.dev = cru->dev;
21562306a36Sopenharmony_ci	v4l2_subdev_init(&ip->subdev, &rzg2l_cru_ip_subdev_ops);
21662306a36Sopenharmony_ci	v4l2_set_subdevdata(&ip->subdev, cru);
21762306a36Sopenharmony_ci	snprintf(ip->subdev.name, sizeof(ip->subdev.name),
21862306a36Sopenharmony_ci		 "cru-ip-%s", dev_name(cru->dev));
21962306a36Sopenharmony_ci	ip->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	ip->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
22262306a36Sopenharmony_ci	ip->subdev.entity.ops = &rzg2l_cru_ip_entity_ops;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	ip->pads[0].flags = MEDIA_PAD_FL_SINK;
22562306a36Sopenharmony_ci	ip->pads[1].flags = MEDIA_PAD_FL_SOURCE;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	ret = media_entity_pads_init(&ip->subdev.entity, 2, ip->pads);
22862306a36Sopenharmony_ci	if (ret)
22962306a36Sopenharmony_ci		return ret;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	ret = v4l2_subdev_init_finalize(&ip->subdev);
23262306a36Sopenharmony_ci	if (ret < 0)
23362306a36Sopenharmony_ci		goto entity_cleanup;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	ret = v4l2_device_register_subdev(&cru->v4l2_dev, &ip->subdev);
23662306a36Sopenharmony_ci	if (ret < 0)
23762306a36Sopenharmony_ci		goto error_subdev;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	return 0;
24062306a36Sopenharmony_cierror_subdev:
24162306a36Sopenharmony_ci	v4l2_subdev_cleanup(&ip->subdev);
24262306a36Sopenharmony_cientity_cleanup:
24362306a36Sopenharmony_ci	media_entity_cleanup(&ip->subdev.entity);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	return ret;
24662306a36Sopenharmony_ci}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_civoid rzg2l_cru_ip_subdev_unregister(struct rzg2l_cru_dev *cru)
24962306a36Sopenharmony_ci{
25062306a36Sopenharmony_ci	struct rzg2l_cru_ip *ip = &cru->ip;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	media_entity_cleanup(&ip->subdev.entity);
25362306a36Sopenharmony_ci	v4l2_subdev_cleanup(&ip->subdev);
25462306a36Sopenharmony_ci	v4l2_device_unregister_subdev(&ip->subdev);
25562306a36Sopenharmony_ci}
256