{"id":1213103,"url":"http://patchwork.ozlabs.org/api/patches/1213103/?format=json","web_url":"http://patchwork.ozlabs.org/project/linux-gpio/patch/c7abf8d15ea54fee504fbec5666d013c26d3b62a.1576745635.git.matti.vaittinen@fi.rohmeurope.com/","project":{"id":42,"url":"http://patchwork.ozlabs.org/api/projects/42/?format=json","name":"Linux GPIO development","link_name":"linux-gpio","list_id":"linux-gpio.vger.kernel.org","list_email":"linux-gpio@vger.kernel.org","web_url":"","scm_url":"","webscm_url":"","list_archive_url":"","list_archive_url_format":"","commit_url_format":""},"msgid":"<c7abf8d15ea54fee504fbec5666d013c26d3b62a.1576745635.git.matti.vaittinen@fi.rohmeurope.com>","list_archive_url":null,"date":"2019-12-19T09:54:18","name":"[v7,11/12] leds: Add common LED binding parsing support to LED class/core","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"2e4932840da376bb855695f966684a6b94458871","submitter":{"id":74146,"url":"http://patchwork.ozlabs.org/api/people/74146/?format=json","name":"Matti Vaittinen","email":"matti.vaittinen@fi.rohmeurope.com"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/linux-gpio/patch/c7abf8d15ea54fee504fbec5666d013c26d3b62a.1576745635.git.matti.vaittinen@fi.rohmeurope.com/mbox/","series":[{"id":149534,"url":"http://patchwork.ozlabs.org/api/series/149534/?format=json","web_url":"http://patchwork.ozlabs.org/project/linux-gpio/list/?series=149534","date":"2019-12-19T09:44:08","name":"Support ROHM BD71828 PMIC","version":7,"mbox":"http://patchwork.ozlabs.org/series/149534/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/1213103/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/1213103/checks/","tags":{},"related":[],"headers":{"Return-Path":"<linux-gpio-owner@vger.kernel.org>","X-Original-To":"incoming@patchwork.ozlabs.org","Delivered-To":"patchwork-incoming@bilbo.ozlabs.org","Authentication-Results":["ozlabs.org; spf=none (no SPF record)\n\tsmtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67;\n\thelo=vger.kernel.org;\n\tenvelope-from=linux-gpio-owner@vger.kernel.org;\n\treceiver=<UNKNOWN>)","ozlabs.org; dmarc=none (p=none dis=none)\n\theader.from=fi.rohmeurope.com"],"Received":["from vger.kernel.org (vger.kernel.org [209.132.180.67])\n\tby ozlabs.org (Postfix) with ESMTP id 47dnKS3JFQz9sQp\n\tfor <incoming@patchwork.ozlabs.org>;\n\tThu, 19 Dec 2019 20:54:36 +1100 (AEDT)","(majordomo@vger.kernel.org) by vger.kernel.org via listexpand\n\tid S1726692AbfLSJyb (ORCPT <rfc822;incoming@patchwork.ozlabs.org>);\n\tThu, 19 Dec 2019 04:54:31 -0500","from mail-lf1-f65.google.com ([209.85.167.65]:36492 \"EHLO\n\tmail-lf1-f65.google.com\" rhost-flags-OK-OK-OK-OK) by vger.kernel.org\n\twith ESMTP id S1726633AbfLSJyb (ORCPT\n\t<rfc822; linux-gpio@vger.kernel.org>); Thu, 19 Dec 2019 04:54:31 -0500","by mail-lf1-f65.google.com with SMTP id n12so3897483lfe.3;\n\tThu, 19 Dec 2019 01:54:27 -0800 (PST)","from localhost.localdomain\n\t(dyt4gctb359myxd0pkwmt-4.rev.dnainternet.fi.\n\t[2001:14bb:430:5140:37cf:5409:8fcc:4495])\n\tby smtp.gmail.com with ESMTPSA id\n\tz13sm2538723ljh.21.2019.12.19.01.54.24\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tThu, 19 Dec 2019 01:54:26 -0800 (PST)"],"X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20161025;\n\th=x-gm-message-state:date:from:to:cc:subject:message-id:references\n\t:mime-version:content-disposition:in-reply-to:user-agent;\n\tbh=myEo0UV715T/+/3HpUNFGJIDrNPp+qqvkMFzsaFr0PM=;\n\tb=QMUl0KNQ62YLm4L2GMopzqZnlZ389oaav7YoOaXAkBxBvMkmCyNbiHctYupRFM7VUd\n\t9FazMY+pZmu8r1wvIheQ1c7bwlrE6Vw7H5U/hle/tFSfXRhOT/sw+p2bNYK6hC8Jy2hU\n\t4eZPEHfblt4T7UIQUW1PNQ9nC266hZVJIpmA1/KoaPR405RpqvWKtQn8/pke6xHXp0Y0\n\tmAHIgzRq0JphkvWZlJkhIuX1+mZahyN4NrRhC6f66yUPO2HHg3Xj1FhqMnsED/SzCURy\n\tZ1QgHIkv3GZgLryoljU7OCxKGLLjSKrLUEJ3QMyZJ+IR1VTNS7nzpcJdcSdQoIBtwQ5R\n\tpixg==","X-Gm-Message-State":"APjAAAV1ptfm5CTosJ4r2xw+vmCMVdJp5XjN12BS9tf7XpoJGaXq8Weh\n\tMW1pcGqBHyy/JMc5npZ5DbS+EwOV","X-Google-Smtp-Source":"APXvYqxazlGN82HnIYud1AqUNzWRxTp4WhXEd61FUU8ZIQz1nEM2xrtW1HdNG3J1GCv5yEXK/BAALg==","X-Received":"by 2002:ac2:5e9b:: with SMTP id\n\tb27mr4810294lfq.147.1576749266751; \n\tThu, 19 Dec 2019 01:54:26 -0800 (PST)","Date":"Thu, 19 Dec 2019 11:54:18 +0200","From":"Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>","To":"matti.vaittinen@fi.rohmeurope.com, mazziesaccount@gmail.com","Cc":"Jacek Anaszewski <jacek.anaszewski@gmail.com>,\n\tPavel Machek <pavel@ucw.cz>, Dan Murphy <dmurphy@ti.com>,\n\tRob Herring <robh+dt@kernel.org>, Mark Rutland <mark.rutland@arm.com>,\n\tLee Jones <lee.jones@linaro.org>,\n\tMichael Turquette <mturquette@baylibre.com>,\n\tStephen Boyd <sboyd@kernel.org>,\n\tLinus Walleij <linus.walleij@linaro.org>,\n\tBartosz Golaszewski <bgolaszewski@baylibre.com>,\n\tLiam Girdwood <lgirdwood@gmail.com>, Mark Brown <broonie@kernel.org>,\n\tAlessandro Zummo <a.zummo@towertech.it>,\n\tAlexandre Belloni <alexandre.belloni@bootlin.com>,\n\tlinux-leds@vger.kernel.org, devicetree@vger.kernel.org,\n\tlinux-kernel@vger.kernel.org, linux-clk@vger.kernel.org,\n\tlinux-gpio@vger.kernel.org, linux-rtc@vger.kernel.org","Subject":"[PATCH v7 11/12] leds: Add common LED binding parsing support to LED\n\tclass/core","Message-ID":"<c7abf8d15ea54fee504fbec5666d013c26d3b62a.1576745635.git.matti.vaittinen@fi.rohmeurope.com>","References":"<cover.1576745635.git.matti.vaittinen@fi.rohmeurope.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=us-ascii","Content-Disposition":"inline","In-Reply-To":"<cover.1576745635.git.matti.vaittinen@fi.rohmeurope.com>","User-Agent":"Mutt/1.12.1 (2019-06-15)","Sender":"linux-gpio-owner@vger.kernel.org","Precedence":"bulk","List-ID":"<linux-gpio.vger.kernel.org>","X-Mailing-List":"linux-gpio@vger.kernel.org"},"content":"Qucik grep for 'for_each' or 'linux,default-trigger' or\n'default-state' under drivers/leds can tell quite a lot. It seems\nmultiple LED controller drivers implement the very similar looping\nthrough the child nodes in order to locate the LED nodes and read\nand support the common LED dt bindings. Implementing this same\nstuff for all LED controllers gets old pretty fast.\n\nThis commit adds support for locating the LED node (based on known\nnode names - or linux,led-compatible property) and handling of\nfew common LED properties.\n\nlinux,default-trigger,\ndefault-state (with the exception of keep),\n\n(in addition to already handled\nfunction-enumerator,\nfunction,\ncolor\nand label).\n\nRegarding the node look-up: If no init_data is given, then no\nnode-lookup is done and cdev name is used as such.\n\nIf init_data is goven but no starting point for node lookup - then\n(parent) device's own DT node is used. If no led-compatible is given,\nthen of_match is searched for. If neither led-compatible not of_match\nis given then device's own node or passed starting point are used as\nsuch.\n\nSigned-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>\n---\n\nNo changes since v6\n\n drivers/leds/led-class.c |  99 +++++++++++++--\n drivers/leds/led-core.c  | 258 ++++++++++++++++++++++++++++++++-------\n include/linux/leds.h     |  94 ++++++++++++--\n 3 files changed, 385 insertions(+), 66 deletions(-)","diff":"diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c\nindex 438774315e6c..edf641cec0be 100644\n--- a/drivers/leds/led-class.c\n+++ b/drivers/leds/led-class.c\n@@ -235,6 +235,29 @@ static int led_classdev_next_name(const char *init_name, char *name,\n \treturn i;\n }\n \n+static void led_add_props(struct led_classdev *ld, struct led_properties *props)\n+{\n+\tif (props->default_trigger)\n+\t\tld->default_trigger = props->default_trigger;\n+\t/*\n+\t * According to binding docs the LED is by-default turned OFF unless\n+\t * default_state is used to indicate it should be ON or that state\n+\t * should be kept as is\n+\t */\n+\tif (props->default_state) {\n+\t\tld->brightness = LED_OFF;\n+\t\tif (!strcmp(props->default_state, \"on\"))\n+\t\t\tld->brightness = LED_FULL;\n+\t/*\n+\t * We probably should not call the brightness_get prior calling\n+\t * the of_parse_cb if one is provided.\n+\t * Add a flag to advertice that state should be queried and kept as-is.\n+\t */\n+\t\telse if (!strcmp(props->default_state, \"keep\"))\n+\t\t\tprops->brightness_keep = true;\n+\t}\n+}\n+\n /**\n  * led_classdev_register_ext - register a new object of led_classdev class\n  *\t\t\t       with init data.\n@@ -251,22 +274,69 @@ int led_classdev_register_ext(struct device *parent,\n \tchar final_name[LED_MAX_NAME_SIZE];\n \tconst char *proposed_name = composed_name;\n \tint ret;\n-\n+\tstruct led_properties props = {0};\n+\tstruct fwnode_handle *fw;\n+\n+\t/*\n+\t * We don't try getting the name based on DT node if init-data is not\n+\t * given. We could see if we find LED properties from the device's node\n+\t * but that might change LED names for old users of\n+\t * led_classdev_register who have been providing the LED name in\n+\t * cdev->name. So we seek fwnode for names only if init_data is given\n+\t */\n \tif (init_data) {\n+\t\tled_cdev->init_data = init_data;\n \t\tif (init_data->devname_mandatory && !init_data->devicename) {\n \t\t\tdev_err(parent, \"Mandatory device name is missing\");\n \t\t\treturn -EINVAL;\n \t\t}\n-\t\tret = led_compose_name(parent, init_data, composed_name);\n+\n+\t\tfw = led_find_fwnode(parent, init_data);\n+\t\tif (IS_ERR(fw))\n+\t\t\treturn PTR_ERR(fw);\n+\n+\t\tif (fw) {\n+\t\t\t/*\n+\t\t\t * We did increase refcount for fwnode. Let's set a flag\n+\t\t\t * so we can decrease it during deregistration\n+\t\t\t */\n+\t\t\tled_cdev->fwnode_owned = true;\n+\n+\t\t\tret = led_parse_fwnode_props(parent, fw, &props);\n+\t\t\tif (ret)\n+\t\t\t\tgoto err_out;\n+\n+\t\t\tif (init_data->of_parse_cb)\n+\t\t\t\tret = init_data->of_parse_cb(led_cdev, fw,\n+\t\t\t\t\t\t\t     &props);\n+\t\t\tif (ret < 0)\n+\t\t\t\tgoto err_out;\n+\n+\t\t\t/*\n+\t\t\t * Handle the common LED properties only for drivers\n+\t\t\t * that explicitly request it. This allows us to test\n+\t\t\t * and convert drivers to utilize common LED parsing one\n+\t\t\t * by one.\n+\t\t\t */\n+\t\t\tif (init_data->parse_fwnode)\n+\t\t\t\tled_add_props(led_cdev, &props);\n+\n+\t\t} else {\n+\t\t\tled_cdev->fwnode_owned = false;\n+\t\t}\n+\t\tret = led_compose_name(parent, init_data, &props,\n+\t\t\t\t       composed_name);\n \t\tif (ret < 0)\n-\t\t\treturn ret;\n+\t\t\tgoto err_out;\n \t} else {\n \t\tproposed_name = led_cdev->name;\n+\t\tled_cdev->fwnode_owned = false;\n+\t\tfw = NULL;\n \t}\n \n \tret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name));\n \tif (ret < 0)\n-\t\treturn ret;\n+\t\tgoto err_out;\n \n \tmutex_init(&led_cdev->led_access);\n \tmutex_lock(&led_cdev->led_access);\n@@ -274,22 +344,28 @@ int led_classdev_register_ext(struct device *parent,\n \t\t\t\tled_cdev, led_cdev->groups, \"%s\", final_name);\n \tif (IS_ERR(led_cdev->dev)) {\n \t\tmutex_unlock(&led_cdev->led_access);\n-\t\treturn PTR_ERR(led_cdev->dev);\n+\t\tret = PTR_ERR(led_cdev->dev);\n+\t\tgoto err_out;\n \t}\n-\tif (init_data && init_data->fwnode)\n-\t\tled_cdev->dev->fwnode = init_data->fwnode;\n+\tif (fw)\n+\t\tled_cdev->dev->fwnode = fw;\n \n \tif (ret)\n \t\tdev_warn(parent, \"Led %s renamed to %s due to name collision\",\n \t\t\t\tled_cdev->name, dev_name(led_cdev->dev));\n \n+\tif (props.brightness_keep)\n+\t\tif (led_cdev->brightness_get)\n+\t\t\tled_cdev->brightness =\n+\t\t\t\t led_cdev->brightness_get(led_cdev);\n+\n \tif (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {\n \t\tret = led_add_brightness_hw_changed(led_cdev);\n \t\tif (ret) {\n \t\t\tdevice_unregister(led_cdev->dev);\n \t\t\tled_cdev->dev = NULL;\n \t\t\tmutex_unlock(&led_cdev->led_access);\n-\t\t\treturn ret;\n+\t\t\tgoto err_out;\n \t\t}\n \t}\n \n@@ -322,6 +398,10 @@ int led_classdev_register_ext(struct device *parent,\n \t\t\tled_cdev->name);\n \n \treturn 0;\n+err_out:\n+\tif (led_cdev->fwnode_owned)\n+\t\tfwnode_handle_put(fw);\n+\treturn ret;\n }\n EXPORT_SYMBOL_GPL(led_classdev_register_ext);\n \n@@ -355,6 +435,9 @@ void led_classdev_unregister(struct led_classdev *led_cdev)\n \tif (led_cdev->flags & LED_BRIGHT_HW_CHANGED)\n \t\tled_remove_brightness_hw_changed(led_cdev);\n \n+\tif (led_cdev->fwnode_owned)\n+\t\tfwnode_handle_put(led_cdev->dev->fwnode);\n+\n \tdevice_unregister(led_cdev->dev);\n \n \tdown_write(&leds_list_lock);\ndiff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c\nindex f1f718dbe0f8..529354bc4e8a 100644\n--- a/drivers/leds/led-core.c\n+++ b/drivers/leds/led-core.c\n@@ -365,70 +365,226 @@ void led_sysfs_enable(struct led_classdev *led_cdev)\n }\n EXPORT_SYMBOL_GPL(led_sysfs_enable);\n \n-static void led_parse_fwnode_props(struct device *dev,\n-\t\t\t\t   struct fwnode_handle *fwnode,\n-\t\t\t\t   struct led_properties *props)\n+static int fw_is_match(struct fwnode_handle *fw,\n+\t\t       struct led_fw_match_property *mp, void *val)\n {\n-\tint ret;\n+\tvoid *cmp = mp->raw_val;\n+\tint ret = -EINVAL;\n+\n+\tif (mp->raw_val) {\n+\t\tret = fwnode_property_read_u8_array(fw, mp->name, val,\n+\t\t\t\t\t\t    mp->size);\n+\t} else if (mp->intval) {\n+\t\tcmp = mp->intval;\n+\t\tswitch (mp->size) {\n+\t\tcase 1:\n+\t\t\tret = fwnode_property_read_u8_array(fw, mp->name, val,\n+\t\t\t\t\t\t    mp->size);\n+\t\t\tbreak;\n+\t\tcase 2:\n+\t\t\tret = fwnode_property_read_u16_array(fw, mp->name, val,\n+\t\t\t\t\t\t    mp->size);\n+\t\t\tbreak;\n+\t\tcase 4:\n+\t\t\tret = fwnode_property_read_u32_array(fw, mp->name, val,\n+\t\t\t\t\t\t    mp->size);\n+\t\t\tbreak;\n+\t\tcase 8:\n+\t\t\tret = fwnode_property_read_u64_array(fw, mp->name, val,\n+\t\t\t\t\t\t    mp->size);\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\tif (!ret && cmp)\n+\t\tif (!memcmp(val, cmp, mp->size))\n+\t\t\treturn 1;\n+\n+\treturn 0;\n+}\n+/**\n+ * led_find_fwnode - find fwnode for led\n+ * @parent\tLED controller device\n+ * @init_data\tled init data with match information\n+ *\n+ * Scans the firmware nodes and returns node matching the given init_data.\n+ * NOTE: Function increases refcount for found node. Caller must decrease\n+ * refcount using fwnode_handle_put when finished with node.\n+ */\n+struct fwnode_handle *led_find_fwnode(struct device *parent,\n+\t\t\t\t      struct led_init_data *init_data)\n+{\n+\tstruct fwnode_handle *fw;\n+\n+\t/*\n+\t * This should never be called W/O init data. We could always return\n+\t * dev_fwnode() - but then we should pump-up the refcount\n+\t */\n+\tif (!init_data)\n+\t\treturn NULL;\n+\n+\t/*\n+\t * For now we do only do node look-up for drivers which populate\n+\t * the new match properties. We could and perhaps should do\n+\t * fw = dev_fwnode(parent); if given fwnode is NULL. But in order to\n+\t * not break existing setups we keep the old behaviour and just directly\n+\t * use the given init_data->fwnode no matter if it is NULL or not.\n+\t */\n+\n+\tif ((!init_data->match_property.name ||\n+\t     !init_data->match_property.size) && !init_data->of_match)\n+\t\treturn fwnode_handle_get(init_data->fwnode);\n+\n+\t/* match information was given - do node look-up */\n+\n+\tif (!init_data->fwnode)\n+\t\tfw = dev_fwnode(parent);\n+\telse\n+\t\tfw = init_data->fwnode;\n+\n+\tif (!fw)\n+\t\treturn NULL;\n+\n+\t/*\n+\t * Simple things are pretty. I think simplest is to use DT node-name\n+\t * for matching the node with LED - same way regulators use the node\n+\t * name to match with desc.\n+\t *\n+\t * This may not work with existing LED DT entries if the node name has\n+\t * been freely selectible. In order to this to work the binding doc\n+\t * for LED driver should define usable node names.\n+\t *\n+\t * If this is not working we can define specific match property which\n+\t * value we scan and use for matching for LEDs connected to the\n+\t * controller.\n+\t */\n+\tif (init_data->match_property.name && init_data->match_property.size) {\n+\t\tu8 *val;\n+\t\tint ret;\n+\t\tstruct fwnode_handle *child;\n+\t\tstruct led_fw_match_property *mp;\n+\n+\t\tmp = &init_data->match_property;\n+\n+\t\tval = kzalloc(mp->size, GFP_KERNEL);\n+\t\tif (!val)\n+\t\t\treturn ERR_PTR(-ENOMEM);\n+\n+\t\tfwnode_for_each_child_node(fw, child) {\n+\t\t\tret = fw_is_match(child, mp, val);\n+\t\t\tif (ret > 0) {\n+\t\t\t\tkfree(val);\n+\t\t\t\treturn child;\n+\t\t\t}\n+\t\t\tif (ret < 0) {\n+\t\t\t\tdev_err(parent,\n+\t\t\t\t\t\"invalid fw match. Use raw_val?\\n\");\n+\t\t\t\tfwnode_handle_put(child);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t\tkfree(val);\n+\t}\n+\tif (init_data->of_match)\n+\t\tfw = fwnode_get_named_child_node(fw, init_data->of_match);\n+\n+\treturn fw;\n+}\n+EXPORT_SYMBOL(led_find_fwnode);\n+\n+int led_parse_fwnode_props(struct device *dev, struct fwnode_handle *fwnode,\n+\t\t\t   struct led_properties *props)\n+{\n+\tint ret = 0;\n \n \tif (!fwnode)\n-\t\treturn;\n+\t\treturn 0;\n \n \tif (fwnode_property_present(fwnode, \"label\")) {\n \t\tret = fwnode_property_read_string(fwnode, \"label\", &props->label);\n \t\tif (ret)\n \t\t\tdev_err(dev, \"Error parsing 'label' property (%d)\\n\", ret);\n-\t\treturn;\n+\t\treturn ret;\n \t}\n \n+\t/**\n+\t * Please note, logic changed - if invalid property is found we bail\n+\t * early out without parsing the rest of the properties. Originally\n+\t * this was the case only for 'label' property. I don't know the\n+\t * rationale behind original logic allowing invalid properties to be\n+\t * given. If there is a reason then we should reconsider this.\n+\t * Intuitively it feels correct to just yell and quit if we hit value we\n+\t * don't understand - but intuition may be wrong at times :)\n+\t */\n \tif (fwnode_property_present(fwnode, \"color\")) {\n \t\tret = fwnode_property_read_u32(fwnode, \"color\", &props->color);\n-\t\tif (ret)\n+\t\tif (ret) {\n \t\t\tdev_err(dev, \"Error parsing 'color' property (%d)\\n\", ret);\n-\t\telse if (props->color >= LED_COLOR_ID_MAX)\n+\t\t\treturn ret;\n+\t\t} else if (props->color >= LED_COLOR_ID_MAX) {\n \t\t\tdev_err(dev, \"LED color identifier out of range\\n\");\n-\t\telse\n-\t\t\tprops->color_present = true;\n+\t\t\treturn ret;\n+\t\t}\n+\t\tprops->color_present = true;\n \t}\n \n+\tif (fwnode_property_present(fwnode, \"function\")) {\n+\t\tret = fwnode_property_read_string(fwnode, \"function\",\n+\t\t\t\t\t\t  &props->function);\n+\t\tif (ret) {\n+\t\t\tdev_err(dev,\n+\t\t\t\t\"Error parsing 'function' property (%d)\\n\",\n+\t\t\t\tret);\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n \n-\tif (!fwnode_property_present(fwnode, \"function\"))\n-\t\treturn;\n-\n-\tret = fwnode_property_read_string(fwnode, \"function\", &props->function);\n-\tif (ret) {\n-\t\tdev_err(dev,\n-\t\t\t\"Error parsing 'function' property (%d)\\n\",\n-\t\t\tret);\n+\tif (fwnode_property_present(fwnode, \"function-enumerator\")) {\n+\t\tret = fwnode_property_read_u32(fwnode, \"function-enumerator\",\n+\t\t\t\t\t       &props->func_enum);\n+\t\tif (ret) {\n+\t\t\tdev_err(dev,\n+\t\t\t\t\"Error parsing 'function-enumerator' property (%d)\\n\",\n+\t\t\t\tret);\n+\t\t\treturn ret;\n+\t\t}\n+\t\tprops->func_enum_present = true;\n \t}\n \n-\tif (!fwnode_property_present(fwnode, \"function-enumerator\"))\n-\t\treturn;\n+\tif (fwnode_property_present(fwnode, \"default-state\")) {\n+\t\tret = fwnode_property_read_string(fwnode, \"default-state\",\n+\t\t\t\t\t\t  &props->default_state);\n+\t\tif (ret) {\n+\t\t\tdev_err(dev,\n+\t\t\t\t\"Error parsing 'default-state' property (%d)\\n\",\n+\t\t\t\tret);\n+\t\t\treturn ret;\n+\t\t}\n+\t}\n \n-\tret = fwnode_property_read_u32(fwnode, \"function-enumerator\",\n-\t\t\t\t       &props->func_enum);\n-\tif (ret) {\n-\t\tdev_err(dev,\n-\t\t\t\"Error parsing 'function-enumerator' property (%d)\\n\",\n-\t\t\tret);\n-\t} else {\n-\t\tprops->func_enum_present = true;\n+\tif (fwnode_property_present(fwnode, \"linux,default-trigger\")) {\n+\t\tret = fwnode_property_read_string(fwnode,\n+\t\t\t\t\t\t  \"linux,default-trigger\",\n+\t\t\t\t\t\t  &props->default_trigger);\n+\t\tif (ret)\n+\t\t\tdev_err(dev,\n+\t\t\t\t\"Error parsing 'linux,default-trigger' property (%d)\\n\",\n+\t\t\t\tret);\n \t}\n+\treturn ret;\n }\n+EXPORT_SYMBOL_GPL(led_parse_fwnode_props);\n \n int led_compose_name(struct device *dev, struct led_init_data *init_data,\n-\t\t     char *led_classdev_name)\n+\t\t     struct led_properties *props, char *led_classdev_name)\n {\n-\tstruct led_properties props = {};\n-\tstruct fwnode_handle *fwnode = init_data->fwnode;\n \tconst char *devicename = init_data->devicename;\n \n \tif (!led_classdev_name)\n \t\treturn -EINVAL;\n \n-\tled_parse_fwnode_props(dev, fwnode, &props);\n-\n-\tif (props.label) {\n+\tif (props->label) {\n \t\t/*\n \t\t * If init_data.devicename is NULL, then it indicates that\n \t\t * DT label should be used as-is for LED class device name.\n@@ -436,23 +592,23 @@ int led_compose_name(struct device *dev, struct led_init_data *init_data,\n \t\t * the final LED class device name.\n \t\t */\n \t\tif (!devicename) {\n-\t\t\tstrscpy(led_classdev_name, props.label,\n+\t\t\tstrscpy(led_classdev_name, props->label,\n \t\t\t\tLED_MAX_NAME_SIZE);\n \t\t} else {\n \t\t\tsnprintf(led_classdev_name, LED_MAX_NAME_SIZE, \"%s:%s\",\n-\t\t\t\t devicename, props.label);\n+\t\t\t\t devicename, props->label);\n \t\t}\n-\t} else if (props.function || props.color_present) {\n+\t} else if (props->function || props->color_present) {\n \t\tchar tmp_buf[LED_MAX_NAME_SIZE];\n \n-\t\tif (props.func_enum_present) {\n+\t\tif (props->func_enum_present) {\n \t\t\tsnprintf(tmp_buf, LED_MAX_NAME_SIZE, \"%s:%s-%d\",\n-\t\t\t\t props.color_present ? led_colors[props.color] : \"\",\n-\t\t\t\t props.function ?: \"\", props.func_enum);\n+\t\t\t\t props->color_present ? led_colors[props->color] : \"\",\n+\t\t\t\t props->function ?: \"\", props->func_enum);\n \t\t} else {\n \t\t\tsnprintf(tmp_buf, LED_MAX_NAME_SIZE, \"%s:%s\",\n-\t\t\t\t props.color_present ? led_colors[props.color] : \"\",\n-\t\t\t\t props.function ?: \"\");\n+\t\t\t\t props->color_present ? led_colors[props->color] : \"\",\n+\t\t\t\t props->function ?: \"\");\n \t\t}\n \t\tif (init_data->devname_mandatory) {\n \t\t\tsnprintf(led_classdev_name, LED_MAX_NAME_SIZE, \"%s:%s\",\n@@ -468,11 +624,19 @@ int led_compose_name(struct device *dev, struct led_init_data *init_data,\n \t\t}\n \t\tsnprintf(led_classdev_name, LED_MAX_NAME_SIZE, \"%s:%s\",\n \t\t\t devicename, init_data->default_label);\n-\t} else if (is_of_node(fwnode)) {\n-\t\tstrscpy(led_classdev_name, to_of_node(fwnode)->name,\n-\t\t\tLED_MAX_NAME_SIZE);\n-\t} else\n-\t\treturn -EINVAL;\n+\t} else {\n+\t\tstruct fwnode_handle *fwnode = led_find_fwnode(dev, init_data);\n+\t\tint ret = -EINVAL;\n+\n+\t\tif (is_of_node(fwnode)) {\n+\t\t\tret = 0;\n+\t\t\tstrscpy(led_classdev_name, to_of_node(fwnode)->name,\n+\t\t\t\tLED_MAX_NAME_SIZE);\n+\t\t}\n+\t\tfwnode_handle_put(fwnode);\n+\n+\t\treturn ret;\n+\t}\n \n \treturn 0;\n }\ndiff --git a/include/linux/leds.h b/include/linux/leds.h\nindex 242258f7d837..c7eef8367211 100644\n--- a/include/linux/leds.h\n+++ b/include/linux/leds.h\n@@ -13,6 +13,7 @@\n #include <linux/kernfs.h>\n #include <linux/list.h>\n #include <linux/mutex.h>\n+#include <linux/property.h>\n #include <linux/rwsem.h>\n #include <linux/spinlock.h>\n #include <linux/timer.h>\n@@ -20,6 +21,7 @@\n \n struct device;\n struct led_pattern;\n+struct led_classdev;\n /*\n  * LED Core\n  */\n@@ -31,8 +33,47 @@ enum led_brightness {\n \tLED_FULL\t= 255,\n };\n \n+struct led_properties {\n+\tu32\t\tcolor;\n+\tbool\t\tcolor_present;\n+\tconst char\t*function;\n+\tu32\t\tfunc_enum;\n+\tbool\t\tfunc_enum_present;\n+\tconst char\t*label;\n+\tconst char\t*default_trigger;\n+\tconst char\t*default_state;\n+\tbool\t\tbrightness_keep;\n+};\n+\n+struct led_fw_match_property {\n+\tconst char *name;\t/* Name of the property to match */\n+\tvoid *raw_val;\t\t/* Raw property value as present in fwnode */\n+\tvoid *intval;\t\t/* Property value if 8,16,32 or 64bit integer */\n+\tsize_t size;\t\t/* Size of value in bytes */\n+};\n+\n struct led_init_data {\n-\t/* device fwnode handle */\n+\t/*\n+\t * If DT binding dictates the node name the driver can fill of_match\n+\t * corresponding to node name describing this LED. If fwnode is given\n+\t * the match is searched from it's child nodes. If not, the match is\n+\t * searched from device's own child nodes.\n+\t */\n+\tconst char *of_match;\n+\t/*\n+\t * If fwnode contains property with known value the driver can specify\n+\t * correct propertty-value pair here to do the matching. This has higher\n+\t * priority than of_match. If fwnode is given the match is searched\n+\t * from it's child nodes. If not match is searched from device's\n+\t * own child nodes.\n+\t */\n+\tstruct led_fw_match_property match_property;\n+\t/*\n+\t * device fwnode handle. If of_match or led_compatible are not given\n+\t * this is used for LED as given. If of_match or led_compatible are\n+\t * given then this is used as a parent node whose child nodes are\n+\t * scanned for given match.\n+\t */\n \tstruct fwnode_handle *fwnode;\n \t/*\n \t * default <color:function> tuple, for backward compatibility\n@@ -53,9 +94,19 @@ struct led_init_data {\n \t * set it to true\n \t */\n \tbool devname_mandatory;\n+\t/*\n+\t * Callback which a LED driver can register if it has own non-standard\n+\t * DT properties. Core calls this with the located DT node during\n+\t * class_device registration\n+\t */\n+\tint (*of_parse_cb)(struct led_classdev *ld, struct fwnode_handle *fw,\n+\t\t\t    struct led_properties *props);\n+\t /* LED core should parse and handle the common firmware properties */\n+\tbool parse_fwnode;\n };\n \n struct led_classdev {\n+\tstruct led_init_data\t*init_data;\n \tconst char\t\t*name;\n \tenum led_brightness\t brightness;\n \tenum led_brightness\t max_brightness;\n@@ -148,6 +199,7 @@ struct led_classdev {\n \n \t/* Ensures consistent access to the LED Flash Class device */\n \tstruct mutex\t\tled_access;\n+\tbool\t\t\tfwnode_owned;\n };\n \n /**\n@@ -299,6 +351,7 @@ void led_sysfs_enable(struct led_classdev *led_cdev);\n  * led_compose_name - compose LED class device name\n  * @dev: LED controller device object\n  * @init_data: the LED class device initialization data\n+ * @props: LED properties as parsed from fwnode.\n  * @led_classdev_name: composed LED class device name\n  *\n  * Create LED class device name basing on the provided init_data argument.\n@@ -308,7 +361,8 @@ void led_sysfs_enable(struct led_classdev *led_cdev);\n  * Returns: 0 on success or negative error value on failure\n  */\n int led_compose_name(struct device *dev, struct led_init_data *init_data,\n-\t\t     char *led_classdev_name);\n+\t\t\t    struct led_properties *props,\n+\t\t\t    char *led_classdev_name);\n \n /**\n  * led_sysfs_is_disabled - check if LED sysfs interface is disabled\n@@ -321,6 +375,33 @@ static inline bool led_sysfs_is_disabled(struct led_classdev *led_cdev)\n \treturn led_cdev->flags & LED_SYSFS_DISABLE;\n }\n \n+/**\n+ * led_find_fwnode - find fwnode matching given LED init data\n+ * @parent: LED controller device this LED is driven by\n+ * @init_data: the LED class device initialization data\n+ *\n+ * Find the fw node matching given LED init data.\n+ * NOTE: Function increases refcount for found node. Caller must decrease\n+ * refcount using fwnode_handle_put when finished with node.\n+ *\n+ * Returns: node handle or NULL if matching fw node was not found\n+ */\n+struct fwnode_handle *led_find_fwnode(struct device *parent,\n+\t\t\t\t      struct led_init_data *init_data);\n+\n+/**\n+ * led_parse_fwnode_props - parse LED common properties from fwnode\n+ * @dev: pointer to LED device.\n+ * @fwnode: LED node containing the properties\n+ * @props: structure where found property data is stored.\n+ *\n+ * Parse the common LED properties from fwnode.\n+ *\n+ * Returns: 0 on success or negative error value on failure\n+ */\n+int led_parse_fwnode_props(struct device *dev, struct fwnode_handle *fwnode,\n+\t\t\t   struct led_properties *props);\n+\n /*\n  * LED Triggers\n  */\n@@ -478,15 +559,6 @@ struct led_platform_data {\n \tstruct led_info\t*leds;\n };\n \n-struct led_properties {\n-\tu32\t\tcolor;\n-\tbool\t\tcolor_present;\n-\tconst char\t*function;\n-\tu32\t\tfunc_enum;\n-\tbool\t\tfunc_enum_present;\n-\tconst char\t*label;\n-};\n-\n struct gpio_desc;\n typedef int (*gpio_blink_set_t)(struct gpio_desc *desc, int state,\n \t\t\t\tunsigned long *delay_on,\n","prefixes":["v7","11/12"]}