| /* |
| * 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; |
| int ret; |
| |
| 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); |
| ret = of_resolve_phandles(overlay); |
| if (ret) { |
| pr_err("%s: Failed to resolve phandles: %d\n", __func__, ret); |
| return NULL; |
| } |
| |
| 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 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); |
| |
| ret = of_overlay_create(overlay); |
| if (ret) |
| pr_err("%s: Creating overlay 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); |