1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Driver for Renesas RZ/G2L CRU
4 *
5 * Copyright (C) 2022 Renesas Electronics Corp.
6 */
7
8#include "rzg2l-cru.h"
9
10struct rzg2l_cru_ip_format {
11	u32 code;
12	unsigned int datatype;
13	unsigned int bpp;
14};
15
16static const struct rzg2l_cru_ip_format rzg2l_cru_ip_formats[] = {
17	{ .code = MEDIA_BUS_FMT_UYVY8_1X16,	.datatype = 0x1e, .bpp = 16 },
18};
19
20enum rzg2l_csi2_pads {
21	RZG2L_CRU_IP_SINK = 0,
22	RZG2L_CRU_IP_SOURCE,
23};
24
25static const struct rzg2l_cru_ip_format *rzg2l_cru_ip_code_to_fmt(unsigned int code)
26{
27	unsigned int i;
28
29	for (i = 0; i < ARRAY_SIZE(rzg2l_cru_ip_formats); i++)
30		if (rzg2l_cru_ip_formats[i].code == code)
31			return &rzg2l_cru_ip_formats[i];
32
33	return NULL;
34}
35
36struct v4l2_mbus_framefmt *rzg2l_cru_ip_get_src_fmt(struct rzg2l_cru_dev *cru)
37{
38	struct v4l2_subdev_state *state;
39	struct v4l2_mbus_framefmt *fmt;
40
41	state = v4l2_subdev_lock_and_get_active_state(&cru->ip.subdev);
42	fmt = v4l2_subdev_get_pad_format(&cru->ip.subdev, state, 1);
43	v4l2_subdev_unlock_state(state);
44
45	return fmt;
46}
47
48static int rzg2l_cru_ip_s_stream(struct v4l2_subdev *sd, int enable)
49{
50	struct rzg2l_cru_dev *cru;
51	int s_stream_ret = 0;
52	int ret;
53
54	cru = v4l2_get_subdevdata(sd);
55
56	if (!enable) {
57		ret = v4l2_subdev_call(cru->ip.remote, video, s_stream, enable);
58		if (ret)
59			s_stream_ret = ret;
60
61		ret = v4l2_subdev_call(cru->ip.remote, video, post_streamoff);
62		if (ret == -ENOIOCTLCMD)
63			ret = 0;
64		if (ret && !s_stream_ret)
65			s_stream_ret = ret;
66		rzg2l_cru_stop_image_processing(cru);
67	} else {
68		ret = v4l2_subdev_call(cru->ip.remote, video, pre_streamon, 0);
69		if (ret == -ENOIOCTLCMD)
70			ret = 0;
71		if (ret)
72			return ret;
73
74		ret = rzg2l_cru_start_image_processing(cru);
75		if (ret) {
76			v4l2_subdev_call(cru->ip.remote, video, post_streamoff);
77			return ret;
78		}
79
80		rzg2l_cru_vclk_unprepare(cru);
81
82		ret = v4l2_subdev_call(cru->ip.remote, video, s_stream, enable);
83		if (ret == -ENOIOCTLCMD)
84			ret = 0;
85		if (!ret) {
86			ret = rzg2l_cru_vclk_prepare(cru);
87			if (!ret)
88				return 0;
89		} else {
90			/* enable back vclk so that s_stream in error path disables it */
91			if (rzg2l_cru_vclk_prepare(cru))
92				dev_err(cru->dev, "Failed to enable vclk\n");
93		}
94
95		s_stream_ret = ret;
96
97		v4l2_subdev_call(cru->ip.remote, video, post_streamoff);
98		rzg2l_cru_stop_image_processing(cru);
99	}
100
101	return s_stream_ret;
102}
103
104static int rzg2l_cru_ip_set_format(struct v4l2_subdev *sd,
105				   struct v4l2_subdev_state *state,
106				   struct v4l2_subdev_format *fmt)
107{
108	struct v4l2_mbus_framefmt *src_format;
109	struct v4l2_mbus_framefmt *sink_format;
110
111	src_format = v4l2_subdev_get_pad_format(sd, state, RZG2L_CRU_IP_SOURCE);
112	if (fmt->pad == RZG2L_CRU_IP_SOURCE) {
113		fmt->format = *src_format;
114		return 0;
115	}
116
117	sink_format = v4l2_subdev_get_pad_format(sd, state, fmt->pad);
118
119	if (!rzg2l_cru_ip_code_to_fmt(fmt->format.code))
120		sink_format->code = rzg2l_cru_ip_formats[0].code;
121	else
122		sink_format->code = fmt->format.code;
123
124	sink_format->field = V4L2_FIELD_NONE;
125	sink_format->colorspace = fmt->format.colorspace;
126	sink_format->xfer_func = fmt->format.xfer_func;
127	sink_format->ycbcr_enc = fmt->format.ycbcr_enc;
128	sink_format->quantization = fmt->format.quantization;
129	sink_format->width = clamp_t(u32, fmt->format.width,
130				     RZG2L_CRU_MIN_INPUT_WIDTH, RZG2L_CRU_MAX_INPUT_WIDTH);
131	sink_format->height = clamp_t(u32, fmt->format.height,
132				      RZG2L_CRU_MIN_INPUT_HEIGHT, RZG2L_CRU_MAX_INPUT_HEIGHT);
133
134	fmt->format = *sink_format;
135
136	/* propagate format to source pad */
137	*src_format = *sink_format;
138
139	return 0;
140}
141
142static int rzg2l_cru_ip_enum_mbus_code(struct v4l2_subdev *sd,
143				       struct v4l2_subdev_state *state,
144				       struct v4l2_subdev_mbus_code_enum *code)
145{
146	if (code->index >= ARRAY_SIZE(rzg2l_cru_ip_formats))
147		return -EINVAL;
148
149	code->code = rzg2l_cru_ip_formats[code->index].code;
150	return 0;
151}
152
153static int rzg2l_cru_ip_enum_frame_size(struct v4l2_subdev *sd,
154					struct v4l2_subdev_state *state,
155					struct v4l2_subdev_frame_size_enum *fse)
156{
157	if (fse->index != 0)
158		return -EINVAL;
159
160	if (fse->code != MEDIA_BUS_FMT_UYVY8_1X16)
161		return -EINVAL;
162
163	fse->min_width = RZG2L_CRU_MIN_INPUT_WIDTH;
164	fse->min_height = RZG2L_CRU_MIN_INPUT_HEIGHT;
165	fse->max_width = RZG2L_CRU_MAX_INPUT_WIDTH;
166	fse->max_height = RZG2L_CRU_MAX_INPUT_HEIGHT;
167
168	return 0;
169}
170
171static int rzg2l_cru_ip_init_config(struct v4l2_subdev *sd,
172				    struct v4l2_subdev_state *sd_state)
173{
174	struct v4l2_subdev_format fmt = { .pad = RZG2L_CRU_IP_SINK, };
175
176	fmt.format.width = RZG2L_CRU_MIN_INPUT_WIDTH;
177	fmt.format.height = RZG2L_CRU_MIN_INPUT_HEIGHT;
178	fmt.format.field = V4L2_FIELD_NONE;
179	fmt.format.code = MEDIA_BUS_FMT_UYVY8_1X16;
180	fmt.format.colorspace = V4L2_COLORSPACE_SRGB;
181	fmt.format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
182	fmt.format.quantization = V4L2_QUANTIZATION_DEFAULT;
183	fmt.format.xfer_func = V4L2_XFER_FUNC_DEFAULT;
184
185	return rzg2l_cru_ip_set_format(sd, sd_state, &fmt);
186}
187
188static const struct v4l2_subdev_video_ops rzg2l_cru_ip_video_ops = {
189	.s_stream = rzg2l_cru_ip_s_stream,
190};
191
192static const struct v4l2_subdev_pad_ops rzg2l_cru_ip_pad_ops = {
193	.enum_mbus_code = rzg2l_cru_ip_enum_mbus_code,
194	.enum_frame_size = rzg2l_cru_ip_enum_frame_size,
195	.init_cfg = rzg2l_cru_ip_init_config,
196	.get_fmt = v4l2_subdev_get_fmt,
197	.set_fmt = rzg2l_cru_ip_set_format,
198};
199
200static const struct v4l2_subdev_ops rzg2l_cru_ip_subdev_ops = {
201	.video = &rzg2l_cru_ip_video_ops,
202	.pad = &rzg2l_cru_ip_pad_ops,
203};
204
205static const struct media_entity_operations rzg2l_cru_ip_entity_ops = {
206	.link_validate = v4l2_subdev_link_validate,
207};
208
209int rzg2l_cru_ip_subdev_register(struct rzg2l_cru_dev *cru)
210{
211	struct rzg2l_cru_ip *ip = &cru->ip;
212	int ret;
213
214	ip->subdev.dev = cru->dev;
215	v4l2_subdev_init(&ip->subdev, &rzg2l_cru_ip_subdev_ops);
216	v4l2_set_subdevdata(&ip->subdev, cru);
217	snprintf(ip->subdev.name, sizeof(ip->subdev.name),
218		 "cru-ip-%s", dev_name(cru->dev));
219	ip->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
220
221	ip->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
222	ip->subdev.entity.ops = &rzg2l_cru_ip_entity_ops;
223
224	ip->pads[0].flags = MEDIA_PAD_FL_SINK;
225	ip->pads[1].flags = MEDIA_PAD_FL_SOURCE;
226
227	ret = media_entity_pads_init(&ip->subdev.entity, 2, ip->pads);
228	if (ret)
229		return ret;
230
231	ret = v4l2_subdev_init_finalize(&ip->subdev);
232	if (ret < 0)
233		goto entity_cleanup;
234
235	ret = v4l2_device_register_subdev(&cru->v4l2_dev, &ip->subdev);
236	if (ret < 0)
237		goto error_subdev;
238
239	return 0;
240error_subdev:
241	v4l2_subdev_cleanup(&ip->subdev);
242entity_cleanup:
243	media_entity_cleanup(&ip->subdev.entity);
244
245	return ret;
246}
247
248void rzg2l_cru_ip_subdev_unregister(struct rzg2l_cru_dev *cru)
249{
250	struct rzg2l_cru_ip *ip = &cru->ip;
251
252	media_entity_cleanup(&ip->subdev.entity);
253	v4l2_subdev_cleanup(&ip->subdev);
254	v4l2_device_unregister_subdev(&ip->subdev);
255}
256