Merge tag 'media/v5.1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab...
[linux-2.6-block.git] / drivers / staging / media / imx / imx-media-internal-sd.c
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 }