1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Media driver for Freescale i.MX5/6 SOC |
4 | * |
5 | * Adds the IPU internal subdevices and the media links between them. |
6 | * |
7 | * Copyright (c) 2016 Mentor Graphics Inc. |
8 | */ |
9 | #include <linux/platform_device.h> |
10 | #include "imx-media.h" |
11 | |
12 | /* max pads per internal-sd */ |
13 | #define MAX_INTERNAL_PADS 8 |
14 | /* max links per internal-sd pad */ |
15 | #define MAX_INTERNAL_LINKS 8 |
16 | |
17 | struct internal_subdev; |
18 | |
19 | struct internal_link { |
20 | int remote; |
21 | int local_pad; |
22 | int remote_pad; |
23 | }; |
24 | |
25 | struct internal_pad { |
26 | int num_links; |
27 | struct internal_link link[MAX_INTERNAL_LINKS]; |
28 | }; |
29 | |
30 | struct internal_subdev { |
31 | u32 grp_id; |
32 | struct internal_pad pad[MAX_INTERNAL_PADS]; |
33 | |
34 | struct v4l2_subdev * (*sync_register)(struct v4l2_device *v4l2_dev, |
35 | struct device *ipu_dev, |
36 | struct ipu_soc *ipu, |
37 | u32 grp_id); |
38 | int (*sync_unregister)(struct v4l2_subdev *sd); |
39 | }; |
40 | |
41 | static const struct internal_subdev int_subdev[NUM_IPU_SUBDEVS] = { |
42 | [IPU_CSI0] = { |
43 | .grp_id = IMX_MEDIA_GRP_ID_IPU_CSI0, |
44 | .pad[CSI_SRC_PAD_DIRECT] = { |
45 | .num_links = 2, |
46 | .link = { |
47 | { |
48 | .local_pad = CSI_SRC_PAD_DIRECT, |
49 | .remote = IPU_IC_PRP, |
50 | .remote_pad = PRP_SINK_PAD, |
51 | }, { |
52 | .local_pad = CSI_SRC_PAD_DIRECT, |
53 | .remote = IPU_VDIC, |
54 | .remote_pad = VDIC_SINK_PAD_DIRECT, |
55 | }, |
56 | }, |
57 | }, |
58 | }, |
59 | |
60 | [IPU_CSI1] = { |
61 | .grp_id = IMX_MEDIA_GRP_ID_IPU_CSI1, |
62 | .pad[CSI_SRC_PAD_DIRECT] = { |
63 | .num_links = 2, |
64 | .link = { |
65 | { |
66 | .local_pad = CSI_SRC_PAD_DIRECT, |
67 | .remote = IPU_IC_PRP, |
68 | .remote_pad = PRP_SINK_PAD, |
69 | }, { |
70 | .local_pad = CSI_SRC_PAD_DIRECT, |
71 | .remote = IPU_VDIC, |
72 | .remote_pad = VDIC_SINK_PAD_DIRECT, |
73 | }, |
74 | }, |
75 | }, |
76 | }, |
77 | |
78 | [IPU_VDIC] = { |
79 | .grp_id = IMX_MEDIA_GRP_ID_IPU_VDIC, |
80 | .sync_register = imx_media_vdic_register, |
81 | .sync_unregister = imx_media_vdic_unregister, |
82 | .pad[VDIC_SRC_PAD_DIRECT] = { |
83 | .num_links = 1, |
84 | .link = { |
85 | { |
86 | .local_pad = VDIC_SRC_PAD_DIRECT, |
87 | .remote = IPU_IC_PRP, |
88 | .remote_pad = PRP_SINK_PAD, |
89 | }, |
90 | }, |
91 | }, |
92 | }, |
93 | |
94 | [IPU_IC_PRP] = { |
95 | .grp_id = IMX_MEDIA_GRP_ID_IPU_IC_PRP, |
96 | .sync_register = imx_media_ic_register, |
97 | .sync_unregister = imx_media_ic_unregister, |
98 | .pad[PRP_SRC_PAD_PRPENC] = { |
99 | .num_links = 1, |
100 | .link = { |
101 | { |
102 | .local_pad = PRP_SRC_PAD_PRPENC, |
103 | .remote = IPU_IC_PRPENC, |
104 | .remote_pad = PRPENCVF_SINK_PAD, |
105 | }, |
106 | }, |
107 | }, |
108 | .pad[PRP_SRC_PAD_PRPVF] = { |
109 | .num_links = 1, |
110 | .link = { |
111 | { |
112 | .local_pad = PRP_SRC_PAD_PRPVF, |
113 | .remote = IPU_IC_PRPVF, |
114 | .remote_pad = PRPENCVF_SINK_PAD, |
115 | }, |
116 | }, |
117 | }, |
118 | }, |
119 | |
120 | [IPU_IC_PRPENC] = { |
121 | .grp_id = IMX_MEDIA_GRP_ID_IPU_IC_PRPENC, |
122 | .sync_register = imx_media_ic_register, |
123 | .sync_unregister = imx_media_ic_unregister, |
124 | }, |
125 | |
126 | [IPU_IC_PRPVF] = { |
127 | .grp_id = IMX_MEDIA_GRP_ID_IPU_IC_PRPVF, |
128 | .sync_register = imx_media_ic_register, |
129 | .sync_unregister = imx_media_ic_unregister, |
130 | }, |
131 | }; |
132 | |
133 | static int create_internal_link(struct imx_media_dev *imxmd, |
134 | struct v4l2_subdev *src, |
135 | struct v4l2_subdev *sink, |
136 | const struct internal_link *link) |
137 | { |
138 | int ret; |
139 | |
140 | /* skip if this link already created */ |
141 | if (media_entity_find_link(source: &src->entity.pads[link->local_pad], |
142 | sink: &sink->entity.pads[link->remote_pad])) |
143 | return 0; |
144 | |
145 | dev_dbg(imxmd->md.dev, "%s:%d -> %s:%d\n" , |
146 | src->name, link->local_pad, |
147 | sink->name, link->remote_pad); |
148 | |
149 | ret = media_create_pad_link(source: &src->entity, source_pad: link->local_pad, |
150 | sink: &sink->entity, sink_pad: link->remote_pad, flags: 0); |
151 | if (ret) |
152 | v4l2_err(&imxmd->v4l2_dev, "%s failed: %d\n" , __func__, ret); |
153 | |
154 | return ret; |
155 | } |
156 | |
157 | static int create_ipu_internal_links(struct imx_media_dev *imxmd, |
158 | const struct internal_subdev *intsd, |
159 | struct v4l2_subdev *sd, |
160 | int ipu_id) |
161 | { |
162 | const struct internal_pad *intpad; |
163 | const struct internal_link *link; |
164 | struct media_pad *pad; |
165 | int i, j, ret; |
166 | |
167 | /* create the source->sink links */ |
168 | for (i = 0; i < sd->entity.num_pads; i++) { |
169 | intpad = &intsd->pad[i]; |
170 | pad = &sd->entity.pads[i]; |
171 | |
172 | if (!(pad->flags & MEDIA_PAD_FL_SOURCE)) |
173 | continue; |
174 | |
175 | for (j = 0; j < intpad->num_links; j++) { |
176 | struct v4l2_subdev *sink; |
177 | |
178 | link = &intpad->link[j]; |
179 | sink = imxmd->sync_sd[ipu_id][link->remote]; |
180 | |
181 | ret = create_internal_link(imxmd, src: sd, sink, link); |
182 | if (ret) |
183 | return ret; |
184 | } |
185 | } |
186 | |
187 | return 0; |
188 | } |
189 | |
190 | int imx_media_register_ipu_internal_subdevs(struct imx_media_dev *imxmd, |
191 | struct v4l2_subdev *csi) |
192 | { |
193 | struct device *ipu_dev = csi->dev->parent; |
194 | const struct internal_subdev *intsd; |
195 | struct v4l2_subdev *sd; |
196 | struct ipu_soc *ipu; |
197 | int i, ipu_id, ret; |
198 | |
199 | ipu = dev_get_drvdata(dev: ipu_dev); |
200 | if (!ipu) { |
201 | v4l2_err(&imxmd->v4l2_dev, "invalid IPU device!\n" ); |
202 | return -ENODEV; |
203 | } |
204 | |
205 | ipu_id = ipu_get_num(ipu); |
206 | if (ipu_id > 1) { |
207 | v4l2_err(&imxmd->v4l2_dev, "invalid IPU id %d!\n" , ipu_id); |
208 | return -ENODEV; |
209 | } |
210 | |
211 | mutex_lock(&imxmd->mutex); |
212 | |
213 | /* record this IPU */ |
214 | if (!imxmd->ipu[ipu_id]) |
215 | imxmd->ipu[ipu_id] = ipu; |
216 | |
217 | /* register the synchronous subdevs */ |
218 | for (i = 0; i < NUM_IPU_SUBDEVS; i++) { |
219 | intsd = &int_subdev[i]; |
220 | |
221 | sd = imxmd->sync_sd[ipu_id][i]; |
222 | |
223 | /* |
224 | * skip if this sync subdev already registered or its |
225 | * not a sync subdev (one of the CSIs) |
226 | */ |
227 | if (sd || !intsd->sync_register) |
228 | continue; |
229 | |
230 | mutex_unlock(lock: &imxmd->mutex); |
231 | sd = intsd->sync_register(&imxmd->v4l2_dev, ipu_dev, ipu, |
232 | intsd->grp_id); |
233 | mutex_lock(&imxmd->mutex); |
234 | if (IS_ERR(ptr: sd)) { |
235 | ret = PTR_ERR(ptr: sd); |
236 | goto err_unwind; |
237 | } |
238 | |
239 | imxmd->sync_sd[ipu_id][i] = sd; |
240 | } |
241 | |
242 | /* |
243 | * all the sync subdevs are registered, create the media links |
244 | * between them. |
245 | */ |
246 | for (i = 0; i < NUM_IPU_SUBDEVS; i++) { |
247 | intsd = &int_subdev[i]; |
248 | |
249 | if (intsd->grp_id == csi->grp_id) { |
250 | sd = csi; |
251 | } else { |
252 | sd = imxmd->sync_sd[ipu_id][i]; |
253 | if (!sd) |
254 | continue; |
255 | } |
256 | |
257 | ret = create_ipu_internal_links(imxmd, intsd, sd, ipu_id); |
258 | if (ret) { |
259 | mutex_unlock(lock: &imxmd->mutex); |
260 | imx_media_unregister_ipu_internal_subdevs(imxmd); |
261 | return ret; |
262 | } |
263 | } |
264 | |
265 | mutex_unlock(lock: &imxmd->mutex); |
266 | return 0; |
267 | |
268 | err_unwind: |
269 | while (--i >= 0) { |
270 | intsd = &int_subdev[i]; |
271 | sd = imxmd->sync_sd[ipu_id][i]; |
272 | if (!sd || !intsd->sync_unregister) |
273 | continue; |
274 | mutex_unlock(lock: &imxmd->mutex); |
275 | intsd->sync_unregister(sd); |
276 | mutex_lock(&imxmd->mutex); |
277 | } |
278 | |
279 | mutex_unlock(lock: &imxmd->mutex); |
280 | return ret; |
281 | } |
282 | |
283 | void imx_media_unregister_ipu_internal_subdevs(struct imx_media_dev *imxmd) |
284 | { |
285 | const struct internal_subdev *intsd; |
286 | struct v4l2_subdev *sd; |
287 | int i, j; |
288 | |
289 | mutex_lock(&imxmd->mutex); |
290 | |
291 | for (i = 0; i < 2; i++) { |
292 | for (j = 0; j < NUM_IPU_SUBDEVS; j++) { |
293 | intsd = &int_subdev[j]; |
294 | sd = imxmd->sync_sd[i][j]; |
295 | |
296 | if (!sd || !intsd->sync_unregister) |
297 | continue; |
298 | |
299 | mutex_unlock(lock: &imxmd->mutex); |
300 | intsd->sync_unregister(sd); |
301 | mutex_lock(&imxmd->mutex); |
302 | } |
303 | } |
304 | |
305 | mutex_unlock(lock: &imxmd->mutex); |
306 | } |
307 | |