1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2016 IBM Corp. |
4 | */ |
5 | |
6 | #include <linux/mfd/syscon.h> |
7 | #include <linux/platform_device.h> |
8 | #include <linux/seq_file.h> |
9 | #include <linux/slab.h> |
10 | #include <linux/string.h> |
11 | #include "../core.h" |
12 | #include "pinctrl-aspeed.h" |
13 | |
14 | int aspeed_pinctrl_get_groups_count(struct pinctrl_dev *pctldev) |
15 | { |
16 | struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
17 | |
18 | return pdata->pinmux.ngroups; |
19 | } |
20 | |
21 | const char *aspeed_pinctrl_get_group_name(struct pinctrl_dev *pctldev, |
22 | unsigned int group) |
23 | { |
24 | struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
25 | |
26 | return pdata->pinmux.groups[group].name; |
27 | } |
28 | |
29 | int aspeed_pinctrl_get_group_pins(struct pinctrl_dev *pctldev, |
30 | unsigned int group, const unsigned int **pins, |
31 | unsigned int *npins) |
32 | { |
33 | struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
34 | |
35 | *pins = &pdata->pinmux.groups[group].pins[0]; |
36 | *npins = pdata->pinmux.groups[group].npins; |
37 | |
38 | return 0; |
39 | } |
40 | |
41 | void aspeed_pinctrl_pin_dbg_show(struct pinctrl_dev *pctldev, |
42 | struct seq_file *s, unsigned int offset) |
43 | { |
44 | seq_printf(m: s, fmt: " %s" , dev_name(dev: pctldev->dev)); |
45 | } |
46 | |
47 | int aspeed_pinmux_get_fn_count(struct pinctrl_dev *pctldev) |
48 | { |
49 | struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
50 | |
51 | return pdata->pinmux.nfunctions; |
52 | } |
53 | |
54 | const char *aspeed_pinmux_get_fn_name(struct pinctrl_dev *pctldev, |
55 | unsigned int function) |
56 | { |
57 | struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
58 | |
59 | return pdata->pinmux.functions[function].name; |
60 | } |
61 | |
62 | int aspeed_pinmux_get_fn_groups(struct pinctrl_dev *pctldev, |
63 | unsigned int function, |
64 | const char * const **groups, |
65 | unsigned int * const num_groups) |
66 | { |
67 | struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
68 | |
69 | *groups = pdata->pinmux.functions[function].groups; |
70 | *num_groups = pdata->pinmux.functions[function].ngroups; |
71 | |
72 | return 0; |
73 | } |
74 | |
75 | static int aspeed_sig_expr_enable(struct aspeed_pinmux_data *ctx, |
76 | const struct aspeed_sig_expr *expr) |
77 | { |
78 | int ret; |
79 | |
80 | pr_debug("Enabling signal %s for %s\n" , expr->signal, |
81 | expr->function); |
82 | |
83 | ret = aspeed_sig_expr_eval(ctx, expr, enabled: true); |
84 | if (ret < 0) |
85 | return ret; |
86 | |
87 | if (!ret) |
88 | return aspeed_sig_expr_set(ctx, expr, enabled: true); |
89 | |
90 | return 0; |
91 | } |
92 | |
93 | static int aspeed_sig_expr_disable(struct aspeed_pinmux_data *ctx, |
94 | const struct aspeed_sig_expr *expr) |
95 | { |
96 | int ret; |
97 | |
98 | pr_debug("Disabling signal %s for %s\n" , expr->signal, |
99 | expr->function); |
100 | |
101 | ret = aspeed_sig_expr_eval(ctx, expr, enabled: true); |
102 | if (ret < 0) |
103 | return ret; |
104 | |
105 | if (ret) |
106 | return aspeed_sig_expr_set(ctx, expr, enabled: false); |
107 | |
108 | return 0; |
109 | } |
110 | |
111 | /** |
112 | * aspeed_disable_sig() - Disable a signal on a pin by disabling all provided |
113 | * signal expressions. |
114 | * |
115 | * @ctx: The pinmux context |
116 | * @exprs: The list of signal expressions (from a priority level on a pin) |
117 | * |
118 | * Return: 0 if all expressions are disabled, otherwise a negative error code |
119 | */ |
120 | static int aspeed_disable_sig(struct aspeed_pinmux_data *ctx, |
121 | const struct aspeed_sig_expr **exprs) |
122 | { |
123 | int ret = 0; |
124 | |
125 | if (!exprs) |
126 | return -EINVAL; |
127 | |
128 | while (*exprs && !ret) { |
129 | ret = aspeed_sig_expr_disable(ctx, expr: *exprs); |
130 | exprs++; |
131 | } |
132 | |
133 | return ret; |
134 | } |
135 | |
136 | /** |
137 | * aspeed_find_expr_by_name - Search for the signal expression needed to |
138 | * enable the pin's signal for the requested function. |
139 | * |
140 | * @exprs: List of signal expressions (haystack) |
141 | * @name: The name of the requested function (needle) |
142 | * |
143 | * Return: A pointer to the signal expression whose function tag matches the |
144 | * provided name, otherwise NULL. |
145 | * |
146 | */ |
147 | static const struct aspeed_sig_expr *aspeed_find_expr_by_name( |
148 | const struct aspeed_sig_expr **exprs, const char *name) |
149 | { |
150 | while (*exprs) { |
151 | if (strcmp((*exprs)->function, name) == 0) |
152 | return *exprs; |
153 | exprs++; |
154 | } |
155 | |
156 | return NULL; |
157 | } |
158 | |
159 | static char *get_defined_attribute(const struct aspeed_pin_desc *pdesc, |
160 | const char *(*get)( |
161 | const struct aspeed_sig_expr *)) |
162 | { |
163 | char *found = NULL; |
164 | size_t len = 0; |
165 | const struct aspeed_sig_expr ***prios, **funcs, *expr; |
166 | |
167 | prios = pdesc->prios; |
168 | |
169 | while ((funcs = *prios)) { |
170 | while ((expr = *funcs)) { |
171 | const char *str = get(expr); |
172 | size_t delta = strlen(str) + 2; |
173 | char *expanded; |
174 | |
175 | expanded = krealloc(objp: found, new_size: len + delta + 1, GFP_KERNEL); |
176 | if (!expanded) { |
177 | kfree(objp: found); |
178 | return expanded; |
179 | } |
180 | |
181 | found = expanded; |
182 | found[len] = '\0'; |
183 | len += delta; |
184 | |
185 | strcat(p: found, q: str); |
186 | strcat(p: found, q: ", " ); |
187 | |
188 | funcs++; |
189 | } |
190 | prios++; |
191 | } |
192 | |
193 | if (len < 2) { |
194 | kfree(objp: found); |
195 | return NULL; |
196 | } |
197 | |
198 | found[len - 2] = '\0'; |
199 | |
200 | return found; |
201 | } |
202 | |
203 | static const char *aspeed_sig_expr_function(const struct aspeed_sig_expr *expr) |
204 | { |
205 | return expr->function; |
206 | } |
207 | |
208 | static char *get_defined_functions(const struct aspeed_pin_desc *pdesc) |
209 | { |
210 | return get_defined_attribute(pdesc, get: aspeed_sig_expr_function); |
211 | } |
212 | |
213 | static const char *aspeed_sig_expr_signal(const struct aspeed_sig_expr *expr) |
214 | { |
215 | return expr->signal; |
216 | } |
217 | |
218 | static char *get_defined_signals(const struct aspeed_pin_desc *pdesc) |
219 | { |
220 | return get_defined_attribute(pdesc, get: aspeed_sig_expr_signal); |
221 | } |
222 | |
223 | int aspeed_pinmux_set_mux(struct pinctrl_dev *pctldev, unsigned int function, |
224 | unsigned int group) |
225 | { |
226 | int i; |
227 | int ret; |
228 | struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
229 | const struct aspeed_pin_group *pgroup = &pdata->pinmux.groups[group]; |
230 | const struct aspeed_pin_function *pfunc = |
231 | &pdata->pinmux.functions[function]; |
232 | |
233 | for (i = 0; i < pgroup->npins; i++) { |
234 | int pin = pgroup->pins[i]; |
235 | const struct aspeed_pin_desc *pdesc = pdata->pins[pin].drv_data; |
236 | const struct aspeed_sig_expr *expr = NULL; |
237 | const struct aspeed_sig_expr **funcs; |
238 | const struct aspeed_sig_expr ***prios; |
239 | |
240 | if (!pdesc) |
241 | return -EINVAL; |
242 | |
243 | pr_debug("Muxing pin %s for %s\n" , pdesc->name, pfunc->name); |
244 | |
245 | prios = pdesc->prios; |
246 | |
247 | if (!prios) |
248 | continue; |
249 | |
250 | /* Disable functions at a higher priority than that requested */ |
251 | while ((funcs = *prios)) { |
252 | expr = aspeed_find_expr_by_name(exprs: funcs, name: pfunc->name); |
253 | |
254 | if (expr) |
255 | break; |
256 | |
257 | ret = aspeed_disable_sig(ctx: &pdata->pinmux, exprs: funcs); |
258 | if (ret) |
259 | return ret; |
260 | |
261 | prios++; |
262 | } |
263 | |
264 | if (!expr) { |
265 | char *functions = get_defined_functions(pdesc); |
266 | char *signals = get_defined_signals(pdesc); |
267 | |
268 | pr_warn("No function %s found on pin %s (%d). Found signal(s) %s for function(s) %s\n" , |
269 | pfunc->name, pdesc->name, pin, signals, |
270 | functions); |
271 | kfree(objp: signals); |
272 | kfree(objp: functions); |
273 | |
274 | return -ENXIO; |
275 | } |
276 | |
277 | ret = aspeed_sig_expr_enable(ctx: &pdata->pinmux, expr); |
278 | if (ret) |
279 | return ret; |
280 | |
281 | pr_debug("Muxed pin %s as %s for %s\n" , pdesc->name, expr->signal, |
282 | expr->function); |
283 | } |
284 | |
285 | return 0; |
286 | } |
287 | |
288 | static bool aspeed_expr_is_gpio(const struct aspeed_sig_expr *expr) |
289 | { |
290 | /* |
291 | * We need to differentiate between GPIO and non-GPIO signals to |
292 | * implement the gpio_request_enable() interface. For better or worse |
293 | * the ASPEED pinctrl driver uses the expression names to determine |
294 | * whether an expression will mux a pin for GPIO. |
295 | * |
296 | * Generally we have the following - A GPIO such as B1 has: |
297 | * |
298 | * - expr->signal set to "GPIOB1" |
299 | * - expr->function set to "GPIOB1" |
300 | * |
301 | * Using this fact we can determine whether the provided expression is |
302 | * a GPIO expression by testing the signal name for the string prefix |
303 | * "GPIO". |
304 | * |
305 | * However, some GPIOs are input-only, and the ASPEED datasheets name |
306 | * them differently. An input-only GPIO such as T0 has: |
307 | * |
308 | * - expr->signal set to "GPIT0" |
309 | * - expr->function set to "GPIT0" |
310 | * |
311 | * It's tempting to generalise the prefix test from "GPIO" to "GPI" to |
312 | * account for both GPIOs and GPIs, but in doing so we run aground on |
313 | * another feature: |
314 | * |
315 | * Some pins in the ASPEED BMC SoCs have a "pass-through" GPIO |
316 | * function where the input state of one pin is replicated as the |
317 | * output state of another (as if they were shorted together - a mux |
318 | * configuration that is typically enabled by hardware strapping). |
319 | * This feature allows the BMC to pass e.g. power button state through |
320 | * to the host while the BMC is yet to boot, but take control of the |
321 | * button state once the BMC has booted by muxing each pin as a |
322 | * separate, pin-specific GPIO. |
323 | * |
324 | * Conceptually this pass-through mode is a form of GPIO and is named |
325 | * as such in the datasheets, e.g. "GPID0". This naming similarity |
326 | * trips us up with the simple GPI-prefixed-signal-name scheme |
327 | * discussed above, as the pass-through configuration is not what we |
328 | * want when muxing a pin as GPIO for the GPIO subsystem. |
329 | * |
330 | * On e.g. the AST2400, a pass-through function "GPID0" is grouped on |
331 | * balls A18 and D16, where we have: |
332 | * |
333 | * For ball A18: |
334 | * - expr->signal set to "GPID0IN" |
335 | * - expr->function set to "GPID0" |
336 | * |
337 | * For ball D16: |
338 | * - expr->signal set to "GPID0OUT" |
339 | * - expr->function set to "GPID0" |
340 | * |
341 | * By contrast, the pin-specific GPIO expressions for the same pins are |
342 | * as follows: |
343 | * |
344 | * For ball A18: |
345 | * - expr->signal looks like "GPIOD0" |
346 | * - expr->function looks like "GPIOD0" |
347 | * |
348 | * For ball D16: |
349 | * - expr->signal looks like "GPIOD1" |
350 | * - expr->function looks like "GPIOD1" |
351 | * |
352 | * Testing both the signal _and_ function names gives us the means |
353 | * differentiate the pass-through GPIO pinmux configuration from the |
354 | * pin-specific configuration that the GPIO subsystem is after: An |
355 | * expression is a pin-specific (non-pass-through) GPIO configuration |
356 | * if the signal prefix is "GPI" and the signal name matches the |
357 | * function name. |
358 | */ |
359 | return !strncmp(expr->signal, "GPI" , 3) && |
360 | !strcmp(expr->signal, expr->function); |
361 | } |
362 | |
363 | static bool aspeed_gpio_in_exprs(const struct aspeed_sig_expr **exprs) |
364 | { |
365 | if (!exprs) |
366 | return false; |
367 | |
368 | while (*exprs) { |
369 | if (aspeed_expr_is_gpio(expr: *exprs)) |
370 | return true; |
371 | exprs++; |
372 | } |
373 | |
374 | return false; |
375 | } |
376 | |
377 | int aspeed_gpio_request_enable(struct pinctrl_dev *pctldev, |
378 | struct pinctrl_gpio_range *range, |
379 | unsigned int offset) |
380 | { |
381 | int ret; |
382 | struct aspeed_pinctrl_data *pdata = pinctrl_dev_get_drvdata(pctldev); |
383 | const struct aspeed_pin_desc *pdesc = pdata->pins[offset].drv_data; |
384 | const struct aspeed_sig_expr ***prios, **funcs, *expr; |
385 | |
386 | if (!pdesc) |
387 | return -EINVAL; |
388 | |
389 | prios = pdesc->prios; |
390 | |
391 | if (!prios) |
392 | return -ENXIO; |
393 | |
394 | pr_debug("Muxing pin %s for GPIO\n" , pdesc->name); |
395 | |
396 | /* Disable any functions of higher priority than GPIO */ |
397 | while ((funcs = *prios)) { |
398 | if (aspeed_gpio_in_exprs(exprs: funcs)) |
399 | break; |
400 | |
401 | ret = aspeed_disable_sig(ctx: &pdata->pinmux, exprs: funcs); |
402 | if (ret) |
403 | return ret; |
404 | |
405 | prios++; |
406 | } |
407 | |
408 | if (!funcs) { |
409 | char *signals = get_defined_signals(pdesc); |
410 | |
411 | pr_warn("No GPIO signal type found on pin %s (%d). Found: %s\n" , |
412 | pdesc->name, offset, signals); |
413 | kfree(objp: signals); |
414 | |
415 | return -ENXIO; |
416 | } |
417 | |
418 | expr = *funcs; |
419 | |
420 | /* |
421 | * Disabling all higher-priority expressions is enough to enable the |
422 | * lowest-priority signal type. As such it has no associated |
423 | * expression. |
424 | */ |
425 | if (!expr) { |
426 | pr_debug("Muxed pin %s as GPIO\n" , pdesc->name); |
427 | return 0; |
428 | } |
429 | |
430 | /* |
431 | * If GPIO is not the lowest priority signal type, assume there is only |
432 | * one expression defined to enable the GPIO function |
433 | */ |
434 | ret = aspeed_sig_expr_enable(ctx: &pdata->pinmux, expr); |
435 | if (ret) |
436 | return ret; |
437 | |
438 | pr_debug("Muxed pin %s as %s\n" , pdesc->name, expr->signal); |
439 | |
440 | return 0; |
441 | } |
442 | |
443 | int aspeed_pinctrl_probe(struct platform_device *pdev, |
444 | struct pinctrl_desc *pdesc, |
445 | struct aspeed_pinctrl_data *pdata) |
446 | { |
447 | struct device *parent; |
448 | struct pinctrl_dev *pctl; |
449 | |
450 | parent = pdev->dev.parent; |
451 | if (!parent) { |
452 | dev_err(&pdev->dev, "No parent for syscon pincontroller\n" ); |
453 | return -ENODEV; |
454 | } |
455 | |
456 | pdata->scu = syscon_node_to_regmap(np: parent->of_node); |
457 | if (IS_ERR(ptr: pdata->scu)) { |
458 | dev_err(&pdev->dev, "No regmap for syscon pincontroller parent\n" ); |
459 | return PTR_ERR(ptr: pdata->scu); |
460 | } |
461 | |
462 | pdata->pinmux.maps[ASPEED_IP_SCU] = pdata->scu; |
463 | |
464 | pctl = pinctrl_register(pctldesc: pdesc, dev: &pdev->dev, driver_data: pdata); |
465 | |
466 | if (IS_ERR(ptr: pctl)) { |
467 | dev_err(&pdev->dev, "Failed to register pinctrl\n" ); |
468 | return PTR_ERR(ptr: pctl); |
469 | } |
470 | |
471 | platform_set_drvdata(pdev, data: pdata); |
472 | |
473 | return 0; |
474 | } |
475 | |
476 | static inline bool pin_in_config_range(unsigned int offset, |
477 | const struct aspeed_pin_config *config) |
478 | { |
479 | return offset >= config->pins[0] && offset <= config->pins[1]; |
480 | } |
481 | |
482 | static inline const struct aspeed_pin_config *find_pinconf_config( |
483 | const struct aspeed_pinctrl_data *pdata, |
484 | unsigned int offset, |
485 | enum pin_config_param param) |
486 | { |
487 | unsigned int i; |
488 | |
489 | for (i = 0; i < pdata->nconfigs; i++) { |
490 | if (param == pdata->configs[i].param && |
491 | pin_in_config_range(offset, config: &pdata->configs[i])) |
492 | return &pdata->configs[i]; |
493 | } |
494 | |
495 | return NULL; |
496 | } |
497 | |
498 | enum aspeed_pin_config_map_type { MAP_TYPE_ARG, MAP_TYPE_VAL }; |
499 | |
500 | static const struct aspeed_pin_config_map *find_pinconf_map( |
501 | const struct aspeed_pinctrl_data *pdata, |
502 | enum pin_config_param param, |
503 | enum aspeed_pin_config_map_type type, |
504 | s64 value) |
505 | { |
506 | int i; |
507 | |
508 | for (i = 0; i < pdata->nconfmaps; i++) { |
509 | const struct aspeed_pin_config_map *elem; |
510 | bool match; |
511 | |
512 | elem = &pdata->confmaps[i]; |
513 | |
514 | switch (type) { |
515 | case MAP_TYPE_ARG: |
516 | match = (elem->arg == -1 || elem->arg == value); |
517 | break; |
518 | case MAP_TYPE_VAL: |
519 | match = (elem->val == value); |
520 | break; |
521 | } |
522 | |
523 | if (param == elem->param && match) |
524 | return elem; |
525 | } |
526 | |
527 | return NULL; |
528 | } |
529 | |
530 | int aspeed_pin_config_get(struct pinctrl_dev *pctldev, unsigned int offset, |
531 | unsigned long *config) |
532 | { |
533 | const enum pin_config_param param = pinconf_to_config_param(config: *config); |
534 | const struct aspeed_pin_config_map *pmap; |
535 | const struct aspeed_pinctrl_data *pdata; |
536 | const struct aspeed_pin_config *pconf; |
537 | unsigned int val; |
538 | int rc = 0; |
539 | u32 arg; |
540 | |
541 | pdata = pinctrl_dev_get_drvdata(pctldev); |
542 | pconf = find_pinconf_config(pdata, offset, param); |
543 | if (!pconf) |
544 | return -ENOTSUPP; |
545 | |
546 | rc = regmap_read(map: pdata->scu, reg: pconf->reg, val: &val); |
547 | if (rc < 0) |
548 | return rc; |
549 | |
550 | pmap = find_pinconf_map(pdata, param, type: MAP_TYPE_VAL, |
551 | value: (val & pconf->mask) >> __ffs(pconf->mask)); |
552 | |
553 | if (!pmap) |
554 | return -EINVAL; |
555 | |
556 | if (param == PIN_CONFIG_DRIVE_STRENGTH) |
557 | arg = (u32) pmap->arg; |
558 | else if (param == PIN_CONFIG_BIAS_PULL_DOWN) |
559 | arg = !!pmap->arg; |
560 | else |
561 | arg = 1; |
562 | |
563 | if (!arg) |
564 | return -EINVAL; |
565 | |
566 | *config = pinconf_to_config_packed(param, argument: arg); |
567 | |
568 | return 0; |
569 | } |
570 | |
571 | int aspeed_pin_config_set(struct pinctrl_dev *pctldev, unsigned int offset, |
572 | unsigned long *configs, unsigned int num_configs) |
573 | { |
574 | const struct aspeed_pinctrl_data *pdata; |
575 | unsigned int i; |
576 | int rc = 0; |
577 | |
578 | pdata = pinctrl_dev_get_drvdata(pctldev); |
579 | |
580 | for (i = 0; i < num_configs; i++) { |
581 | const struct aspeed_pin_config_map *pmap; |
582 | const struct aspeed_pin_config *pconf; |
583 | enum pin_config_param param; |
584 | unsigned int val; |
585 | u32 arg; |
586 | |
587 | param = pinconf_to_config_param(config: configs[i]); |
588 | arg = pinconf_to_config_argument(config: configs[i]); |
589 | |
590 | pconf = find_pinconf_config(pdata, offset, param); |
591 | if (!pconf) |
592 | return -ENOTSUPP; |
593 | |
594 | pmap = find_pinconf_map(pdata, param, type: MAP_TYPE_ARG, value: arg); |
595 | |
596 | if (WARN_ON(!pmap)) |
597 | return -EINVAL; |
598 | |
599 | val = pmap->val << __ffs(pconf->mask); |
600 | |
601 | rc = regmap_update_bits(map: pdata->scu, reg: pconf->reg, |
602 | mask: pconf->mask, val); |
603 | |
604 | if (rc < 0) |
605 | return rc; |
606 | |
607 | pr_debug("%s: Set SCU%02X[0x%08X]=0x%X for param %d(=%d) on pin %d\n" , |
608 | __func__, pconf->reg, pconf->mask, |
609 | val, param, arg, offset); |
610 | } |
611 | |
612 | return 0; |
613 | } |
614 | |
615 | int aspeed_pin_config_group_get(struct pinctrl_dev *pctldev, |
616 | unsigned int selector, |
617 | unsigned long *config) |
618 | { |
619 | const unsigned int *pins; |
620 | unsigned int npins; |
621 | int rc; |
622 | |
623 | rc = aspeed_pinctrl_get_group_pins(pctldev, group: selector, pins: &pins, npins: &npins); |
624 | if (rc < 0) |
625 | return rc; |
626 | |
627 | if (!npins) |
628 | return -ENODEV; |
629 | |
630 | rc = aspeed_pin_config_get(pctldev, offset: pins[0], config); |
631 | |
632 | return rc; |
633 | } |
634 | |
635 | int aspeed_pin_config_group_set(struct pinctrl_dev *pctldev, |
636 | unsigned int selector, |
637 | unsigned long *configs, |
638 | unsigned int num_configs) |
639 | { |
640 | const unsigned int *pins; |
641 | unsigned int npins; |
642 | int rc; |
643 | int i; |
644 | |
645 | pr_debug("%s: Fetching pins for group selector %d\n" , |
646 | __func__, selector); |
647 | rc = aspeed_pinctrl_get_group_pins(pctldev, group: selector, pins: &pins, npins: &npins); |
648 | if (rc < 0) |
649 | return rc; |
650 | |
651 | for (i = 0; i < npins; i++) { |
652 | rc = aspeed_pin_config_set(pctldev, offset: pins[i], configs, |
653 | num_configs); |
654 | if (rc < 0) |
655 | return rc; |
656 | } |
657 | |
658 | return 0; |
659 | } |
660 | |