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