1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Microchip Image Sensor Controller (ISC) Scaler entity support |
4 | * |
5 | * Copyright (C) 2022 Microchip Technology, Inc. |
6 | * |
7 | * Author: Eugen Hristev <eugen.hristev@microchip.com> |
8 | * |
9 | */ |
10 | |
11 | #include <media/media-device.h> |
12 | #include <media/media-entity.h> |
13 | #include <media/v4l2-device.h> |
14 | #include <media/v4l2-subdev.h> |
15 | |
16 | #include "microchip-isc-regs.h" |
17 | #include "microchip-isc.h" |
18 | |
19 | static void isc_scaler_prepare_fmt(struct v4l2_mbus_framefmt *framefmt) |
20 | { |
21 | framefmt->colorspace = V4L2_COLORSPACE_SRGB; |
22 | framefmt->field = V4L2_FIELD_NONE; |
23 | framefmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; |
24 | framefmt->quantization = V4L2_QUANTIZATION_DEFAULT; |
25 | framefmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; |
26 | }; |
27 | |
28 | static int isc_scaler_get_fmt(struct v4l2_subdev *sd, |
29 | struct v4l2_subdev_state *sd_state, |
30 | struct v4l2_subdev_format *format) |
31 | { |
32 | struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); |
33 | struct v4l2_mbus_framefmt *v4l2_try_fmt; |
34 | |
35 | if (format->which == V4L2_SUBDEV_FORMAT_TRY) { |
36 | v4l2_try_fmt = v4l2_subdev_state_get_format(sd_state, |
37 | format->pad); |
38 | format->format = *v4l2_try_fmt; |
39 | |
40 | return 0; |
41 | } |
42 | |
43 | format->format = isc->scaler_format[format->pad]; |
44 | |
45 | return 0; |
46 | } |
47 | |
48 | static int isc_scaler_set_fmt(struct v4l2_subdev *sd, |
49 | struct v4l2_subdev_state *sd_state, |
50 | struct v4l2_subdev_format *req_fmt) |
51 | { |
52 | struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); |
53 | struct v4l2_mbus_framefmt *v4l2_try_fmt; |
54 | struct isc_format *fmt; |
55 | unsigned int i; |
56 | |
57 | /* Source format is fixed, we cannot change it */ |
58 | if (req_fmt->pad == ISC_SCALER_PAD_SOURCE) { |
59 | req_fmt->format = isc->scaler_format[ISC_SCALER_PAD_SOURCE]; |
60 | return 0; |
61 | } |
62 | |
63 | /* There is no limit on the frame size on the sink pad */ |
64 | v4l_bound_align_image(width: &req_fmt->format.width, wmin: 16, UINT_MAX, walign: 0, |
65 | height: &req_fmt->format.height, hmin: 16, UINT_MAX, halign: 0, salign: 0); |
66 | |
67 | isc_scaler_prepare_fmt(framefmt: &req_fmt->format); |
68 | |
69 | fmt = isc_find_format_by_code(isc, code: req_fmt->format.code, index: &i); |
70 | |
71 | if (!fmt) |
72 | fmt = &isc->formats_list[0]; |
73 | |
74 | req_fmt->format.code = fmt->mbus_code; |
75 | |
76 | if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) { |
77 | v4l2_try_fmt = v4l2_subdev_state_get_format(sd_state, |
78 | req_fmt->pad); |
79 | *v4l2_try_fmt = req_fmt->format; |
80 | /* Trying on the sink pad makes the source pad change too */ |
81 | v4l2_try_fmt = v4l2_subdev_state_get_format(sd_state, |
82 | ISC_SCALER_PAD_SOURCE); |
83 | *v4l2_try_fmt = req_fmt->format; |
84 | |
85 | v4l_bound_align_image(width: &v4l2_try_fmt->width, |
86 | wmin: 16, wmax: isc->max_width, walign: 0, |
87 | height: &v4l2_try_fmt->height, |
88 | hmin: 16, hmax: isc->max_height, halign: 0, salign: 0); |
89 | /* if we are just trying, we are done */ |
90 | return 0; |
91 | } |
92 | |
93 | isc->scaler_format[ISC_SCALER_PAD_SINK] = req_fmt->format; |
94 | |
95 | /* The source pad is the same as the sink, but we have to crop it */ |
96 | isc->scaler_format[ISC_SCALER_PAD_SOURCE] = |
97 | isc->scaler_format[ISC_SCALER_PAD_SINK]; |
98 | v4l_bound_align_image |
99 | (width: &isc->scaler_format[ISC_SCALER_PAD_SOURCE].width, wmin: 16, |
100 | wmax: isc->max_width, walign: 0, |
101 | height: &isc->scaler_format[ISC_SCALER_PAD_SOURCE].height, hmin: 16, |
102 | hmax: isc->max_height, halign: 0, salign: 0); |
103 | |
104 | return 0; |
105 | } |
106 | |
107 | static int isc_scaler_enum_mbus_code(struct v4l2_subdev *sd, |
108 | struct v4l2_subdev_state *sd_state, |
109 | struct v4l2_subdev_mbus_code_enum *code) |
110 | { |
111 | struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); |
112 | |
113 | /* |
114 | * All formats supported by the ISC are supported by the scaler. |
115 | * Advertise the formats which the ISC can take as input, as the scaler |
116 | * entity cropping is part of the PFE module (parallel front end) |
117 | */ |
118 | if (code->index < isc->formats_list_size) { |
119 | code->code = isc->formats_list[code->index].mbus_code; |
120 | return 0; |
121 | } |
122 | |
123 | return -EINVAL; |
124 | } |
125 | |
126 | static int isc_scaler_g_sel(struct v4l2_subdev *sd, |
127 | struct v4l2_subdev_state *sd_state, |
128 | struct v4l2_subdev_selection *sel) |
129 | { |
130 | struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); |
131 | |
132 | if (sel->pad == ISC_SCALER_PAD_SOURCE) |
133 | return -EINVAL; |
134 | |
135 | if (sel->target != V4L2_SEL_TGT_CROP_BOUNDS && |
136 | sel->target != V4L2_SEL_TGT_CROP) |
137 | return -EINVAL; |
138 | |
139 | sel->r.height = isc->scaler_format[ISC_SCALER_PAD_SOURCE].height; |
140 | sel->r.width = isc->scaler_format[ISC_SCALER_PAD_SOURCE].width; |
141 | |
142 | sel->r.left = 0; |
143 | sel->r.top = 0; |
144 | |
145 | return 0; |
146 | } |
147 | |
148 | static int isc_scaler_init_state(struct v4l2_subdev *sd, |
149 | struct v4l2_subdev_state *sd_state) |
150 | { |
151 | struct v4l2_mbus_framefmt *v4l2_try_fmt = |
152 | v4l2_subdev_state_get_format(sd_state, 0); |
153 | struct v4l2_rect *try_crop; |
154 | struct isc_device *isc = container_of(sd, struct isc_device, scaler_sd); |
155 | |
156 | *v4l2_try_fmt = isc->scaler_format[ISC_SCALER_PAD_SOURCE]; |
157 | |
158 | try_crop = v4l2_subdev_state_get_crop(sd_state, 0); |
159 | |
160 | try_crop->top = 0; |
161 | try_crop->left = 0; |
162 | try_crop->width = v4l2_try_fmt->width; |
163 | try_crop->height = v4l2_try_fmt->height; |
164 | |
165 | return 0; |
166 | } |
167 | |
168 | static const struct v4l2_subdev_pad_ops isc_scaler_pad_ops = { |
169 | .enum_mbus_code = isc_scaler_enum_mbus_code, |
170 | .set_fmt = isc_scaler_set_fmt, |
171 | .get_fmt = isc_scaler_get_fmt, |
172 | .get_selection = isc_scaler_g_sel, |
173 | }; |
174 | |
175 | static const struct media_entity_operations isc_scaler_entity_ops = { |
176 | .link_validate = v4l2_subdev_link_validate, |
177 | }; |
178 | |
179 | static const struct v4l2_subdev_ops xisc_scaler_subdev_ops = { |
180 | .pad = &isc_scaler_pad_ops, |
181 | }; |
182 | |
183 | static const struct v4l2_subdev_internal_ops isc_scaler_internal_ops = { |
184 | .init_state = isc_scaler_init_state, |
185 | }; |
186 | |
187 | int isc_scaler_init(struct isc_device *isc) |
188 | { |
189 | int ret; |
190 | |
191 | v4l2_subdev_init(sd: &isc->scaler_sd, ops: &xisc_scaler_subdev_ops); |
192 | isc->scaler_sd.internal_ops = &isc_scaler_internal_ops; |
193 | |
194 | isc->scaler_sd.owner = THIS_MODULE; |
195 | isc->scaler_sd.dev = isc->dev; |
196 | snprintf(buf: isc->scaler_sd.name, size: sizeof(isc->scaler_sd.name), |
197 | fmt: "microchip_isc_scaler" ); |
198 | |
199 | isc->scaler_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; |
200 | isc->scaler_sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; |
201 | isc->scaler_sd.entity.ops = &isc_scaler_entity_ops; |
202 | isc->scaler_pads[ISC_SCALER_PAD_SINK].flags = MEDIA_PAD_FL_SINK; |
203 | isc->scaler_pads[ISC_SCALER_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; |
204 | |
205 | isc_scaler_prepare_fmt(framefmt: &isc->scaler_format[ISC_SCALER_PAD_SOURCE]); |
206 | isc->scaler_format[ISC_SCALER_PAD_SOURCE].height = isc->max_height; |
207 | isc->scaler_format[ISC_SCALER_PAD_SOURCE].width = isc->max_width; |
208 | isc->scaler_format[ISC_SCALER_PAD_SOURCE].code = |
209 | isc->formats_list[0].mbus_code; |
210 | |
211 | isc->scaler_format[ISC_SCALER_PAD_SINK] = |
212 | isc->scaler_format[ISC_SCALER_PAD_SOURCE]; |
213 | |
214 | ret = media_entity_pads_init(entity: &isc->scaler_sd.entity, |
215 | num_pads: ISC_SCALER_PADS_NUM, |
216 | pads: isc->scaler_pads); |
217 | if (ret < 0) { |
218 | dev_err(isc->dev, "scaler sd media entity init failed\n" ); |
219 | return ret; |
220 | } |
221 | |
222 | ret = v4l2_device_register_subdev(v4l2_dev: &isc->v4l2_dev, sd: &isc->scaler_sd); |
223 | if (ret < 0) { |
224 | dev_err(isc->dev, "scaler sd failed to register subdev\n" ); |
225 | return ret; |
226 | } |
227 | |
228 | return ret; |
229 | } |
230 | EXPORT_SYMBOL_GPL(isc_scaler_init); |
231 | |
232 | int isc_scaler_link(struct isc_device *isc) |
233 | { |
234 | int ret; |
235 | |
236 | ret = media_create_pad_link(source: &isc->current_subdev->sd->entity, |
237 | source_pad: isc->remote_pad, sink: &isc->scaler_sd.entity, |
238 | sink_pad: ISC_SCALER_PAD_SINK, |
239 | MEDIA_LNK_FL_ENABLED | |
240 | MEDIA_LNK_FL_IMMUTABLE); |
241 | |
242 | if (ret < 0) { |
243 | dev_err(isc->dev, "Failed to create pad link: %s to %s\n" , |
244 | isc->current_subdev->sd->entity.name, |
245 | isc->scaler_sd.entity.name); |
246 | return ret; |
247 | } |
248 | |
249 | dev_dbg(isc->dev, "link with %s pad: %d\n" , |
250 | isc->current_subdev->sd->name, isc->remote_pad); |
251 | |
252 | ret = media_create_pad_link(source: &isc->scaler_sd.entity, |
253 | source_pad: ISC_SCALER_PAD_SOURCE, |
254 | sink: &isc->video_dev.entity, sink_pad: ISC_PAD_SINK, |
255 | MEDIA_LNK_FL_ENABLED | |
256 | MEDIA_LNK_FL_IMMUTABLE); |
257 | |
258 | if (ret < 0) { |
259 | dev_err(isc->dev, "Failed to create pad link: %s to %s\n" , |
260 | isc->scaler_sd.entity.name, |
261 | isc->video_dev.entity.name); |
262 | return ret; |
263 | } |
264 | |
265 | dev_dbg(isc->dev, "link with %s pad: %d\n" , isc->scaler_sd.name, |
266 | ISC_SCALER_PAD_SOURCE); |
267 | |
268 | return ret; |
269 | } |
270 | EXPORT_SYMBOL_GPL(isc_scaler_link); |
271 | |
272 | |