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