Skip to content

Commit

Permalink
leds: Add common LED binding parsing support to LED class/core
Browse files Browse the repository at this point in the history
Qucik grep for 'for_each' or 'linux,default-trigger' or
'default-state' under drivers/leds can tell quite a lot. It seems
multiple LED controller drivers implement the very similar looping
through the child nodes in order to locate the LED nodes and read
and support the common LED dt bindings. Implementing this same
stuff for all LED controllers gets old pretty fast.

This commit adds support for locating the LED node (based on known
node names - or linux,led-compatible property) and handling of
few common LED properties.

linux,default-trigger,
default-state (with the exception of keep),

(in addition to already handled
function-enumerator,
function,
color
and label).

Regarding the node look-up: If no init_data is given, then no
node-lookup is done and cdev name is used as such.

If init_data is goven but no starting point for node lookup - then
(parent) device's own DT node is used. If no led-compatible is given,
then of_match is searched for. If neither led-compatible not of_match
is given then device's own node or passed starting point are used as
such.

Signed-off-by: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
  • Loading branch information
M-Vaittinen authored and intel-lab-lkp committed Nov 16, 2019
1 parent 4f0bd16 commit 7b8033c
Show file tree
Hide file tree
Showing 3 changed files with 360 additions and 64 deletions.
88 changes: 80 additions & 8 deletions drivers/leds/led-class.c
Expand Up @@ -235,6 +235,29 @@ static int led_classdev_next_name(const char *init_name, char *name,
return i;
}

static void led_add_props(struct led_classdev *ld, struct led_properties *props)
{
if (props->default_trigger)
ld->default_trigger = props->default_trigger;
/*
* According to binding docs the LED is by-default turned OFF unless
* default_state is used to indicate it should be ON or that state
* should be kept as is
*/
if (props->default_state) {
ld->brightness = LED_OFF;
if (!strcmp(props->default_state, "on"))
ld->brightness = LED_FULL;
/*
* We probably should not call the brightness_get prior calling
* the of_parse_cb if one is provided.
* Add a flag to advertice that state should be queried and kept as-is.
*/
else if (!strcmp(props->default_state, "keep"))
props->brightness_keep = true;
}
}

/**
* led_classdev_register_ext - register a new object of led_classdev class
* with init data.
Expand All @@ -251,45 +274,87 @@ int led_classdev_register_ext(struct device *parent,
char final_name[LED_MAX_NAME_SIZE];
const char *proposed_name = composed_name;
int ret;

struct led_properties props = {0};
struct fwnode_handle *fw;

/*
* We don't try getting the name based on DT node if init-data is not
* given. We could see if we find LED properties from the device's node
* but that might change LED names for old users of
* led_classdev_register who have been providing the LED name in
* cdev->name. So we seek fwnode for names only if init_data is given
*/
if (init_data) {
led_cdev->init_data = init_data;
if (init_data->devname_mandatory && !init_data->devicename) {
dev_err(parent, "Mandatory device name is missing");
return -EINVAL;
}
ret = led_compose_name(parent, init_data, composed_name);

fw = led_find_fwnode(parent, init_data);
if (!fw) {
dev_err(parent, "No fwnode found\n");
return -ENOENT;
}
/*
* We did increase refcount for fwnode. Let's set a flag so we
* can decrease it during deregistration
*/
led_cdev->fwnode_owned = true;

ret = led_parse_fwnode_props(parent, fw, &props);
if (ret)
goto err_out;

if (init_data->of_parse_cb)
ret = init_data->of_parse_cb(led_cdev, fw, &props);
if (ret < 0)
return ret;
goto err_out;

led_add_props(led_cdev, &props);

ret = led_compose_name(parent, init_data, &props,
composed_name);
if (ret < 0)
goto err_out;
} else {
proposed_name = led_cdev->name;
led_cdev->fwnode_owned = false;
fw = NULL;
}

ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name));
if (ret < 0)
return ret;
goto err_out;

mutex_init(&led_cdev->led_access);
mutex_lock(&led_cdev->led_access);
led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
led_cdev, led_cdev->groups, "%s", final_name);
if (IS_ERR(led_cdev->dev)) {
mutex_unlock(&led_cdev->led_access);
return PTR_ERR(led_cdev->dev);
ret = PTR_ERR(led_cdev->dev);
goto err_out;
}
if (init_data && init_data->fwnode)
led_cdev->dev->fwnode = init_data->fwnode;
if (fw)
led_cdev->dev->fwnode = fw;

if (ret)
dev_warn(parent, "Led %s renamed to %s due to name collision",
led_cdev->name, dev_name(led_cdev->dev));

if (props.brightness_keep)
if (led_cdev->brightness_get)
led_cdev->brightness =
led_cdev->brightness_get(led_cdev);

if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
ret = led_add_brightness_hw_changed(led_cdev);
if (ret) {
device_unregister(led_cdev->dev);
led_cdev->dev = NULL;
mutex_unlock(&led_cdev->led_access);
return ret;
goto err_out;
}
}

Expand Down Expand Up @@ -322,6 +387,10 @@ int led_classdev_register_ext(struct device *parent,
led_cdev->name);

return 0;
err_out:
if (led_cdev->fwnode_owned)
fwnode_handle_put(fw);
return ret;
}
EXPORT_SYMBOL_GPL(led_classdev_register_ext);

Expand Down Expand Up @@ -355,6 +424,9 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
led_remove_brightness_hw_changed(led_cdev);

if (led_cdev->fwnode_owned)
fwnode_handle_put(led_cdev->dev->fwnode);

device_unregister(led_cdev->dev);

down_write(&leds_list_lock);
Expand Down

0 comments on commit 7b8033c

Please sign in to comment.