Commit | Line | Data |
---|---|---|
4e722158 JS |
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); |