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 enum isd_enum {
13 	isd_csi0 = 0,
14 	isd_csi1,
15 	isd_vdic,
16 	isd_ic_prp,
17 	isd_ic_prpenc,
18 	isd_ic_prpvf,
19 	num_isd,
20 };
21 
22 static const struct internal_subdev_id {
23 	enum isd_enum index;
24 	const char *name;
25 	u32 grp_id;
26 } isd_id[num_isd] = {
27 	[isd_csi0] = {
28 		.index = isd_csi0,
29 		.grp_id = IMX_MEDIA_GRP_ID_IPU_CSI0,
30 		.name = "imx-ipuv3-csi",
31 	},
32 	[isd_csi1] = {
33 		.index = isd_csi1,
34 		.grp_id = IMX_MEDIA_GRP_ID_IPU_CSI1,
35 		.name = "imx-ipuv3-csi",
36 	},
37 	[isd_vdic] = {
38 		.index = isd_vdic,
39 		.grp_id = IMX_MEDIA_GRP_ID_IPU_VDIC,
40 		.name = "imx-ipuv3-vdic",
41 	},
42 	[isd_ic_prp] = {
43 		.index = isd_ic_prp,
44 		.grp_id = IMX_MEDIA_GRP_ID_IPU_IC_PRP,
45 		.name = "imx-ipuv3-ic",
46 	},
47 	[isd_ic_prpenc] = {
48 		.index = isd_ic_prpenc,
49 		.grp_id = IMX_MEDIA_GRP_ID_IPU_IC_PRPENC,
50 		.name = "imx-ipuv3-ic",
51 	},
52 	[isd_ic_prpvf] = {
53 		.index = isd_ic_prpvf,
54 		.grp_id = IMX_MEDIA_GRP_ID_IPU_IC_PRPVF,
55 		.name = "imx-ipuv3-ic",
56 	},
57 };
58 
59 struct internal_subdev;
60 
61 struct internal_link {
62 	const struct internal_subdev *remote;
63 	int local_pad;
64 	int remote_pad;
65 };
66 
67 /* max pads per internal-sd */
68 #define MAX_INTERNAL_PADS   8
69 /* max links per internal-sd pad */
70 #define MAX_INTERNAL_LINKS  8
71 
72 struct internal_pad {
73 	struct internal_link link[MAX_INTERNAL_LINKS];
74 };
75 
76 static const struct internal_subdev {
77 	const struct internal_subdev_id *id;
78 	struct internal_pad pad[MAX_INTERNAL_PADS];
79 } int_subdev[num_isd] = {
80 	[isd_csi0] = {
81 		.id = &isd_id[isd_csi0],
82 		.pad[CSI_SRC_PAD_DIRECT] = {
83 			.link = {
84 				{
85 					.local_pad = CSI_SRC_PAD_DIRECT,
86 					.remote = &int_subdev[isd_ic_prp],
87 					.remote_pad = PRP_SINK_PAD,
88 				}, {
89 					.local_pad = CSI_SRC_PAD_DIRECT,
90 					.remote = &int_subdev[isd_vdic],
91 					.remote_pad = VDIC_SINK_PAD_DIRECT,
92 				},
93 			},
94 		},
95 	},
96 
97 	[isd_csi1] = {
98 		.id = &isd_id[isd_csi1],
99 		.pad[CSI_SRC_PAD_DIRECT] = {
100 			.link = {
101 				{
102 					.local_pad = CSI_SRC_PAD_DIRECT,
103 					.remote = &int_subdev[isd_ic_prp],
104 					.remote_pad = PRP_SINK_PAD,
105 				}, {
106 					.local_pad = CSI_SRC_PAD_DIRECT,
107 					.remote = &int_subdev[isd_vdic],
108 					.remote_pad = VDIC_SINK_PAD_DIRECT,
109 				},
110 			},
111 		},
112 	},
113 
114 	[isd_vdic] = {
115 		.id = &isd_id[isd_vdic],
116 		.pad[VDIC_SRC_PAD_DIRECT] = {
117 			.link = {
118 				{
119 					.local_pad = VDIC_SRC_PAD_DIRECT,
120 					.remote = &int_subdev[isd_ic_prp],
121 					.remote_pad = PRP_SINK_PAD,
122 				},
123 			},
124 		},
125 	},
126 
127 	[isd_ic_prp] = {
128 		.id = &isd_id[isd_ic_prp],
129 		.pad[PRP_SRC_PAD_PRPENC] = {
130 			.link = {
131 				{
132 					.local_pad = PRP_SRC_PAD_PRPENC,
133 					.remote = &int_subdev[isd_ic_prpenc],
134 					.remote_pad = 0,
135 				},
136 			},
137 		},
138 		.pad[PRP_SRC_PAD_PRPVF] = {
139 			.link = {
140 				{
141 					.local_pad = PRP_SRC_PAD_PRPVF,
142 					.remote = &int_subdev[isd_ic_prpvf],
143 					.remote_pad = 0,
144 				},
145 			},
146 		},
147 	},
148 
149 	[isd_ic_prpenc] = {
150 		.id = &isd_id[isd_ic_prpenc],
151 	},
152 
153 	[isd_ic_prpvf] = {
154 		.id = &isd_id[isd_ic_prpvf],
155 	},
156 };
157 
158 /* form a device name given an internal subdev and ipu id */
159 static inline void isd_to_devname(char *devname, int sz,
160 				  const struct internal_subdev *isd,
161 				  int ipu_id)
162 {
163 	int pdev_id = ipu_id * num_isd + isd->id->index;
164 
165 	snprintf(devname, sz, "%s.%d", isd->id->name, pdev_id);
166 }
167 
168 static const struct internal_subdev *find_intsd_by_grp_id(u32 grp_id)
169 {
170 	enum isd_enum i;
171 
172 	for (i = 0; i < num_isd; i++) {
173 		const struct internal_subdev *isd = &int_subdev[i];
174 
175 		if (isd->id->grp_id == grp_id)
176 			return isd;
177 	}
178 
179 	return NULL;
180 }
181 
182 static struct v4l2_subdev *find_sink(struct imx_media_dev *imxmd,
183 				     struct v4l2_subdev *src,
184 				     const struct internal_link *link)
185 {
186 	char sink_devname[32];
187 	int ipu_id;
188 
189 	/*
190 	 * retrieve IPU id from subdev name, note: can't get this from
191 	 * struct imx_media_ipu_internal_sd_pdata because if src is
192 	 * a CSI, it has different struct ipu_client_platformdata which
193 	 * does not contain IPU id.
194 	 */
195 	if (sscanf(src->name, "ipu%d", &ipu_id) != 1)
196 		return NULL;
197 
198 	isd_to_devname(sink_devname, sizeof(sink_devname),
199 		       link->remote, ipu_id - 1);
200 
201 	return imx_media_find_subdev_by_devname(imxmd, sink_devname);
202 }
203 
204 static int create_ipu_internal_link(struct imx_media_dev *imxmd,
205 				    struct v4l2_subdev *src,
206 				    const struct internal_link *link)
207 {
208 	struct v4l2_subdev *sink;
209 	int ret;
210 
211 	sink = find_sink(imxmd, src, link);
212 	if (!sink)
213 		return -ENODEV;
214 
215 	v4l2_info(&imxmd->v4l2_dev, "%s:%d -> %s:%d\n",
216 		  src->name, link->local_pad,
217 		  sink->name, link->remote_pad);
218 
219 	ret = media_create_pad_link(&src->entity, link->local_pad,
220 				    &sink->entity, link->remote_pad, 0);
221 	if (ret)
222 		v4l2_err(&imxmd->v4l2_dev,
223 			 "create_pad_link failed: %d\n", ret);
224 
225 	return ret;
226 }
227 
228 int imx_media_create_ipu_internal_links(struct imx_media_dev *imxmd,
229 					struct v4l2_subdev *sd)
230 {
231 	const struct internal_subdev *intsd;
232 	const struct internal_pad *intpad;
233 	const struct internal_link *link;
234 	struct media_pad *pad;
235 	int i, j, ret;
236 
237 	intsd = find_intsd_by_grp_id(sd->grp_id);
238 	if (!intsd)
239 		return -ENODEV;
240 
241 	/* create the source->sink links */
242 	for (i = 0; i < sd->entity.num_pads; i++) {
243 		intpad = &intsd->pad[i];
244 		pad = &sd->entity.pads[i];
245 
246 		if (!(pad->flags & MEDIA_PAD_FL_SOURCE))
247 			continue;
248 
249 		for (j = 0; ; j++) {
250 			link = &intpad->link[j];
251 
252 			if (!link->remote)
253 				break;
254 
255 			ret = create_ipu_internal_link(imxmd, sd, link);
256 			if (ret)
257 				return ret;
258 		}
259 	}
260 
261 	return 0;
262 }
263 
264 /* register an internal subdev as a platform device */
265 static int add_internal_subdev(struct imx_media_dev *imxmd,
266 			       const struct internal_subdev *isd,
267 			       int ipu_id)
268 {
269 	struct imx_media_ipu_internal_sd_pdata pdata;
270 	struct platform_device_info pdevinfo = {};
271 	struct platform_device *pdev;
272 
273 	pdata.grp_id = isd->id->grp_id;
274 
275 	/* the id of IPU this subdev will control */
276 	pdata.ipu_id = ipu_id;
277 
278 	/* create subdev name */
279 	imx_media_grp_id_to_sd_name(pdata.sd_name, sizeof(pdata.sd_name),
280 				    pdata.grp_id, ipu_id);
281 
282 	pdevinfo.name = isd->id->name;
283 	pdevinfo.id = ipu_id * num_isd + isd->id->index;
284 	pdevinfo.parent = imxmd->md.dev;
285 	pdevinfo.data = &pdata;
286 	pdevinfo.size_data = sizeof(pdata);
287 	pdevinfo.dma_mask = DMA_BIT_MASK(32);
288 
289 	pdev = platform_device_register_full(&pdevinfo);
290 	if (IS_ERR(pdev))
291 		return PTR_ERR(pdev);
292 
293 	return imx_media_add_async_subdev(imxmd, NULL, pdev);
294 }
295 
296 /* adds the internal subdevs in one ipu */
297 int imx_media_add_ipu_internal_subdevs(struct imx_media_dev *imxmd,
298 				       int ipu_id)
299 {
300 	enum isd_enum i;
301 	int ret;
302 
303 	for (i = 0; i < num_isd; i++) {
304 		const struct internal_subdev *isd = &int_subdev[i];
305 
306 		/*
307 		 * the CSIs are represented in the device-tree, so those
308 		 * devices are already added to the async subdev list by
309 		 * of_parse_subdev().
310 		 */
311 		switch (isd->id->grp_id) {
312 		case IMX_MEDIA_GRP_ID_IPU_CSI0:
313 		case IMX_MEDIA_GRP_ID_IPU_CSI1:
314 			ret = 0;
315 			break;
316 		default:
317 			ret = add_internal_subdev(imxmd, isd, ipu_id);
318 			break;
319 		}
320 
321 		if (ret)
322 			goto remove;
323 	}
324 
325 	return 0;
326 
327 remove:
328 	imx_media_remove_ipu_internal_subdevs(imxmd);
329 	return ret;
330 }
331 
332 void imx_media_remove_ipu_internal_subdevs(struct imx_media_dev *imxmd)
333 {
334 	struct imx_media_async_subdev *imxasd;
335 	struct v4l2_async_subdev *asd;
336 
337 	list_for_each_entry(asd, &imxmd->notifier.asd_list, asd_list) {
338 		imxasd = to_imx_media_asd(asd);
339 
340 		if (!imxasd->pdev)
341 			continue;
342 
343 		platform_device_unregister(imxasd->pdev);
344 	}
345 }
346