1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Core driver for the generic pin config portions of the pin control subsystem |
4 | * |
5 | * Copyright (C) 2011 ST-Ericsson SA |
6 | * Written on behalf of Linaro for ST-Ericsson |
7 | * |
8 | * Author: Linus Walleij <linus.walleij@linaro.org> |
9 | */ |
10 | |
11 | #define pr_fmt(fmt) "generic pinconfig core: " fmt |
12 | |
13 | #include <linux/array_size.h> |
14 | #include <linux/debugfs.h> |
15 | #include <linux/device.h> |
16 | #include <linux/init.h> |
17 | #include <linux/module.h> |
18 | #include <linux/of.h> |
19 | #include <linux/slab.h> |
20 | #include <linux/seq_file.h> |
21 | |
22 | #include <linux/pinctrl/pinconf-generic.h> |
23 | #include <linux/pinctrl/pinconf.h> |
24 | #include <linux/pinctrl/pinctrl.h> |
25 | |
26 | #include "core.h" |
27 | #include "pinconf.h" |
28 | #include "pinctrl-utils.h" |
29 | |
30 | #ifdef CONFIG_DEBUG_FS |
31 | static const struct pin_config_item conf_items[] = { |
32 | PCONFDUMP(PIN_CONFIG_BIAS_BUS_HOLD, "input bias bus hold" , NULL, false), |
33 | PCONFDUMP(PIN_CONFIG_BIAS_DISABLE, "input bias disabled" , NULL, false), |
34 | PCONFDUMP(PIN_CONFIG_BIAS_HIGH_IMPEDANCE, "input bias high impedance" , NULL, false), |
35 | PCONFDUMP(PIN_CONFIG_BIAS_PULL_DOWN, "input bias pull down" , "ohms" , true), |
36 | PCONFDUMP(PIN_CONFIG_BIAS_PULL_PIN_DEFAULT, |
37 | "input bias pull to pin specific state" , "ohms" , true), |
38 | PCONFDUMP(PIN_CONFIG_BIAS_PULL_UP, "input bias pull up" , "ohms" , true), |
39 | PCONFDUMP(PIN_CONFIG_DRIVE_OPEN_DRAIN, "output drive open drain" , NULL, false), |
40 | PCONFDUMP(PIN_CONFIG_DRIVE_OPEN_SOURCE, "output drive open source" , NULL, false), |
41 | PCONFDUMP(PIN_CONFIG_DRIVE_PUSH_PULL, "output drive push pull" , NULL, false), |
42 | PCONFDUMP(PIN_CONFIG_DRIVE_STRENGTH, "output drive strength" , "mA" , true), |
43 | PCONFDUMP(PIN_CONFIG_DRIVE_STRENGTH_UA, "output drive strength" , "uA" , true), |
44 | PCONFDUMP(PIN_CONFIG_INPUT_DEBOUNCE, "input debounce" , "usec" , true), |
45 | PCONFDUMP(PIN_CONFIG_INPUT_ENABLE, "input enabled" , NULL, false), |
46 | PCONFDUMP(PIN_CONFIG_INPUT_SCHMITT, "input schmitt trigger" , NULL, false), |
47 | PCONFDUMP(PIN_CONFIG_INPUT_SCHMITT_ENABLE, "input schmitt enabled" , NULL, false), |
48 | PCONFDUMP(PIN_CONFIG_MODE_LOW_POWER, "pin low power" , "mode" , true), |
49 | PCONFDUMP(PIN_CONFIG_OUTPUT_ENABLE, "output enabled" , NULL, false), |
50 | PCONFDUMP(PIN_CONFIG_OUTPUT, "pin output" , "level" , true), |
51 | PCONFDUMP(PIN_CONFIG_OUTPUT_IMPEDANCE_OHMS, "output impedance" , "ohms" , true), |
52 | PCONFDUMP(PIN_CONFIG_POWER_SOURCE, "pin power source" , "selector" , true), |
53 | PCONFDUMP(PIN_CONFIG_SLEEP_HARDWARE_STATE, "sleep hardware state" , NULL, false), |
54 | PCONFDUMP(PIN_CONFIG_SLEW_RATE, "slew rate" , NULL, true), |
55 | PCONFDUMP(PIN_CONFIG_SKEW_DELAY, "skew delay" , NULL, true), |
56 | }; |
57 | |
58 | static void pinconf_generic_dump_one(struct pinctrl_dev *pctldev, |
59 | struct seq_file *s, const char *gname, |
60 | unsigned pin, |
61 | const struct pin_config_item *items, |
62 | int nitems, int *print_sep) |
63 | { |
64 | int i; |
65 | |
66 | for (i = 0; i < nitems; i++) { |
67 | unsigned long config; |
68 | int ret; |
69 | |
70 | /* We want to check out this parameter */ |
71 | config = pinconf_to_config_packed(param: items[i].param, argument: 0); |
72 | if (gname) |
73 | ret = pin_config_group_get(dev_name: dev_name(dev: pctldev->dev), |
74 | pin_group: gname, config: &config); |
75 | else |
76 | ret = pin_config_get_for_pin(pctldev, pin, config: &config); |
77 | /* These are legal errors */ |
78 | if (ret == -EINVAL || ret == -ENOTSUPP) |
79 | continue; |
80 | if (ret) { |
81 | seq_printf(m: s, fmt: "ERROR READING CONFIG SETTING %d " , i); |
82 | continue; |
83 | } |
84 | /* comma between multiple configs */ |
85 | if (*print_sep) |
86 | seq_puts(m: s, s: ", " ); |
87 | *print_sep = 1; |
88 | seq_puts(m: s, s: items[i].display); |
89 | /* Print unit if available */ |
90 | if (items[i].has_arg) { |
91 | seq_printf(m: s, fmt: " (%u" , |
92 | pinconf_to_config_argument(config)); |
93 | if (items[i].format) |
94 | seq_printf(m: s, fmt: " %s)" , items[i].format); |
95 | else |
96 | seq_puts(m: s, s: ")" ); |
97 | } |
98 | } |
99 | } |
100 | |
101 | /** |
102 | * pinconf_generic_dump_pins - Print information about pin or group of pins |
103 | * @pctldev: Pincontrol device |
104 | * @s: File to print to |
105 | * @gname: Group name specifying pins |
106 | * @pin: Pin number specyfying pin |
107 | * |
108 | * Print the pinconf configuration for the requested pin(s) to @s. Pins can be |
109 | * specified either by pin using @pin or by group using @gname. Only one needs |
110 | * to be specified the other can be NULL/0. |
111 | */ |
112 | void pinconf_generic_dump_pins(struct pinctrl_dev *pctldev, struct seq_file *s, |
113 | const char *gname, unsigned pin) |
114 | { |
115 | const struct pinconf_ops *ops = pctldev->desc->confops; |
116 | int print_sep = 0; |
117 | |
118 | if (!ops->is_generic) |
119 | return; |
120 | |
121 | /* generic parameters */ |
122 | pinconf_generic_dump_one(pctldev, s, gname, pin, items: conf_items, |
123 | ARRAY_SIZE(conf_items), print_sep: &print_sep); |
124 | /* driver-specific parameters */ |
125 | if (pctldev->desc->num_custom_params && |
126 | pctldev->desc->custom_conf_items) |
127 | pinconf_generic_dump_one(pctldev, s, gname, pin, |
128 | items: pctldev->desc->custom_conf_items, |
129 | nitems: pctldev->desc->num_custom_params, |
130 | print_sep: &print_sep); |
131 | } |
132 | |
133 | void pinconf_generic_dump_config(struct pinctrl_dev *pctldev, |
134 | struct seq_file *s, unsigned long config) |
135 | { |
136 | int i; |
137 | |
138 | for (i = 0; i < ARRAY_SIZE(conf_items); i++) { |
139 | if (pinconf_to_config_param(config) != conf_items[i].param) |
140 | continue; |
141 | seq_printf(m: s, fmt: "%s: 0x%x" , conf_items[i].display, |
142 | pinconf_to_config_argument(config)); |
143 | } |
144 | |
145 | if (!pctldev->desc->num_custom_params || |
146 | !pctldev->desc->custom_conf_items) |
147 | return; |
148 | |
149 | for (i = 0; i < pctldev->desc->num_custom_params; i++) { |
150 | if (pinconf_to_config_param(config) != |
151 | pctldev->desc->custom_conf_items[i].param) |
152 | continue; |
153 | seq_printf(m: s, fmt: "%s: 0x%x" , |
154 | pctldev->desc->custom_conf_items[i].display, |
155 | pinconf_to_config_argument(config)); |
156 | } |
157 | } |
158 | EXPORT_SYMBOL_GPL(pinconf_generic_dump_config); |
159 | #endif |
160 | |
161 | #ifdef CONFIG_OF |
162 | static const struct pinconf_generic_params dt_params[] = { |
163 | { "bias-bus-hold" , PIN_CONFIG_BIAS_BUS_HOLD, 0 }, |
164 | { "bias-disable" , PIN_CONFIG_BIAS_DISABLE, 0 }, |
165 | { "bias-high-impedance" , PIN_CONFIG_BIAS_HIGH_IMPEDANCE, 0 }, |
166 | { "bias-pull-up" , PIN_CONFIG_BIAS_PULL_UP, 1 }, |
167 | { "bias-pull-pin-default" , PIN_CONFIG_BIAS_PULL_PIN_DEFAULT, 1 }, |
168 | { "bias-pull-down" , PIN_CONFIG_BIAS_PULL_DOWN, 1 }, |
169 | { "drive-open-drain" , PIN_CONFIG_DRIVE_OPEN_DRAIN, 0 }, |
170 | { "drive-open-source" , PIN_CONFIG_DRIVE_OPEN_SOURCE, 0 }, |
171 | { "drive-push-pull" , PIN_CONFIG_DRIVE_PUSH_PULL, 0 }, |
172 | { "drive-strength" , PIN_CONFIG_DRIVE_STRENGTH, 0 }, |
173 | { "drive-strength-microamp" , PIN_CONFIG_DRIVE_STRENGTH_UA, 0 }, |
174 | { "input-debounce" , PIN_CONFIG_INPUT_DEBOUNCE, 0 }, |
175 | { "input-disable" , PIN_CONFIG_INPUT_ENABLE, 0 }, |
176 | { "input-enable" , PIN_CONFIG_INPUT_ENABLE, 1 }, |
177 | { "input-schmitt" , PIN_CONFIG_INPUT_SCHMITT, 0 }, |
178 | { "input-schmitt-disable" , PIN_CONFIG_INPUT_SCHMITT_ENABLE, 0 }, |
179 | { "input-schmitt-enable" , PIN_CONFIG_INPUT_SCHMITT_ENABLE, 1 }, |
180 | { "low-power-disable" , PIN_CONFIG_MODE_LOW_POWER, 0 }, |
181 | { "low-power-enable" , PIN_CONFIG_MODE_LOW_POWER, 1 }, |
182 | { "output-disable" , PIN_CONFIG_OUTPUT_ENABLE, 0 }, |
183 | { "output-enable" , PIN_CONFIG_OUTPUT_ENABLE, 1 }, |
184 | { "output-high" , PIN_CONFIG_OUTPUT, 1, }, |
185 | { "output-impedance-ohms" , PIN_CONFIG_OUTPUT_IMPEDANCE_OHMS, 0 }, |
186 | { "output-low" , PIN_CONFIG_OUTPUT, 0, }, |
187 | { "power-source" , PIN_CONFIG_POWER_SOURCE, 0 }, |
188 | { "sleep-hardware-state" , PIN_CONFIG_SLEEP_HARDWARE_STATE, 0 }, |
189 | { "slew-rate" , PIN_CONFIG_SLEW_RATE, 0 }, |
190 | { "skew-delay" , PIN_CONFIG_SKEW_DELAY, 0 }, |
191 | }; |
192 | |
193 | /** |
194 | * parse_dt_cfg() - Parse DT pinconf parameters |
195 | * @np: DT node |
196 | * @params: Array of describing generic parameters |
197 | * @count: Number of entries in @params |
198 | * @cfg: Array of parsed config options |
199 | * @ncfg: Number of entries in @cfg |
200 | * |
201 | * Parse the config options described in @params from @np and puts the result |
202 | * in @cfg. @cfg does not need to be empty, entries are added beginning at |
203 | * @ncfg. @ncfg is updated to reflect the number of entries after parsing. @cfg |
204 | * needs to have enough memory allocated to hold all possible entries. |
205 | */ |
206 | static void parse_dt_cfg(struct device_node *np, |
207 | const struct pinconf_generic_params *params, |
208 | unsigned int count, unsigned long *cfg, |
209 | unsigned int *ncfg) |
210 | { |
211 | int i; |
212 | |
213 | for (i = 0; i < count; i++) { |
214 | u32 val; |
215 | int ret; |
216 | const struct pinconf_generic_params *par = ¶ms[i]; |
217 | |
218 | ret = of_property_read_u32(np, propname: par->property, out_value: &val); |
219 | |
220 | /* property not found */ |
221 | if (ret == -EINVAL) |
222 | continue; |
223 | |
224 | /* use default value, when no value is specified */ |
225 | if (ret) |
226 | val = par->default_value; |
227 | |
228 | pr_debug("found %s with value %u\n" , par->property, val); |
229 | cfg[*ncfg] = pinconf_to_config_packed(param: par->param, argument: val); |
230 | (*ncfg)++; |
231 | } |
232 | } |
233 | |
234 | /** |
235 | * pinconf_generic_parse_dt_config() |
236 | * parse the config properties into generic pinconfig values. |
237 | * @np: node containing the pinconfig properties |
238 | * @pctldev: pincontrol device |
239 | * @configs: array with nconfigs entries containing the generic pinconf values |
240 | * must be freed when no longer necessary. |
241 | * @nconfigs: number of configurations |
242 | */ |
243 | int pinconf_generic_parse_dt_config(struct device_node *np, |
244 | struct pinctrl_dev *pctldev, |
245 | unsigned long **configs, |
246 | unsigned int *nconfigs) |
247 | { |
248 | unsigned long *cfg; |
249 | unsigned int max_cfg, ncfg = 0; |
250 | int ret; |
251 | |
252 | if (!np) |
253 | return -EINVAL; |
254 | |
255 | /* allocate a temporary array big enough to hold one of each option */ |
256 | max_cfg = ARRAY_SIZE(dt_params); |
257 | if (pctldev) |
258 | max_cfg += pctldev->desc->num_custom_params; |
259 | cfg = kcalloc(n: max_cfg, size: sizeof(*cfg), GFP_KERNEL); |
260 | if (!cfg) |
261 | return -ENOMEM; |
262 | |
263 | parse_dt_cfg(np, params: dt_params, ARRAY_SIZE(dt_params), cfg, ncfg: &ncfg); |
264 | if (pctldev && pctldev->desc->num_custom_params && |
265 | pctldev->desc->custom_params) |
266 | parse_dt_cfg(np, params: pctldev->desc->custom_params, |
267 | count: pctldev->desc->num_custom_params, cfg, ncfg: &ncfg); |
268 | |
269 | ret = 0; |
270 | |
271 | /* no configs found at all */ |
272 | if (ncfg == 0) { |
273 | *configs = NULL; |
274 | *nconfigs = 0; |
275 | goto out; |
276 | } |
277 | |
278 | /* |
279 | * Now limit the number of configs to the real number of |
280 | * found properties. |
281 | */ |
282 | *configs = kmemdup(p: cfg, size: ncfg * sizeof(unsigned long), GFP_KERNEL); |
283 | if (!*configs) { |
284 | ret = -ENOMEM; |
285 | goto out; |
286 | } |
287 | |
288 | *nconfigs = ncfg; |
289 | |
290 | out: |
291 | kfree(objp: cfg); |
292 | return ret; |
293 | } |
294 | EXPORT_SYMBOL_GPL(pinconf_generic_parse_dt_config); |
295 | |
296 | int pinconf_generic_dt_subnode_to_map(struct pinctrl_dev *pctldev, |
297 | struct device_node *np, struct pinctrl_map **map, |
298 | unsigned *reserved_maps, unsigned *num_maps, |
299 | enum pinctrl_map_type type) |
300 | { |
301 | int ret; |
302 | const char *function; |
303 | struct device *dev = pctldev->dev; |
304 | unsigned long *configs = NULL; |
305 | unsigned num_configs = 0; |
306 | unsigned reserve, strings_count; |
307 | struct property *prop; |
308 | const char *group; |
309 | const char *subnode_target_type = "pins" ; |
310 | |
311 | ret = of_property_count_strings(np, propname: "pins" ); |
312 | if (ret < 0) { |
313 | ret = of_property_count_strings(np, propname: "groups" ); |
314 | if (ret < 0) |
315 | /* skip this node; may contain config child nodes */ |
316 | return 0; |
317 | if (type == PIN_MAP_TYPE_INVALID) |
318 | type = PIN_MAP_TYPE_CONFIGS_GROUP; |
319 | subnode_target_type = "groups" ; |
320 | } else { |
321 | if (type == PIN_MAP_TYPE_INVALID) |
322 | type = PIN_MAP_TYPE_CONFIGS_PIN; |
323 | } |
324 | strings_count = ret; |
325 | |
326 | ret = of_property_read_string(np, propname: "function" , out_string: &function); |
327 | if (ret < 0) { |
328 | /* EINVAL=missing, which is fine since it's optional */ |
329 | if (ret != -EINVAL) |
330 | dev_err(dev, "%pOF: could not parse property function\n" , |
331 | np); |
332 | function = NULL; |
333 | } |
334 | |
335 | ret = pinconf_generic_parse_dt_config(np, pctldev, &configs, |
336 | &num_configs); |
337 | if (ret < 0) { |
338 | dev_err(dev, "%pOF: could not parse node property\n" , np); |
339 | return ret; |
340 | } |
341 | |
342 | reserve = 0; |
343 | if (function != NULL) |
344 | reserve++; |
345 | if (num_configs) |
346 | reserve++; |
347 | |
348 | reserve *= strings_count; |
349 | |
350 | ret = pinctrl_utils_reserve_map(pctldev, map, reserved_maps, |
351 | num_maps, reserve); |
352 | if (ret < 0) |
353 | goto exit; |
354 | |
355 | of_property_for_each_string(np, subnode_target_type, prop, group) { |
356 | if (function) { |
357 | ret = pinctrl_utils_add_map_mux(pctldev, map, |
358 | reserved_maps, num_maps, group, |
359 | function); |
360 | if (ret < 0) |
361 | goto exit; |
362 | } |
363 | |
364 | if (num_configs) { |
365 | ret = pinctrl_utils_add_map_configs(pctldev, map, |
366 | reserved_maps, num_maps, group, configs, |
367 | num_configs, type); |
368 | if (ret < 0) |
369 | goto exit; |
370 | } |
371 | } |
372 | ret = 0; |
373 | |
374 | exit: |
375 | kfree(objp: configs); |
376 | return ret; |
377 | } |
378 | EXPORT_SYMBOL_GPL(pinconf_generic_dt_subnode_to_map); |
379 | |
380 | int pinconf_generic_dt_node_to_map(struct pinctrl_dev *pctldev, |
381 | struct device_node *np_config, struct pinctrl_map **map, |
382 | unsigned *num_maps, enum pinctrl_map_type type) |
383 | { |
384 | unsigned reserved_maps; |
385 | struct device_node *np; |
386 | int ret; |
387 | |
388 | reserved_maps = 0; |
389 | *map = NULL; |
390 | *num_maps = 0; |
391 | |
392 | ret = pinconf_generic_dt_subnode_to_map(pctldev, np_config, map, |
393 | &reserved_maps, num_maps, type); |
394 | if (ret < 0) |
395 | goto exit; |
396 | |
397 | for_each_available_child_of_node(np_config, np) { |
398 | ret = pinconf_generic_dt_subnode_to_map(pctldev, np, map, |
399 | &reserved_maps, num_maps, type); |
400 | if (ret < 0) { |
401 | of_node_put(node: np); |
402 | goto exit; |
403 | } |
404 | } |
405 | return 0; |
406 | |
407 | exit: |
408 | pinctrl_utils_free_map(pctldev, map: *map, num_maps: *num_maps); |
409 | return ret; |
410 | } |
411 | EXPORT_SYMBOL_GPL(pinconf_generic_dt_node_to_map); |
412 | |
413 | void pinconf_generic_dt_free_map(struct pinctrl_dev *pctldev, |
414 | struct pinctrl_map *map, |
415 | unsigned num_maps) |
416 | { |
417 | pinctrl_utils_free_map(pctldev, map, num_maps); |
418 | } |
419 | EXPORT_SYMBOL_GPL(pinconf_generic_dt_free_map); |
420 | |
421 | #endif |
422 | |