1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * V4L2 fwnode binding parsing library 4 * 5 * The origins of the V4L2 fwnode library are in V4L2 OF library that 6 * formerly was located in v4l2-of.c. 7 * 8 * Copyright (c) 2016 Intel Corporation. 9 * Author: Sakari Ailus <sakari.ailus@linux.intel.com> 10 * 11 * Copyright (C) 2012 - 2013 Samsung Electronics Co., Ltd. 12 * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> 13 * 14 * Copyright (C) 2012 Renesas Electronics Corp. 15 * Author: Guennadi Liakhovetski <g.liakhovetski@gmx.de> 16 */ 17 #include <linux/acpi.h> 18 #include <linux/kernel.h> 19 #include <linux/mm.h> 20 #include <linux/module.h> 21 #include <linux/of.h> 22 #include <linux/property.h> 23 #include <linux/slab.h> 24 #include <linux/string.h> 25 #include <linux/types.h> 26 27 #include <media/v4l2-async.h> 28 #include <media/v4l2-fwnode.h> 29 #include <media/v4l2-subdev.h> 30 31 static const struct v4l2_fwnode_bus_conv { 32 enum v4l2_fwnode_bus_type fwnode_bus_type; 33 enum v4l2_mbus_type mbus_type; 34 const char *name; 35 } buses[] = { 36 { 37 V4L2_FWNODE_BUS_TYPE_GUESS, 38 V4L2_MBUS_UNKNOWN, 39 "not specified", 40 }, { 41 V4L2_FWNODE_BUS_TYPE_CSI2_CPHY, 42 V4L2_MBUS_CSI2_CPHY, 43 "MIPI CSI-2 C-PHY", 44 }, { 45 V4L2_FWNODE_BUS_TYPE_CSI1, 46 V4L2_MBUS_CSI1, 47 "MIPI CSI-1", 48 }, { 49 V4L2_FWNODE_BUS_TYPE_CCP2, 50 V4L2_MBUS_CCP2, 51 "compact camera port 2", 52 }, { 53 V4L2_FWNODE_BUS_TYPE_CSI2_DPHY, 54 V4L2_MBUS_CSI2_DPHY, 55 "MIPI CSI-2 D-PHY", 56 }, { 57 V4L2_FWNODE_BUS_TYPE_PARALLEL, 58 V4L2_MBUS_PARALLEL, 59 "parallel", 60 }, { 61 V4L2_FWNODE_BUS_TYPE_BT656, 62 V4L2_MBUS_BT656, 63 "Bt.656", 64 } 65 }; 66 67 static const struct v4l2_fwnode_bus_conv * 68 get_v4l2_fwnode_bus_conv_by_fwnode_bus(enum v4l2_fwnode_bus_type type) 69 { 70 unsigned int i; 71 72 for (i = 0; i < ARRAY_SIZE(buses); i++) 73 if (buses[i].fwnode_bus_type == type) 74 return &buses[i]; 75 76 return NULL; 77 } 78 79 static enum v4l2_mbus_type 80 v4l2_fwnode_bus_type_to_mbus(enum v4l2_fwnode_bus_type type) 81 { 82 const struct v4l2_fwnode_bus_conv *conv = 83 get_v4l2_fwnode_bus_conv_by_fwnode_bus(type); 84 85 return conv ? conv->mbus_type : V4L2_MBUS_INVALID; 86 } 87 88 static const char * 89 v4l2_fwnode_bus_type_to_string(enum v4l2_fwnode_bus_type type) 90 { 91 const struct v4l2_fwnode_bus_conv *conv = 92 get_v4l2_fwnode_bus_conv_by_fwnode_bus(type); 93 94 return conv ? conv->name : "not found"; 95 } 96 97 static const struct v4l2_fwnode_bus_conv * 98 get_v4l2_fwnode_bus_conv_by_mbus(enum v4l2_mbus_type type) 99 { 100 unsigned int i; 101 102 for (i = 0; i < ARRAY_SIZE(buses); i++) 103 if (buses[i].mbus_type == type) 104 return &buses[i]; 105 106 return NULL; 107 } 108 109 static const char * 110 v4l2_fwnode_mbus_type_to_string(enum v4l2_mbus_type type) 111 { 112 const struct v4l2_fwnode_bus_conv *conv = 113 get_v4l2_fwnode_bus_conv_by_mbus(type); 114 115 return conv ? conv->name : "not found"; 116 } 117 118 static int v4l2_fwnode_endpoint_parse_csi2_bus(struct fwnode_handle *fwnode, 119 struct v4l2_fwnode_endpoint *vep, 120 enum v4l2_mbus_type bus_type) 121 { 122 struct v4l2_fwnode_bus_mipi_csi2 *bus = &vep->bus.mipi_csi2; 123 bool have_clk_lane = false, have_data_lanes = false, 124 have_lane_polarities = false; 125 unsigned int flags = 0, lanes_used = 0; 126 u32 array[1 + V4L2_FWNODE_CSI2_MAX_DATA_LANES]; 127 u32 clock_lane = 0; 128 unsigned int num_data_lanes = 0; 129 bool use_default_lane_mapping = false; 130 unsigned int i; 131 u32 v; 132 int rval; 133 134 if (bus_type == V4L2_MBUS_CSI2_DPHY || 135 bus_type == V4L2_MBUS_CSI2_CPHY) { 136 use_default_lane_mapping = true; 137 138 num_data_lanes = min_t(u32, bus->num_data_lanes, 139 V4L2_FWNODE_CSI2_MAX_DATA_LANES); 140 141 clock_lane = bus->clock_lane; 142 if (clock_lane) 143 use_default_lane_mapping = false; 144 145 for (i = 0; i < num_data_lanes; i++) { 146 array[i] = bus->data_lanes[i]; 147 if (array[i]) 148 use_default_lane_mapping = false; 149 } 150 151 if (use_default_lane_mapping) 152 pr_debug("no lane mapping given, using defaults\n"); 153 } 154 155 rval = fwnode_property_count_u32(fwnode, "data-lanes"); 156 if (rval > 0) { 157 num_data_lanes = 158 min_t(int, V4L2_FWNODE_CSI2_MAX_DATA_LANES, rval); 159 160 fwnode_property_read_u32_array(fwnode, "data-lanes", array, 161 num_data_lanes); 162 163 have_data_lanes = true; 164 if (use_default_lane_mapping) { 165 pr_debug("data-lanes property exists; disabling default mapping\n"); 166 use_default_lane_mapping = false; 167 } 168 } 169 170 for (i = 0; i < num_data_lanes; i++) { 171 if (lanes_used & BIT(array[i])) { 172 if (have_data_lanes || !use_default_lane_mapping) 173 pr_warn("duplicated lane %u in data-lanes, using defaults\n", 174 array[i]); 175 use_default_lane_mapping = true; 176 } 177 lanes_used |= BIT(array[i]); 178 179 if (have_data_lanes) 180 pr_debug("lane %u position %u\n", i, array[i]); 181 } 182 183 rval = fwnode_property_count_u32(fwnode, "lane-polarities"); 184 if (rval > 0) { 185 if (rval != 1 + num_data_lanes /* clock+data */) { 186 pr_warn("invalid number of lane-polarities entries (need %u, got %u)\n", 187 1 + num_data_lanes, rval); 188 return -EINVAL; 189 } 190 191 have_lane_polarities = true; 192 } 193 194 if (!fwnode_property_read_u32(fwnode, "clock-lanes", &v)) { 195 clock_lane = v; 196 pr_debug("clock lane position %u\n", v); 197 have_clk_lane = true; 198 } 199 200 if (have_clk_lane && lanes_used & BIT(clock_lane) && 201 !use_default_lane_mapping) { 202 pr_warn("duplicated lane %u in clock-lanes, using defaults\n", 203 v); 204 use_default_lane_mapping = true; 205 } 206 207 if (fwnode_property_present(fwnode, "clock-noncontinuous")) { 208 flags |= V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK; 209 pr_debug("non-continuous clock\n"); 210 } else { 211 flags |= V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; 212 } 213 214 if (bus_type == V4L2_MBUS_CSI2_DPHY || 215 bus_type == V4L2_MBUS_CSI2_CPHY || lanes_used || 216 have_clk_lane || (flags & ~V4L2_MBUS_CSI2_CONTINUOUS_CLOCK)) { 217 /* Only D-PHY has a clock lane. */ 218 unsigned int dfl_data_lane_index = 219 bus_type == V4L2_MBUS_CSI2_DPHY; 220 221 bus->flags = flags; 222 if (bus_type == V4L2_MBUS_UNKNOWN) 223 vep->bus_type = V4L2_MBUS_CSI2_DPHY; 224 bus->num_data_lanes = num_data_lanes; 225 226 if (use_default_lane_mapping) { 227 bus->clock_lane = 0; 228 for (i = 0; i < num_data_lanes; i++) 229 bus->data_lanes[i] = dfl_data_lane_index + i; 230 } else { 231 bus->clock_lane = clock_lane; 232 for (i = 0; i < num_data_lanes; i++) 233 bus->data_lanes[i] = array[i]; 234 } 235 236 if (have_lane_polarities) { 237 fwnode_property_read_u32_array(fwnode, 238 "lane-polarities", array, 239 1 + num_data_lanes); 240 241 for (i = 0; i < 1 + num_data_lanes; i++) { 242 bus->lane_polarities[i] = array[i]; 243 pr_debug("lane %u polarity %sinverted", 244 i, array[i] ? "" : "not "); 245 } 246 } else { 247 pr_debug("no lane polarities defined, assuming not inverted\n"); 248 } 249 } 250 251 return 0; 252 } 253 254 #define PARALLEL_MBUS_FLAGS (V4L2_MBUS_HSYNC_ACTIVE_HIGH | \ 255 V4L2_MBUS_HSYNC_ACTIVE_LOW | \ 256 V4L2_MBUS_VSYNC_ACTIVE_HIGH | \ 257 V4L2_MBUS_VSYNC_ACTIVE_LOW | \ 258 V4L2_MBUS_FIELD_EVEN_HIGH | \ 259 V4L2_MBUS_FIELD_EVEN_LOW) 260 261 static void 262 v4l2_fwnode_endpoint_parse_parallel_bus(struct fwnode_handle *fwnode, 263 struct v4l2_fwnode_endpoint *vep, 264 enum v4l2_mbus_type bus_type) 265 { 266 struct v4l2_fwnode_bus_parallel *bus = &vep->bus.parallel; 267 unsigned int flags = 0; 268 u32 v; 269 270 if (bus_type == V4L2_MBUS_PARALLEL || bus_type == V4L2_MBUS_BT656) 271 flags = bus->flags; 272 273 if (!fwnode_property_read_u32(fwnode, "hsync-active", &v)) { 274 flags &= ~(V4L2_MBUS_HSYNC_ACTIVE_HIGH | 275 V4L2_MBUS_HSYNC_ACTIVE_LOW); 276 flags |= v ? V4L2_MBUS_HSYNC_ACTIVE_HIGH : 277 V4L2_MBUS_HSYNC_ACTIVE_LOW; 278 pr_debug("hsync-active %s\n", v ? "high" : "low"); 279 } 280 281 if (!fwnode_property_read_u32(fwnode, "vsync-active", &v)) { 282 flags &= ~(V4L2_MBUS_VSYNC_ACTIVE_HIGH | 283 V4L2_MBUS_VSYNC_ACTIVE_LOW); 284 flags |= v ? V4L2_MBUS_VSYNC_ACTIVE_HIGH : 285 V4L2_MBUS_VSYNC_ACTIVE_LOW; 286 pr_debug("vsync-active %s\n", v ? "high" : "low"); 287 } 288 289 if (!fwnode_property_read_u32(fwnode, "field-even-active", &v)) { 290 flags &= ~(V4L2_MBUS_FIELD_EVEN_HIGH | 291 V4L2_MBUS_FIELD_EVEN_LOW); 292 flags |= v ? V4L2_MBUS_FIELD_EVEN_HIGH : 293 V4L2_MBUS_FIELD_EVEN_LOW; 294 pr_debug("field-even-active %s\n", v ? "high" : "low"); 295 } 296 297 if (!fwnode_property_read_u32(fwnode, "pclk-sample", &v)) { 298 flags &= ~(V4L2_MBUS_PCLK_SAMPLE_RISING | 299 V4L2_MBUS_PCLK_SAMPLE_FALLING); 300 flags |= v ? V4L2_MBUS_PCLK_SAMPLE_RISING : 301 V4L2_MBUS_PCLK_SAMPLE_FALLING; 302 pr_debug("pclk-sample %s\n", v ? "high" : "low"); 303 } 304 305 if (!fwnode_property_read_u32(fwnode, "data-active", &v)) { 306 flags &= ~(V4L2_MBUS_DATA_ACTIVE_HIGH | 307 V4L2_MBUS_DATA_ACTIVE_LOW); 308 flags |= v ? V4L2_MBUS_DATA_ACTIVE_HIGH : 309 V4L2_MBUS_DATA_ACTIVE_LOW; 310 pr_debug("data-active %s\n", v ? "high" : "low"); 311 } 312 313 if (fwnode_property_present(fwnode, "slave-mode")) { 314 pr_debug("slave mode\n"); 315 flags &= ~V4L2_MBUS_MASTER; 316 flags |= V4L2_MBUS_SLAVE; 317 } else { 318 flags &= ~V4L2_MBUS_SLAVE; 319 flags |= V4L2_MBUS_MASTER; 320 } 321 322 if (!fwnode_property_read_u32(fwnode, "bus-width", &v)) { 323 bus->bus_width = v; 324 pr_debug("bus-width %u\n", v); 325 } 326 327 if (!fwnode_property_read_u32(fwnode, "data-shift", &v)) { 328 bus->data_shift = v; 329 pr_debug("data-shift %u\n", v); 330 } 331 332 if (!fwnode_property_read_u32(fwnode, "sync-on-green-active", &v)) { 333 flags &= ~(V4L2_MBUS_VIDEO_SOG_ACTIVE_HIGH | 334 V4L2_MBUS_VIDEO_SOG_ACTIVE_LOW); 335 flags |= v ? V4L2_MBUS_VIDEO_SOG_ACTIVE_HIGH : 336 V4L2_MBUS_VIDEO_SOG_ACTIVE_LOW; 337 pr_debug("sync-on-green-active %s\n", v ? "high" : "low"); 338 } 339 340 if (!fwnode_property_read_u32(fwnode, "data-enable-active", &v)) { 341 flags &= ~(V4L2_MBUS_DATA_ENABLE_HIGH | 342 V4L2_MBUS_DATA_ENABLE_LOW); 343 flags |= v ? V4L2_MBUS_DATA_ENABLE_HIGH : 344 V4L2_MBUS_DATA_ENABLE_LOW; 345 pr_debug("data-enable-active %s\n", v ? "high" : "low"); 346 } 347 348 switch (bus_type) { 349 default: 350 bus->flags = flags; 351 if (flags & PARALLEL_MBUS_FLAGS) 352 vep->bus_type = V4L2_MBUS_PARALLEL; 353 else 354 vep->bus_type = V4L2_MBUS_BT656; 355 break; 356 case V4L2_MBUS_PARALLEL: 357 vep->bus_type = V4L2_MBUS_PARALLEL; 358 bus->flags = flags; 359 break; 360 case V4L2_MBUS_BT656: 361 vep->bus_type = V4L2_MBUS_BT656; 362 bus->flags = flags & ~PARALLEL_MBUS_FLAGS; 363 break; 364 } 365 } 366 367 static void 368 v4l2_fwnode_endpoint_parse_csi1_bus(struct fwnode_handle *fwnode, 369 struct v4l2_fwnode_endpoint *vep, 370 enum v4l2_mbus_type bus_type) 371 { 372 struct v4l2_fwnode_bus_mipi_csi1 *bus = &vep->bus.mipi_csi1; 373 u32 v; 374 375 if (!fwnode_property_read_u32(fwnode, "clock-inv", &v)) { 376 bus->clock_inv = v; 377 pr_debug("clock-inv %u\n", v); 378 } 379 380 if (!fwnode_property_read_u32(fwnode, "strobe", &v)) { 381 bus->strobe = v; 382 pr_debug("strobe %u\n", v); 383 } 384 385 if (!fwnode_property_read_u32(fwnode, "data-lanes", &v)) { 386 bus->data_lane = v; 387 pr_debug("data-lanes %u\n", v); 388 } 389 390 if (!fwnode_property_read_u32(fwnode, "clock-lanes", &v)) { 391 bus->clock_lane = v; 392 pr_debug("clock-lanes %u\n", v); 393 } 394 395 if (bus_type == V4L2_MBUS_CCP2) 396 vep->bus_type = V4L2_MBUS_CCP2; 397 else 398 vep->bus_type = V4L2_MBUS_CSI1; 399 } 400 401 static int __v4l2_fwnode_endpoint_parse(struct fwnode_handle *fwnode, 402 struct v4l2_fwnode_endpoint *vep) 403 { 404 u32 bus_type = V4L2_FWNODE_BUS_TYPE_GUESS; 405 enum v4l2_mbus_type mbus_type; 406 int rval; 407 408 pr_debug("===== begin parsing endpoint %pfw\n", fwnode); 409 410 fwnode_property_read_u32(fwnode, "bus-type", &bus_type); 411 pr_debug("fwnode video bus type %s (%u), mbus type %s (%u)\n", 412 v4l2_fwnode_bus_type_to_string(bus_type), bus_type, 413 v4l2_fwnode_mbus_type_to_string(vep->bus_type), 414 vep->bus_type); 415 mbus_type = v4l2_fwnode_bus_type_to_mbus(bus_type); 416 if (mbus_type == V4L2_MBUS_INVALID) { 417 pr_debug("unsupported bus type %u\n", bus_type); 418 return -EINVAL; 419 } 420 421 if (vep->bus_type != V4L2_MBUS_UNKNOWN) { 422 if (mbus_type != V4L2_MBUS_UNKNOWN && 423 vep->bus_type != mbus_type) { 424 pr_debug("expecting bus type %s\n", 425 v4l2_fwnode_mbus_type_to_string(vep->bus_type)); 426 return -ENXIO; 427 } 428 } else { 429 vep->bus_type = mbus_type; 430 } 431 432 switch (vep->bus_type) { 433 case V4L2_MBUS_UNKNOWN: 434 rval = v4l2_fwnode_endpoint_parse_csi2_bus(fwnode, vep, 435 V4L2_MBUS_UNKNOWN); 436 if (rval) 437 return rval; 438 439 if (vep->bus_type == V4L2_MBUS_UNKNOWN) 440 v4l2_fwnode_endpoint_parse_parallel_bus(fwnode, vep, 441 V4L2_MBUS_UNKNOWN); 442 443 pr_debug("assuming media bus type %s (%u)\n", 444 v4l2_fwnode_mbus_type_to_string(vep->bus_type), 445 vep->bus_type); 446 447 break; 448 case V4L2_MBUS_CCP2: 449 case V4L2_MBUS_CSI1: 450 v4l2_fwnode_endpoint_parse_csi1_bus(fwnode, vep, vep->bus_type); 451 452 break; 453 case V4L2_MBUS_CSI2_DPHY: 454 case V4L2_MBUS_CSI2_CPHY: 455 rval = v4l2_fwnode_endpoint_parse_csi2_bus(fwnode, vep, 456 vep->bus_type); 457 if (rval) 458 return rval; 459 460 break; 461 case V4L2_MBUS_PARALLEL: 462 case V4L2_MBUS_BT656: 463 v4l2_fwnode_endpoint_parse_parallel_bus(fwnode, vep, 464 vep->bus_type); 465 466 break; 467 default: 468 pr_warn("unsupported bus type %u\n", mbus_type); 469 return -EINVAL; 470 } 471 472 fwnode_graph_parse_endpoint(fwnode, &vep->base); 473 474 return 0; 475 } 476 477 int v4l2_fwnode_endpoint_parse(struct fwnode_handle *fwnode, 478 struct v4l2_fwnode_endpoint *vep) 479 { 480 int ret; 481 482 ret = __v4l2_fwnode_endpoint_parse(fwnode, vep); 483 484 pr_debug("===== end parsing endpoint %pfw\n", fwnode); 485 486 return ret; 487 } 488 EXPORT_SYMBOL_GPL(v4l2_fwnode_endpoint_parse); 489 490 void v4l2_fwnode_endpoint_free(struct v4l2_fwnode_endpoint *vep) 491 { 492 if (IS_ERR_OR_NULL(vep)) 493 return; 494 495 kfree(vep->link_frequencies); 496 vep->link_frequencies = NULL; 497 } 498 EXPORT_SYMBOL_GPL(v4l2_fwnode_endpoint_free); 499 500 int v4l2_fwnode_endpoint_alloc_parse(struct fwnode_handle *fwnode, 501 struct v4l2_fwnode_endpoint *vep) 502 { 503 int rval; 504 505 rval = __v4l2_fwnode_endpoint_parse(fwnode, vep); 506 if (rval < 0) 507 return rval; 508 509 rval = fwnode_property_count_u64(fwnode, "link-frequencies"); 510 if (rval > 0) { 511 unsigned int i; 512 513 vep->link_frequencies = 514 kmalloc_array(rval, sizeof(*vep->link_frequencies), 515 GFP_KERNEL); 516 if (!vep->link_frequencies) 517 return -ENOMEM; 518 519 vep->nr_of_link_frequencies = rval; 520 521 rval = fwnode_property_read_u64_array(fwnode, 522 "link-frequencies", 523 vep->link_frequencies, 524 vep->nr_of_link_frequencies); 525 if (rval < 0) { 526 v4l2_fwnode_endpoint_free(vep); 527 return rval; 528 } 529 530 for (i = 0; i < vep->nr_of_link_frequencies; i++) 531 pr_debug("link-frequencies %u value %llu\n", i, 532 vep->link_frequencies[i]); 533 } 534 535 pr_debug("===== end parsing endpoint %pfw\n", fwnode); 536 537 return 0; 538 } 539 EXPORT_SYMBOL_GPL(v4l2_fwnode_endpoint_alloc_parse); 540 541 int v4l2_fwnode_parse_link(struct fwnode_handle *fwnode, 542 struct v4l2_fwnode_link *link) 543 { 544 struct fwnode_endpoint fwep; 545 546 memset(link, 0, sizeof(*link)); 547 548 fwnode_graph_parse_endpoint(fwnode, &fwep); 549 link->local_id = fwep.id; 550 link->local_port = fwep.port; 551 link->local_node = fwnode_graph_get_port_parent(fwnode); 552 553 fwnode = fwnode_graph_get_remote_endpoint(fwnode); 554 if (!fwnode) { 555 fwnode_handle_put(fwnode); 556 return -ENOLINK; 557 } 558 559 fwnode_graph_parse_endpoint(fwnode, &fwep); 560 link->remote_id = fwep.id; 561 link->remote_port = fwep.port; 562 link->remote_node = fwnode_graph_get_port_parent(fwnode); 563 564 return 0; 565 } 566 EXPORT_SYMBOL_GPL(v4l2_fwnode_parse_link); 567 568 void v4l2_fwnode_put_link(struct v4l2_fwnode_link *link) 569 { 570 fwnode_handle_put(link->local_node); 571 fwnode_handle_put(link->remote_node); 572 } 573 EXPORT_SYMBOL_GPL(v4l2_fwnode_put_link); 574 575 static const struct v4l2_fwnode_connector_conv { 576 enum v4l2_connector_type type; 577 const char *compatible; 578 } connectors[] = { 579 { 580 .type = V4L2_CONN_COMPOSITE, 581 .compatible = "composite-video-connector", 582 }, { 583 .type = V4L2_CONN_SVIDEO, 584 .compatible = "svideo-connector", 585 }, 586 }; 587 588 static enum v4l2_connector_type 589 v4l2_fwnode_string_to_connector_type(const char *con_str) 590 { 591 unsigned int i; 592 593 for (i = 0; i < ARRAY_SIZE(connectors); i++) 594 if (!strcmp(con_str, connectors[i].compatible)) 595 return connectors[i].type; 596 597 return V4L2_CONN_UNKNOWN; 598 } 599 600 static void 601 v4l2_fwnode_connector_parse_analog(struct fwnode_handle *fwnode, 602 struct v4l2_fwnode_connector *vc) 603 { 604 u32 stds; 605 int ret; 606 607 ret = fwnode_property_read_u32(fwnode, "sdtv-standards", &stds); 608 609 /* The property is optional. */ 610 vc->connector.analog.sdtv_stds = ret ? V4L2_STD_ALL : stds; 611 } 612 613 void v4l2_fwnode_connector_free(struct v4l2_fwnode_connector *connector) 614 { 615 struct v4l2_connector_link *link, *tmp; 616 617 if (IS_ERR_OR_NULL(connector) || connector->type == V4L2_CONN_UNKNOWN) 618 return; 619 620 list_for_each_entry_safe(link, tmp, &connector->links, head) { 621 v4l2_fwnode_put_link(&link->fwnode_link); 622 list_del(&link->head); 623 kfree(link); 624 } 625 626 kfree(connector->label); 627 connector->label = NULL; 628 connector->type = V4L2_CONN_UNKNOWN; 629 } 630 EXPORT_SYMBOL_GPL(v4l2_fwnode_connector_free); 631 632 static enum v4l2_connector_type 633 v4l2_fwnode_get_connector_type(struct fwnode_handle *fwnode) 634 { 635 const char *type_name; 636 int err; 637 638 if (!fwnode) 639 return V4L2_CONN_UNKNOWN; 640 641 /* The connector-type is stored within the compatible string. */ 642 err = fwnode_property_read_string(fwnode, "compatible", &type_name); 643 if (err) 644 return V4L2_CONN_UNKNOWN; 645 646 return v4l2_fwnode_string_to_connector_type(type_name); 647 } 648 649 int v4l2_fwnode_connector_parse(struct fwnode_handle *fwnode, 650 struct v4l2_fwnode_connector *connector) 651 { 652 struct fwnode_handle *connector_node; 653 enum v4l2_connector_type connector_type; 654 const char *label; 655 int err; 656 657 if (!fwnode) 658 return -EINVAL; 659 660 memset(connector, 0, sizeof(*connector)); 661 662 INIT_LIST_HEAD(&connector->links); 663 664 connector_node = fwnode_graph_get_port_parent(fwnode); 665 connector_type = v4l2_fwnode_get_connector_type(connector_node); 666 if (connector_type == V4L2_CONN_UNKNOWN) { 667 fwnode_handle_put(connector_node); 668 connector_node = fwnode_graph_get_remote_port_parent(fwnode); 669 connector_type = v4l2_fwnode_get_connector_type(connector_node); 670 } 671 672 if (connector_type == V4L2_CONN_UNKNOWN) { 673 pr_err("Unknown connector type\n"); 674 err = -ENOTCONN; 675 goto out; 676 } 677 678 connector->type = connector_type; 679 connector->name = fwnode_get_name(connector_node); 680 err = fwnode_property_read_string(connector_node, "label", &label); 681 connector->label = err ? NULL : kstrdup_const(label, GFP_KERNEL); 682 683 /* Parse the connector specific properties. */ 684 switch (connector->type) { 685 case V4L2_CONN_COMPOSITE: 686 case V4L2_CONN_SVIDEO: 687 v4l2_fwnode_connector_parse_analog(connector_node, connector); 688 break; 689 /* Avoid compiler warnings */ 690 case V4L2_CONN_UNKNOWN: 691 break; 692 } 693 694 out: 695 fwnode_handle_put(connector_node); 696 697 return err; 698 } 699 EXPORT_SYMBOL_GPL(v4l2_fwnode_connector_parse); 700 701 int v4l2_fwnode_connector_add_link(struct fwnode_handle *fwnode, 702 struct v4l2_fwnode_connector *connector) 703 { 704 struct fwnode_handle *connector_ep; 705 struct v4l2_connector_link *link; 706 int err; 707 708 if (!fwnode || !connector || connector->type == V4L2_CONN_UNKNOWN) 709 return -EINVAL; 710 711 connector_ep = fwnode_graph_get_remote_endpoint(fwnode); 712 if (!connector_ep) 713 return -ENOTCONN; 714 715 link = kzalloc(sizeof(*link), GFP_KERNEL); 716 if (!link) { 717 err = -ENOMEM; 718 goto err; 719 } 720 721 err = v4l2_fwnode_parse_link(connector_ep, &link->fwnode_link); 722 if (err) 723 goto err; 724 725 fwnode_handle_put(connector_ep); 726 727 list_add(&link->head, &connector->links); 728 connector->nr_of_links++; 729 730 return 0; 731 732 err: 733 kfree(link); 734 fwnode_handle_put(connector_ep); 735 736 return err; 737 } 738 EXPORT_SYMBOL_GPL(v4l2_fwnode_connector_add_link); 739 740 int v4l2_fwnode_device_parse(struct device *dev, 741 struct v4l2_fwnode_device_properties *props) 742 { 743 struct fwnode_handle *fwnode = dev_fwnode(dev); 744 u32 val; 745 int ret; 746 747 memset(props, 0, sizeof(*props)); 748 749 props->orientation = V4L2_FWNODE_PROPERTY_UNSET; 750 ret = fwnode_property_read_u32(fwnode, "orientation", &val); 751 if (!ret) { 752 switch (val) { 753 case V4L2_FWNODE_ORIENTATION_FRONT: 754 case V4L2_FWNODE_ORIENTATION_BACK: 755 case V4L2_FWNODE_ORIENTATION_EXTERNAL: 756 break; 757 default: 758 dev_warn(dev, "Unsupported device orientation: %u\n", val); 759 return -EINVAL; 760 } 761 762 props->orientation = val; 763 dev_dbg(dev, "device orientation: %u\n", val); 764 } 765 766 props->rotation = V4L2_FWNODE_PROPERTY_UNSET; 767 ret = fwnode_property_read_u32(fwnode, "rotation", &val); 768 if (!ret) { 769 if (val >= 360) { 770 dev_warn(dev, "Unsupported device rotation: %u\n", val); 771 return -EINVAL; 772 } 773 774 props->rotation = val; 775 dev_dbg(dev, "device rotation: %u\n", val); 776 } 777 778 return 0; 779 } 780 EXPORT_SYMBOL_GPL(v4l2_fwnode_device_parse); 781 782 static int 783 v4l2_async_notifier_fwnode_parse_endpoint(struct device *dev, 784 struct v4l2_async_notifier *notifier, 785 struct fwnode_handle *endpoint, 786 unsigned int asd_struct_size, 787 parse_endpoint_func parse_endpoint) 788 { 789 struct v4l2_fwnode_endpoint vep = { .bus_type = 0 }; 790 struct v4l2_async_subdev *asd; 791 int ret; 792 793 asd = kzalloc(asd_struct_size, GFP_KERNEL); 794 if (!asd) 795 return -ENOMEM; 796 797 asd->match_type = V4L2_ASYNC_MATCH_FWNODE; 798 asd->match.fwnode = 799 fwnode_graph_get_remote_port_parent(endpoint); 800 if (!asd->match.fwnode) { 801 dev_dbg(dev, "no remote endpoint found\n"); 802 ret = -ENOTCONN; 803 goto out_err; 804 } 805 806 ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &vep); 807 if (ret) { 808 dev_warn(dev, "unable to parse V4L2 fwnode endpoint (%d)\n", 809 ret); 810 goto out_err; 811 } 812 813 ret = parse_endpoint ? parse_endpoint(dev, &vep, asd) : 0; 814 if (ret == -ENOTCONN) 815 dev_dbg(dev, "ignoring port@%u/endpoint@%u\n", vep.base.port, 816 vep.base.id); 817 else if (ret < 0) 818 dev_warn(dev, 819 "driver could not parse port@%u/endpoint@%u (%d)\n", 820 vep.base.port, vep.base.id, ret); 821 v4l2_fwnode_endpoint_free(&vep); 822 if (ret < 0) 823 goto out_err; 824 825 ret = __v4l2_async_notifier_add_subdev(notifier, asd); 826 if (ret < 0) { 827 /* not an error if asd already exists */ 828 if (ret == -EEXIST) 829 ret = 0; 830 goto out_err; 831 } 832 833 return 0; 834 835 out_err: 836 fwnode_handle_put(asd->match.fwnode); 837 kfree(asd); 838 839 return ret == -ENOTCONN ? 0 : ret; 840 } 841 842 static int 843 __v4l2_async_notifier_parse_fwnode_ep(struct device *dev, 844 struct v4l2_async_notifier *notifier, 845 size_t asd_struct_size, 846 unsigned int port, 847 bool has_port, 848 parse_endpoint_func parse_endpoint) 849 { 850 struct fwnode_handle *fwnode; 851 int ret = 0; 852 853 if (WARN_ON(asd_struct_size < sizeof(struct v4l2_async_subdev))) 854 return -EINVAL; 855 856 fwnode_graph_for_each_endpoint(dev_fwnode(dev), fwnode) { 857 struct fwnode_handle *dev_fwnode; 858 bool is_available; 859 860 dev_fwnode = fwnode_graph_get_port_parent(fwnode); 861 is_available = fwnode_device_is_available(dev_fwnode); 862 fwnode_handle_put(dev_fwnode); 863 if (!is_available) 864 continue; 865 866 if (has_port) { 867 struct fwnode_endpoint ep; 868 869 ret = fwnode_graph_parse_endpoint(fwnode, &ep); 870 if (ret) 871 break; 872 873 if (ep.port != port) 874 continue; 875 } 876 877 ret = v4l2_async_notifier_fwnode_parse_endpoint(dev, 878 notifier, 879 fwnode, 880 asd_struct_size, 881 parse_endpoint); 882 if (ret < 0) 883 break; 884 } 885 886 fwnode_handle_put(fwnode); 887 888 return ret; 889 } 890 891 int 892 v4l2_async_notifier_parse_fwnode_endpoints(struct device *dev, 893 struct v4l2_async_notifier *notifier, 894 size_t asd_struct_size, 895 parse_endpoint_func parse_endpoint) 896 { 897 return __v4l2_async_notifier_parse_fwnode_ep(dev, notifier, 898 asd_struct_size, 0, 899 false, parse_endpoint); 900 } 901 EXPORT_SYMBOL_GPL(v4l2_async_notifier_parse_fwnode_endpoints); 902 903 /* 904 * v4l2_fwnode_reference_parse - parse references for async sub-devices 905 * @dev: the device node the properties of which are parsed for references 906 * @notifier: the async notifier where the async subdevs will be added 907 * @prop: the name of the property 908 * 909 * Return: 0 on success 910 * -ENOENT if no entries were found 911 * -ENOMEM if memory allocation failed 912 * -EINVAL if property parsing failed 913 */ 914 static int v4l2_fwnode_reference_parse(struct device *dev, 915 struct v4l2_async_notifier *notifier, 916 const char *prop) 917 { 918 struct fwnode_reference_args args; 919 unsigned int index; 920 int ret; 921 922 for (index = 0; 923 !(ret = fwnode_property_get_reference_args(dev_fwnode(dev), 924 prop, NULL, 0, 925 index, &args)); 926 index++) 927 fwnode_handle_put(args.fwnode); 928 929 if (!index) 930 return -ENOENT; 931 932 /* 933 * Note that right now both -ENODATA and -ENOENT may signal 934 * out-of-bounds access. Return the error in cases other than that. 935 */ 936 if (ret != -ENOENT && ret != -ENODATA) 937 return ret; 938 939 for (index = 0; 940 !fwnode_property_get_reference_args(dev_fwnode(dev), prop, NULL, 941 0, index, &args); 942 index++) { 943 struct v4l2_async_subdev *asd; 944 945 asd = v4l2_async_notifier_add_fwnode_subdev(notifier, 946 args.fwnode, 947 struct v4l2_async_subdev); 948 fwnode_handle_put(args.fwnode); 949 if (IS_ERR(asd)) { 950 /* not an error if asd already exists */ 951 if (PTR_ERR(asd) == -EEXIST) 952 continue; 953 954 return PTR_ERR(asd); 955 } 956 } 957 958 return 0; 959 } 960 961 /* 962 * v4l2_fwnode_reference_get_int_prop - parse a reference with integer 963 * arguments 964 * @fwnode: fwnode to read @prop from 965 * @notifier: notifier for @dev 966 * @prop: the name of the property 967 * @index: the index of the reference to get 968 * @props: the array of integer property names 969 * @nprops: the number of integer property names in @nprops 970 * 971 * First find an fwnode referred to by the reference at @index in @prop. 972 * 973 * Then under that fwnode, @nprops times, for each property in @props, 974 * iteratively follow child nodes starting from fwnode such that they have the 975 * property in @props array at the index of the child node distance from the 976 * root node and the value of that property matching with the integer argument 977 * of the reference, at the same index. 978 * 979 * The child fwnode reached at the end of the iteration is then returned to the 980 * caller. 981 * 982 * The core reason for this is that you cannot refer to just any node in ACPI. 983 * So to refer to an endpoint (easy in DT) you need to refer to a device, then 984 * provide a list of (property name, property value) tuples where each tuple 985 * uniquely identifies a child node. The first tuple identifies a child directly 986 * underneath the device fwnode, the next tuple identifies a child node 987 * underneath the fwnode identified by the previous tuple, etc. until you 988 * reached the fwnode you need. 989 * 990 * THIS EXAMPLE EXISTS MERELY TO DOCUMENT THIS FUNCTION. DO NOT USE IT AS A 991 * REFERENCE IN HOW ACPI TABLES SHOULD BE WRITTEN!! See documentation under 992 * Documentation/firmware-guide/acpi/dsd/ instead and especially graph.txt, 993 * data-node-references.txt and leds.txt . 994 * 995 * Scope (\_SB.PCI0.I2C2) 996 * { 997 * Device (CAM0) 998 * { 999 * Name (_DSD, Package () { 1000 * ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), 1001 * Package () { 1002 * Package () { 1003 * "compatible", 1004 * Package () { "nokia,smia" } 1005 * }, 1006 * }, 1007 * ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), 1008 * Package () { 1009 * Package () { "port0", "PRT0" }, 1010 * } 1011 * }) 1012 * Name (PRT0, Package() { 1013 * ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), 1014 * Package () { 1015 * Package () { "port", 0 }, 1016 * }, 1017 * ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), 1018 * Package () { 1019 * Package () { "endpoint0", "EP00" }, 1020 * } 1021 * }) 1022 * Name (EP00, Package() { 1023 * ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), 1024 * Package () { 1025 * Package () { "endpoint", 0 }, 1026 * Package () { 1027 * "remote-endpoint", 1028 * Package() { 1029 * \_SB.PCI0.ISP, 4, 0 1030 * } 1031 * }, 1032 * } 1033 * }) 1034 * } 1035 * } 1036 * 1037 * Scope (\_SB.PCI0) 1038 * { 1039 * Device (ISP) 1040 * { 1041 * Name (_DSD, Package () { 1042 * ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), 1043 * Package () { 1044 * Package () { "port4", "PRT4" }, 1045 * } 1046 * }) 1047 * 1048 * Name (PRT4, Package() { 1049 * ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), 1050 * Package () { 1051 * Package () { "port", 4 }, 1052 * }, 1053 * ToUUID("dbb8e3e6-5886-4ba6-8795-1319f52a966b"), 1054 * Package () { 1055 * Package () { "endpoint0", "EP40" }, 1056 * } 1057 * }) 1058 * 1059 * Name (EP40, Package() { 1060 * ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), 1061 * Package () { 1062 * Package () { "endpoint", 0 }, 1063 * Package () { 1064 * "remote-endpoint", 1065 * Package () { 1066 * \_SB.PCI0.I2C2.CAM0, 1067 * 0, 0 1068 * } 1069 * }, 1070 * } 1071 * }) 1072 * } 1073 * } 1074 * 1075 * From the EP40 node under ISP device, you could parse the graph remote 1076 * endpoint using v4l2_fwnode_reference_get_int_prop with these arguments: 1077 * 1078 * @fwnode: fwnode referring to EP40 under ISP. 1079 * @prop: "remote-endpoint" 1080 * @index: 0 1081 * @props: "port", "endpoint" 1082 * @nprops: 2 1083 * 1084 * And you'd get back fwnode referring to EP00 under CAM0. 1085 * 1086 * The same works the other way around: if you use EP00 under CAM0 as the 1087 * fwnode, you'll get fwnode referring to EP40 under ISP. 1088 * 1089 * The same example in DT syntax would look like this: 1090 * 1091 * cam: cam0 { 1092 * compatible = "nokia,smia"; 1093 * 1094 * port { 1095 * port = <0>; 1096 * endpoint { 1097 * endpoint = <0>; 1098 * remote-endpoint = <&isp 4 0>; 1099 * }; 1100 * }; 1101 * }; 1102 * 1103 * isp: isp { 1104 * ports { 1105 * port@4 { 1106 * port = <4>; 1107 * endpoint { 1108 * endpoint = <0>; 1109 * remote-endpoint = <&cam 0 0>; 1110 * }; 1111 * }; 1112 * }; 1113 * }; 1114 * 1115 * Return: 0 on success 1116 * -ENOENT if no entries (or the property itself) were found 1117 * -EINVAL if property parsing otherwise failed 1118 * -ENOMEM if memory allocation failed 1119 */ 1120 static struct fwnode_handle * 1121 v4l2_fwnode_reference_get_int_prop(struct fwnode_handle *fwnode, 1122 const char *prop, 1123 unsigned int index, 1124 const char * const *props, 1125 unsigned int nprops) 1126 { 1127 struct fwnode_reference_args fwnode_args; 1128 u64 *args = fwnode_args.args; 1129 struct fwnode_handle *child; 1130 int ret; 1131 1132 /* 1133 * Obtain remote fwnode as well as the integer arguments. 1134 * 1135 * Note that right now both -ENODATA and -ENOENT may signal 1136 * out-of-bounds access. Return -ENOENT in that case. 1137 */ 1138 ret = fwnode_property_get_reference_args(fwnode, prop, NULL, nprops, 1139 index, &fwnode_args); 1140 if (ret) 1141 return ERR_PTR(ret == -ENODATA ? -ENOENT : ret); 1142 1143 /* 1144 * Find a node in the tree under the referred fwnode corresponding to 1145 * the integer arguments. 1146 */ 1147 fwnode = fwnode_args.fwnode; 1148 while (nprops--) { 1149 u32 val; 1150 1151 /* Loop over all child nodes under fwnode. */ 1152 fwnode_for_each_child_node(fwnode, child) { 1153 if (fwnode_property_read_u32(child, *props, &val)) 1154 continue; 1155 1156 /* Found property, see if its value matches. */ 1157 if (val == *args) 1158 break; 1159 } 1160 1161 fwnode_handle_put(fwnode); 1162 1163 /* No property found; return an error here. */ 1164 if (!child) { 1165 fwnode = ERR_PTR(-ENOENT); 1166 break; 1167 } 1168 1169 props++; 1170 args++; 1171 fwnode = child; 1172 } 1173 1174 return fwnode; 1175 } 1176 1177 struct v4l2_fwnode_int_props { 1178 const char *name; 1179 const char * const *props; 1180 unsigned int nprops; 1181 }; 1182 1183 /* 1184 * v4l2_fwnode_reference_parse_int_props - parse references for async 1185 * sub-devices 1186 * @dev: struct device pointer 1187 * @notifier: notifier for @dev 1188 * @prop: the name of the property 1189 * @props: the array of integer property names 1190 * @nprops: the number of integer properties 1191 * 1192 * Use v4l2_fwnode_reference_get_int_prop to find fwnodes through reference in 1193 * property @prop with integer arguments with child nodes matching in properties 1194 * @props. Then, set up V4L2 async sub-devices for those fwnodes in the notifier 1195 * accordingly. 1196 * 1197 * While it is technically possible to use this function on DT, it is only 1198 * meaningful on ACPI. On Device tree you can refer to any node in the tree but 1199 * on ACPI the references are limited to devices. 1200 * 1201 * Return: 0 on success 1202 * -ENOENT if no entries (or the property itself) were found 1203 * -EINVAL if property parsing otherwisefailed 1204 * -ENOMEM if memory allocation failed 1205 */ 1206 static int 1207 v4l2_fwnode_reference_parse_int_props(struct device *dev, 1208 struct v4l2_async_notifier *notifier, 1209 const struct v4l2_fwnode_int_props *p) 1210 { 1211 struct fwnode_handle *fwnode; 1212 unsigned int index; 1213 int ret; 1214 const char *prop = p->name; 1215 const char * const *props = p->props; 1216 unsigned int nprops = p->nprops; 1217 1218 index = 0; 1219 do { 1220 fwnode = v4l2_fwnode_reference_get_int_prop(dev_fwnode(dev), 1221 prop, index, 1222 props, nprops); 1223 if (IS_ERR(fwnode)) { 1224 /* 1225 * Note that right now both -ENODATA and -ENOENT may 1226 * signal out-of-bounds access. Return the error in 1227 * cases other than that. 1228 */ 1229 if (PTR_ERR(fwnode) != -ENOENT && 1230 PTR_ERR(fwnode) != -ENODATA) 1231 return PTR_ERR(fwnode); 1232 break; 1233 } 1234 fwnode_handle_put(fwnode); 1235 index++; 1236 } while (1); 1237 1238 for (index = 0; 1239 !IS_ERR((fwnode = v4l2_fwnode_reference_get_int_prop(dev_fwnode(dev), 1240 prop, index, 1241 props, 1242 nprops))); 1243 index++) { 1244 struct v4l2_async_subdev *asd; 1245 1246 asd = v4l2_async_notifier_add_fwnode_subdev(notifier, fwnode, 1247 struct v4l2_async_subdev); 1248 fwnode_handle_put(fwnode); 1249 if (IS_ERR(asd)) { 1250 ret = PTR_ERR(asd); 1251 /* not an error if asd already exists */ 1252 if (ret == -EEXIST) 1253 continue; 1254 1255 return PTR_ERR(asd); 1256 } 1257 } 1258 1259 return !fwnode || PTR_ERR(fwnode) == -ENOENT ? 0 : PTR_ERR(fwnode); 1260 } 1261 1262 int v4l2_async_notifier_parse_fwnode_sensor_common(struct device *dev, 1263 struct v4l2_async_notifier *notifier) 1264 { 1265 static const char * const led_props[] = { "led" }; 1266 static const struct v4l2_fwnode_int_props props[] = { 1267 { "flash-leds", led_props, ARRAY_SIZE(led_props) }, 1268 { "lens-focus", NULL, 0 }, 1269 }; 1270 unsigned int i; 1271 1272 for (i = 0; i < ARRAY_SIZE(props); i++) { 1273 int ret; 1274 1275 if (props[i].props && is_acpi_node(dev_fwnode(dev))) 1276 ret = v4l2_fwnode_reference_parse_int_props(dev, 1277 notifier, 1278 &props[i]); 1279 else 1280 ret = v4l2_fwnode_reference_parse(dev, notifier, 1281 props[i].name); 1282 if (ret && ret != -ENOENT) { 1283 dev_warn(dev, "parsing property \"%s\" failed (%d)\n", 1284 props[i].name, ret); 1285 return ret; 1286 } 1287 } 1288 1289 return 0; 1290 } 1291 EXPORT_SYMBOL_GPL(v4l2_async_notifier_parse_fwnode_sensor_common); 1292 1293 int v4l2_async_register_subdev_sensor_common(struct v4l2_subdev *sd) 1294 { 1295 struct v4l2_async_notifier *notifier; 1296 int ret; 1297 1298 if (WARN_ON(!sd->dev)) 1299 return -ENODEV; 1300 1301 notifier = kzalloc(sizeof(*notifier), GFP_KERNEL); 1302 if (!notifier) 1303 return -ENOMEM; 1304 1305 v4l2_async_notifier_init(notifier); 1306 1307 ret = v4l2_async_notifier_parse_fwnode_sensor_common(sd->dev, 1308 notifier); 1309 if (ret < 0) 1310 goto out_cleanup; 1311 1312 ret = v4l2_async_subdev_notifier_register(sd, notifier); 1313 if (ret < 0) 1314 goto out_cleanup; 1315 1316 ret = v4l2_async_register_subdev(sd); 1317 if (ret < 0) 1318 goto out_unregister; 1319 1320 sd->subdev_notifier = notifier; 1321 1322 return 0; 1323 1324 out_unregister: 1325 v4l2_async_notifier_unregister(notifier); 1326 1327 out_cleanup: 1328 v4l2_async_notifier_cleanup(notifier); 1329 kfree(notifier); 1330 1331 return ret; 1332 } 1333 EXPORT_SYMBOL_GPL(v4l2_async_register_subdev_sensor_common); 1334 1335 MODULE_LICENSE("GPL"); 1336 MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>"); 1337 MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); 1338 MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>"); 1339