Jyri Sarha | 4e72215 | 2015-02-18 11:24:03 +0200 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2015 Texas Instruments |
| 3 | * Author: Jyri Sarha <jsarha@ti.com> |
| 4 | * |
| 5 | * This program is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 as published by |
| 7 | * the Free Software Foundation. |
| 8 | * |
| 9 | */ |
| 10 | |
| 11 | /* |
| 12 | * To support the old "ti,tilcdc,slave" binding the binding has to be |
| 13 | * transformed to the new external encoder binding. |
| 14 | */ |
| 15 | |
| 16 | #include <linux/kernel.h> |
| 17 | #include <linux/of.h> |
| 18 | #include <linux/of_graph.h> |
| 19 | #include <linux/of_fdt.h> |
| 20 | #include <linux/slab.h> |
| 21 | #include <linux/list.h> |
| 22 | |
| 23 | #include "tilcdc_slave_compat.h" |
| 24 | |
| 25 | struct kfree_table { |
| 26 | int total; |
| 27 | int num; |
| 28 | void **table; |
| 29 | }; |
| 30 | |
| 31 | static int __init kfree_table_init(struct kfree_table *kft) |
| 32 | { |
| 33 | kft->total = 32; |
| 34 | kft->num = 0; |
| 35 | kft->table = kmalloc(kft->total * sizeof(*kft->table), |
| 36 | GFP_KERNEL); |
| 37 | if (!kft->table) |
| 38 | return -ENOMEM; |
| 39 | |
| 40 | return 0; |
| 41 | } |
| 42 | |
| 43 | static int __init kfree_table_add(struct kfree_table *kft, void *p) |
| 44 | { |
| 45 | if (kft->num == kft->total) { |
| 46 | void **old = kft->table; |
| 47 | |
| 48 | kft->total *= 2; |
| 49 | kft->table = krealloc(old, kft->total * sizeof(*kft->table), |
| 50 | GFP_KERNEL); |
| 51 | if (!kft->table) { |
| 52 | kft->table = old; |
| 53 | kfree(p); |
| 54 | return -ENOMEM; |
| 55 | } |
| 56 | } |
| 57 | kft->table[kft->num++] = p; |
| 58 | return 0; |
| 59 | } |
| 60 | |
| 61 | static void __init kfree_table_free(struct kfree_table *kft) |
| 62 | { |
| 63 | int i; |
| 64 | |
| 65 | for (i = 0; i < kft->num; i++) |
| 66 | kfree(kft->table[i]); |
| 67 | |
| 68 | kfree(kft->table); |
| 69 | } |
| 70 | |
| 71 | static |
| 72 | struct property * __init tilcdc_prop_dup(const struct property *prop, |
| 73 | struct kfree_table *kft) |
| 74 | { |
| 75 | struct property *nprop; |
| 76 | |
| 77 | nprop = kzalloc(sizeof(*nprop), GFP_KERNEL); |
| 78 | if (!nprop || kfree_table_add(kft, nprop)) |
| 79 | return NULL; |
| 80 | |
| 81 | nprop->name = kstrdup(prop->name, GFP_KERNEL); |
| 82 | if (!nprop->name || kfree_table_add(kft, nprop->name)) |
| 83 | return NULL; |
| 84 | |
| 85 | nprop->value = kmemdup(prop->value, prop->length, GFP_KERNEL); |
| 86 | if (!nprop->value || kfree_table_add(kft, nprop->value)) |
| 87 | return NULL; |
| 88 | |
| 89 | nprop->length = prop->length; |
| 90 | |
| 91 | return nprop; |
| 92 | } |
| 93 | |
| 94 | static void __init tilcdc_copy_props(struct device_node *from, |
| 95 | struct device_node *to, |
| 96 | const char * const props[], |
| 97 | struct kfree_table *kft) |
| 98 | { |
| 99 | struct property *prop; |
| 100 | int i; |
| 101 | |
| 102 | for (i = 0; props[i]; i++) { |
| 103 | prop = of_find_property(from, props[i], NULL); |
| 104 | if (!prop) |
| 105 | continue; |
| 106 | |
| 107 | prop = tilcdc_prop_dup(prop, kft); |
| 108 | if (!prop) |
| 109 | continue; |
| 110 | |
| 111 | prop->next = to->properties; |
| 112 | to->properties = prop; |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | static int __init tilcdc_prop_str_update(struct property *prop, |
| 117 | const char *str, |
| 118 | struct kfree_table *kft) |
| 119 | { |
| 120 | prop->value = kstrdup(str, GFP_KERNEL); |
| 121 | if (kfree_table_add(kft, prop->value) || !prop->value) |
| 122 | return -ENOMEM; |
| 123 | prop->length = strlen(str)+1; |
| 124 | return 0; |
| 125 | } |
| 126 | |
| 127 | static void __init tilcdc_node_disable(struct device_node *node) |
| 128 | { |
| 129 | struct property *prop; |
| 130 | |
| 131 | prop = kzalloc(sizeof(*prop), GFP_KERNEL); |
| 132 | if (!prop) |
| 133 | return; |
| 134 | |
| 135 | prop->name = "status"; |
| 136 | prop->value = "disabled"; |
| 137 | prop->length = strlen((char *)prop->value)+1; |
| 138 | |
| 139 | of_update_property(node, prop); |
| 140 | } |
| 141 | |
| 142 | struct device_node * __init tilcdc_get_overlay(struct kfree_table *kft) |
| 143 | { |
| 144 | const int size = __dtb_tilcdc_slave_compat_end - |
| 145 | __dtb_tilcdc_slave_compat_begin; |
| 146 | static void *overlay_data; |
| 147 | struct device_node *overlay; |
| 148 | int ret; |
| 149 | |
| 150 | if (!size) { |
| 151 | pr_warn("%s: No overlay data\n", __func__); |
| 152 | return NULL; |
| 153 | } |
| 154 | |
| 155 | overlay_data = kmemdup(__dtb_tilcdc_slave_compat_begin, |
| 156 | size, GFP_KERNEL); |
| 157 | if (!overlay_data || kfree_table_add(kft, overlay_data)) |
| 158 | return NULL; |
| 159 | |
| 160 | of_fdt_unflatten_tree(overlay_data, &overlay); |
| 161 | if (!overlay) { |
| 162 | pr_warn("%s: Unfattening overlay tree failed\n", __func__); |
| 163 | return NULL; |
| 164 | } |
| 165 | |
| 166 | of_node_set_flag(overlay, OF_DETACHED); |
| 167 | ret = of_resolve_phandles(overlay); |
| 168 | if (ret) { |
| 169 | pr_err("%s: Failed to resolve phandles: %d\n", __func__, ret); |
| 170 | return NULL; |
| 171 | } |
| 172 | |
| 173 | return overlay; |
| 174 | } |
| 175 | |
| 176 | static const struct of_device_id tilcdc_slave_of_match[] __initconst = { |
| 177 | { .compatible = "ti,tilcdc,slave", }, |
| 178 | {}, |
| 179 | }; |
| 180 | |
| 181 | static const struct of_device_id tilcdc_of_match[] __initconst = { |
| 182 | { .compatible = "ti,am33xx-tilcdc", }, |
| 183 | {}, |
| 184 | }; |
| 185 | |
| 186 | static const struct of_device_id tilcdc_tda998x_of_match[] __initconst = { |
| 187 | { .compatible = "nxp,tda998x", }, |
| 188 | {}, |
| 189 | }; |
| 190 | |
| 191 | static const char * const tilcdc_slave_props[] __initconst = { |
| 192 | "pinctrl-names", |
| 193 | "pinctrl-0", |
| 194 | "pinctrl-1", |
| 195 | NULL |
| 196 | }; |
| 197 | |
| 198 | void __init tilcdc_convert_slave_node(void) |
| 199 | { |
| 200 | struct device_node *slave = NULL, *lcdc = NULL; |
| 201 | struct device_node *i2c = NULL, *fragment = NULL; |
| 202 | struct device_node *overlay, *encoder; |
| 203 | struct property *prop; |
| 204 | /* For all memory needed for the overlay tree. This memory can |
| 205 | be freed after the overlay has been applied. */ |
| 206 | struct kfree_table kft; |
| 207 | int ret; |
| 208 | |
| 209 | if (kfree_table_init(&kft)) |
| 210 | goto out; |
| 211 | |
| 212 | lcdc = of_find_matching_node(NULL, tilcdc_of_match); |
| 213 | slave = of_find_matching_node(NULL, tilcdc_slave_of_match); |
| 214 | |
| 215 | if (!slave || !of_device_is_available(lcdc)) |
| 216 | goto out; |
| 217 | |
| 218 | i2c = of_parse_phandle(slave, "i2c", 0); |
| 219 | if (!i2c) { |
| 220 | pr_err("%s: Can't find i2c node trough phandle\n", __func__); |
| 221 | goto out; |
| 222 | } |
| 223 | |
| 224 | overlay = tilcdc_get_overlay(&kft); |
| 225 | if (!overlay) |
| 226 | goto out; |
| 227 | |
| 228 | encoder = of_find_matching_node(overlay, tilcdc_tda998x_of_match); |
| 229 | if (!encoder) { |
| 230 | pr_err("%s: Failed to find tda998x node\n", __func__); |
| 231 | goto out; |
| 232 | } |
| 233 | |
| 234 | tilcdc_copy_props(slave, encoder, tilcdc_slave_props, &kft); |
| 235 | |
| 236 | for_each_child_of_node(overlay, fragment) { |
| 237 | prop = of_find_property(fragment, "target-path", NULL); |
| 238 | if (!prop) |
| 239 | continue; |
| 240 | if (!strncmp("i2c", (char *)prop->value, prop->length)) |
| 241 | if (tilcdc_prop_str_update(prop, i2c->full_name, &kft)) |
| 242 | goto out; |
| 243 | if (!strncmp("lcdc", (char *)prop->value, prop->length)) |
| 244 | if (tilcdc_prop_str_update(prop, lcdc->full_name, &kft)) |
| 245 | goto out; |
| 246 | } |
| 247 | |
| 248 | tilcdc_node_disable(slave); |
| 249 | |
| 250 | ret = of_overlay_create(overlay); |
| 251 | if (ret) |
| 252 | pr_err("%s: Creating overlay failed: %d\n", __func__, ret); |
| 253 | else |
| 254 | pr_info("%s: ti,tilcdc,slave node successfully converted\n", |
| 255 | __func__); |
| 256 | out: |
| 257 | kfree_table_free(&kft); |
| 258 | of_node_put(i2c); |
| 259 | of_node_put(slave); |
| 260 | of_node_put(lcdc); |
| 261 | of_node_put(fragment); |
| 262 | } |
| 263 | |
| 264 | int __init tilcdc_slave_compat_init(void) |
| 265 | { |
| 266 | tilcdc_convert_slave_node(); |
| 267 | return 0; |
| 268 | } |
| 269 | |
| 270 | subsys_initcall(tilcdc_slave_compat_init); |