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 25struct kfree_table { 26 int total; 27 int num; 28 void **table; 29}; 30 31static 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 43static 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 61static 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 71static 72struct 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 94static 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 116static 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 127static 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 142struct 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 176static const struct of_device_id tilcdc_slave_of_match[] __initconst = { 177 { .compatible = "ti,tilcdc,slave", }, 178 {}, 179}; 180 181static const struct of_device_id tilcdc_of_match[] __initconst = { 182 { .compatible = "ti,am33xx-tilcdc", }, 183 {}, 184}; 185 186static const struct of_device_id tilcdc_tda998x_of_match[] __initconst = { 187 { .compatible = "nxp,tda998x", }, 188 {}, 189}; 190 191static const char * const tilcdc_slave_props[] __initconst = { 192 "pinctrl-names", 193 "pinctrl-0", 194 "pinctrl-1", 195 NULL 196}; 197 198void __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__); 256out: 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 264int __init tilcdc_slave_compat_init(void) 265{ 266 tilcdc_convert_slave_node(); 267 return 0; 268} 269 270subsys_initcall(tilcdc_slave_compat_init); 271