GNU Linux-libre 4.19.304-gnu1
[releases.git] / drivers / staging / media / imx / imx-media-internal-sd.c
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 */
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
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
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
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
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 */
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 */
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
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
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 }