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