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(&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 
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 
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 
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