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
create_internal_link(struct imx_media_dev * imxmd,struct v4l2_subdev * src,struct v4l2_subdev * sink,const struct internal_link * link)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(&src->entity.pads[link->local_pad],
142 &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(&src->entity, link->local_pad,
150 &sink->entity, link->remote_pad, 0);
151 if (ret)
152 v4l2_err(&imxmd->v4l2_dev, "%s failed: %d\n", __func__, ret);
153
154 return ret;
155 }
156
create_ipu_internal_links(struct imx_media_dev * imxmd,const struct internal_subdev * intsd,struct v4l2_subdev * sd,int ipu_id)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, sd, sink, link);
182 if (ret)
183 return ret;
184 }
185 }
186
187 return 0;
188 }
189
imx_media_register_ipu_internal_subdevs(struct imx_media_dev * imxmd,struct v4l2_subdev * csi)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(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(&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(sd)) {
235 ret = PTR_ERR(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(&imxmd->mutex);
260 imx_media_unregister_ipu_internal_subdevs(imxmd);
261 return ret;
262 }
263 }
264
265 mutex_unlock(&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(&imxmd->mutex);
275 intsd->sync_unregister(sd);
276 mutex_lock(&imxmd->mutex);
277 }
278
279 mutex_unlock(&imxmd->mutex);
280 return ret;
281 }
282
imx_media_unregister_ipu_internal_subdevs(struct imx_media_dev * imxmd)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(&imxmd->mutex);
300 intsd->sync_unregister(sd);
301 mutex_lock(&imxmd->mutex);
302 }
303 }
304
305 mutex_unlock(&imxmd->mutex);
306 }
307