The process of applying an overlay consists of:
- unflatten an overlay FDT (flattened device tree) into an
EDT (expanded device tree)
- fixup the phandle values in the overlay EDT to fit in a
range above the phandle values in the live device tree
- create the overlay changeset to reflect the contents of
the overlay EDT
- apply the overlay changeset, to modify the live device tree,
potentially changing the maximum phandle value in the live
device tree
There is currently no protection against two overlay applies
concurrently determining what range of phandle values are in use
in the live device tree, and subsequently changing that range.
Add a mutex to prevent multiple overlay applies from occurring
simultaneously.
Move of_resolve_phandles() into of_overlay_apply() so that it does not
have to be duplicated by each caller of of_overlay_apply().
The test in of_resolve_phandles() that the overlay tree is detached is
temporarily disabled so that old style overlay unittests do not fail.
Signed-off-by: Frank Rowand <frank.rowand@sony.com>
Signed-off-by: Rob Herring <robh@kernel.org>
267 lines
5.9 KiB
C
267 lines
5.9 KiB
C
/*
|
|
* Copyright (C) 2015 Texas Instruments
|
|
* Author: Jyri Sarha <jsarha@ti.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published by
|
|
* the Free Software Foundation.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* To support the old "ti,tilcdc,slave" binding the binding has to be
|
|
* transformed to the new external encoder binding.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/of_fdt.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/list.h>
|
|
|
|
#include "tilcdc_slave_compat.h"
|
|
|
|
struct kfree_table {
|
|
int total;
|
|
int num;
|
|
void **table;
|
|
};
|
|
|
|
static int __init kfree_table_init(struct kfree_table *kft)
|
|
{
|
|
kft->total = 32;
|
|
kft->num = 0;
|
|
kft->table = kmalloc(kft->total * sizeof(*kft->table),
|
|
GFP_KERNEL);
|
|
if (!kft->table)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init kfree_table_add(struct kfree_table *kft, void *p)
|
|
{
|
|
if (kft->num == kft->total) {
|
|
void **old = kft->table;
|
|
|
|
kft->total *= 2;
|
|
kft->table = krealloc(old, kft->total * sizeof(*kft->table),
|
|
GFP_KERNEL);
|
|
if (!kft->table) {
|
|
kft->table = old;
|
|
kfree(p);
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
kft->table[kft->num++] = p;
|
|
return 0;
|
|
}
|
|
|
|
static void __init kfree_table_free(struct kfree_table *kft)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < kft->num; i++)
|
|
kfree(kft->table[i]);
|
|
|
|
kfree(kft->table);
|
|
}
|
|
|
|
static
|
|
struct property * __init tilcdc_prop_dup(const struct property *prop,
|
|
struct kfree_table *kft)
|
|
{
|
|
struct property *nprop;
|
|
|
|
nprop = kzalloc(sizeof(*nprop), GFP_KERNEL);
|
|
if (!nprop || kfree_table_add(kft, nprop))
|
|
return NULL;
|
|
|
|
nprop->name = kstrdup(prop->name, GFP_KERNEL);
|
|
if (!nprop->name || kfree_table_add(kft, nprop->name))
|
|
return NULL;
|
|
|
|
nprop->value = kmemdup(prop->value, prop->length, GFP_KERNEL);
|
|
if (!nprop->value || kfree_table_add(kft, nprop->value))
|
|
return NULL;
|
|
|
|
nprop->length = prop->length;
|
|
|
|
return nprop;
|
|
}
|
|
|
|
static void __init tilcdc_copy_props(struct device_node *from,
|
|
struct device_node *to,
|
|
const char * const props[],
|
|
struct kfree_table *kft)
|
|
{
|
|
struct property *prop;
|
|
int i;
|
|
|
|
for (i = 0; props[i]; i++) {
|
|
prop = of_find_property(from, props[i], NULL);
|
|
if (!prop)
|
|
continue;
|
|
|
|
prop = tilcdc_prop_dup(prop, kft);
|
|
if (!prop)
|
|
continue;
|
|
|
|
prop->next = to->properties;
|
|
to->properties = prop;
|
|
}
|
|
}
|
|
|
|
static int __init tilcdc_prop_str_update(struct property *prop,
|
|
const char *str,
|
|
struct kfree_table *kft)
|
|
{
|
|
prop->value = kstrdup(str, GFP_KERNEL);
|
|
if (kfree_table_add(kft, prop->value) || !prop->value)
|
|
return -ENOMEM;
|
|
prop->length = strlen(str)+1;
|
|
return 0;
|
|
}
|
|
|
|
static void __init tilcdc_node_disable(struct device_node *node)
|
|
{
|
|
struct property *prop;
|
|
|
|
prop = kzalloc(sizeof(*prop), GFP_KERNEL);
|
|
if (!prop)
|
|
return;
|
|
|
|
prop->name = "status";
|
|
prop->value = "disabled";
|
|
prop->length = strlen((char *)prop->value)+1;
|
|
|
|
of_update_property(node, prop);
|
|
}
|
|
|
|
static struct device_node * __init tilcdc_get_overlay(struct kfree_table *kft)
|
|
{
|
|
const int size = __dtb_tilcdc_slave_compat_end -
|
|
__dtb_tilcdc_slave_compat_begin;
|
|
static void *overlay_data;
|
|
struct device_node *overlay;
|
|
|
|
if (!size) {
|
|
pr_warn("%s: No overlay data\n", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
overlay_data = kmemdup(__dtb_tilcdc_slave_compat_begin,
|
|
size, GFP_KERNEL);
|
|
if (!overlay_data || kfree_table_add(kft, overlay_data))
|
|
return NULL;
|
|
|
|
of_fdt_unflatten_tree(overlay_data, NULL, &overlay);
|
|
if (!overlay) {
|
|
pr_warn("%s: Unfattening overlay tree failed\n", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
of_node_set_flag(overlay, OF_DETACHED);
|
|
|
|
return overlay;
|
|
}
|
|
|
|
static const struct of_device_id tilcdc_slave_of_match[] __initconst = {
|
|
{ .compatible = "ti,tilcdc,slave", },
|
|
{},
|
|
};
|
|
|
|
static const struct of_device_id tilcdc_of_match[] __initconst = {
|
|
{ .compatible = "ti,am33xx-tilcdc", },
|
|
{},
|
|
};
|
|
|
|
static const struct of_device_id tilcdc_tda998x_of_match[] __initconst = {
|
|
{ .compatible = "nxp,tda998x", },
|
|
{},
|
|
};
|
|
|
|
static const char * const tilcdc_slave_props[] __initconst = {
|
|
"pinctrl-names",
|
|
"pinctrl-0",
|
|
"pinctrl-1",
|
|
NULL
|
|
};
|
|
|
|
static void __init tilcdc_convert_slave_node(void)
|
|
{
|
|
struct device_node *slave = NULL, *lcdc = NULL;
|
|
struct device_node *i2c = NULL, *fragment = NULL;
|
|
struct device_node *overlay, *encoder;
|
|
struct property *prop;
|
|
/* For all memory needed for the overlay tree. This memory can
|
|
be freed after the overlay has been applied. */
|
|
struct kfree_table kft;
|
|
int ovcs_id, ret;
|
|
|
|
if (kfree_table_init(&kft))
|
|
return;
|
|
|
|
lcdc = of_find_matching_node(NULL, tilcdc_of_match);
|
|
slave = of_find_matching_node(NULL, tilcdc_slave_of_match);
|
|
|
|
if (!slave || !of_device_is_available(lcdc))
|
|
goto out;
|
|
|
|
i2c = of_parse_phandle(slave, "i2c", 0);
|
|
if (!i2c) {
|
|
pr_err("%s: Can't find i2c node trough phandle\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
overlay = tilcdc_get_overlay(&kft);
|
|
if (!overlay)
|
|
goto out;
|
|
|
|
encoder = of_find_matching_node(overlay, tilcdc_tda998x_of_match);
|
|
if (!encoder) {
|
|
pr_err("%s: Failed to find tda998x node\n", __func__);
|
|
goto out;
|
|
}
|
|
|
|
tilcdc_copy_props(slave, encoder, tilcdc_slave_props, &kft);
|
|
|
|
for_each_child_of_node(overlay, fragment) {
|
|
prop = of_find_property(fragment, "target-path", NULL);
|
|
if (!prop)
|
|
continue;
|
|
if (!strncmp("i2c", (char *)prop->value, prop->length))
|
|
if (tilcdc_prop_str_update(prop, i2c->full_name, &kft))
|
|
goto out;
|
|
if (!strncmp("lcdc", (char *)prop->value, prop->length))
|
|
if (tilcdc_prop_str_update(prop, lcdc->full_name, &kft))
|
|
goto out;
|
|
}
|
|
|
|
tilcdc_node_disable(slave);
|
|
|
|
ovcs_id = 0;
|
|
ret = of_overlay_apply(overlay, &ovcs_id);
|
|
if (ret)
|
|
pr_err("%s: Applying overlay changeset failed: %d\n",
|
|
__func__, ret);
|
|
else
|
|
pr_info("%s: ti,tilcdc,slave node successfully converted\n",
|
|
__func__);
|
|
out:
|
|
kfree_table_free(&kft);
|
|
of_node_put(i2c);
|
|
of_node_put(slave);
|
|
of_node_put(lcdc);
|
|
of_node_put(fragment);
|
|
}
|
|
|
|
static int __init tilcdc_slave_compat_init(void)
|
|
{
|
|
tilcdc_convert_slave_node();
|
|
return 0;
|
|
}
|
|
|
|
subsys_initcall(tilcdc_slave_compat_init);
|