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