diff options
Diffstat (limited to 'drivers/pinctrl/pinctrl-sunxi.c')
-rw-r--r-- | drivers/pinctrl/pinctrl-sunxi.c | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/drivers/pinctrl/pinctrl-sunxi.c b/drivers/pinctrl/pinctrl-sunxi.c new file mode 100644 index 000000000000..1a81613e8f77 --- /dev/null +++ b/drivers/pinctrl/pinctrl-sunxi.c | |||
@@ -0,0 +1,548 @@ | |||
1 | /* | ||
2 | * Allwinner A1X SoCs pinctrl driver. | ||
3 | * | ||
4 | * Copyright (C) 2012 Maxime Ripard | ||
5 | * | ||
6 | * Maxime Ripard <maxime.ripard@free-electrons.com> | ||
7 | * | ||
8 | * This file is licensed under the terms of the GNU General Public | ||
9 | * License version 2. This program is licensed "as is" without any | ||
10 | * warranty of any kind, whether express or implied. | ||
11 | */ | ||
12 | |||
13 | #include <linux/io.h> | ||
14 | #include <linux/module.h> | ||
15 | #include <linux/of.h> | ||
16 | #include <linux/of_address.h> | ||
17 | #include <linux/of_device.h> | ||
18 | #include <linux/pinctrl/consumer.h> | ||
19 | #include <linux/pinctrl/machine.h> | ||
20 | #include <linux/pinctrl/pinctrl.h> | ||
21 | #include <linux/pinctrl/pinconf-generic.h> | ||
22 | #include <linux/pinctrl/pinmux.h> | ||
23 | #include <linux/platform_device.h> | ||
24 | #include <linux/slab.h> | ||
25 | |||
26 | #include "core.h" | ||
27 | #include "pinctrl-sunxi.h" | ||
28 | |||
29 | static struct sunxi_pinctrl_group * | ||
30 | sunxi_pinctrl_find_group_by_name(struct sunxi_pinctrl *pctl, const char *group) | ||
31 | { | ||
32 | int i; | ||
33 | |||
34 | for (i = 0; i < pctl->ngroups; i++) { | ||
35 | struct sunxi_pinctrl_group *grp = pctl->groups + i; | ||
36 | |||
37 | if (!strcmp(grp->name, group)) | ||
38 | return grp; | ||
39 | } | ||
40 | |||
41 | return NULL; | ||
42 | } | ||
43 | |||
44 | static struct sunxi_pinctrl_function * | ||
45 | sunxi_pinctrl_find_function_by_name(struct sunxi_pinctrl *pctl, | ||
46 | const char *name) | ||
47 | { | ||
48 | struct sunxi_pinctrl_function *func = pctl->functions; | ||
49 | int i; | ||
50 | |||
51 | for (i = 0; i < pctl->nfunctions; i++) { | ||
52 | if (!func[i].name) | ||
53 | break; | ||
54 | |||
55 | if (!strcmp(func[i].name, name)) | ||
56 | return func + i; | ||
57 | } | ||
58 | |||
59 | return NULL; | ||
60 | } | ||
61 | |||
62 | static struct sunxi_desc_function * | ||
63 | sunxi_pinctrl_desc_find_function_by_name(struct sunxi_pinctrl *pctl, | ||
64 | const char *pin_name, | ||
65 | const char *func_name) | ||
66 | { | ||
67 | int i; | ||
68 | |||
69 | for (i = 0; i < pctl->desc->npins; i++) { | ||
70 | const struct sunxi_desc_pin *pin = pctl->desc->pins + i; | ||
71 | |||
72 | if (!strcmp(pin->pin.name, pin_name)) { | ||
73 | struct sunxi_desc_function *func = pin->functions; | ||
74 | |||
75 | while (func->name) { | ||
76 | if (!strcmp(func->name, func_name)) | ||
77 | return func; | ||
78 | |||
79 | func++; | ||
80 | } | ||
81 | } | ||
82 | } | ||
83 | |||
84 | return NULL; | ||
85 | } | ||
86 | |||
87 | static int sunxi_pctrl_get_groups_count(struct pinctrl_dev *pctldev) | ||
88 | { | ||
89 | struct sunxi_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); | ||
90 | |||
91 | return pctl->ngroups; | ||
92 | } | ||
93 | |||
94 | static const char *sunxi_pctrl_get_group_name(struct pinctrl_dev *pctldev, | ||
95 | unsigned group) | ||
96 | { | ||
97 | struct sunxi_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); | ||
98 | |||
99 | return pctl->groups[group].name; | ||
100 | } | ||
101 | |||
102 | static int sunxi_pctrl_get_group_pins(struct pinctrl_dev *pctldev, | ||
103 | unsigned group, | ||
104 | const unsigned **pins, | ||
105 | unsigned *num_pins) | ||
106 | { | ||
107 | struct sunxi_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); | ||
108 | |||
109 | *pins = (unsigned *)&pctl->groups[group].pin; | ||
110 | *num_pins = 1; | ||
111 | |||
112 | return 0; | ||
113 | } | ||
114 | |||
115 | static int sunxi_pctrl_dt_node_to_map(struct pinctrl_dev *pctldev, | ||
116 | struct device_node *node, | ||
117 | struct pinctrl_map **map, | ||
118 | unsigned *num_maps) | ||
119 | { | ||
120 | struct sunxi_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); | ||
121 | unsigned long *pinconfig; | ||
122 | struct property *prop; | ||
123 | const char *function; | ||
124 | const char *group; | ||
125 | int ret, nmaps, i = 0; | ||
126 | u32 val; | ||
127 | |||
128 | *map = NULL; | ||
129 | *num_maps = 0; | ||
130 | |||
131 | ret = of_property_read_string(node, "allwinner,function", &function); | ||
132 | if (ret) { | ||
133 | dev_err(pctl->dev, | ||
134 | "missing allwinner,function property in node %s\n", | ||
135 | node->name); | ||
136 | return -EINVAL; | ||
137 | } | ||
138 | |||
139 | nmaps = of_property_count_strings(node, "allwinner,pins") * 2; | ||
140 | if (nmaps < 0) { | ||
141 | dev_err(pctl->dev, | ||
142 | "missing allwinner,pins property in node %s\n", | ||
143 | node->name); | ||
144 | return -EINVAL; | ||
145 | } | ||
146 | |||
147 | *map = kmalloc(nmaps * sizeof(struct pinctrl_map), GFP_KERNEL); | ||
148 | if (!map) | ||
149 | return -ENOMEM; | ||
150 | |||
151 | of_property_for_each_string(node, "allwinner,pins", prop, group) { | ||
152 | struct sunxi_pinctrl_group *grp = | ||
153 | sunxi_pinctrl_find_group_by_name(pctl, group); | ||
154 | int j = 0, configlen = 0; | ||
155 | |||
156 | if (!grp) { | ||
157 | dev_err(pctl->dev, "unknown pin %s", group); | ||
158 | continue; | ||
159 | } | ||
160 | |||
161 | if (!sunxi_pinctrl_desc_find_function_by_name(pctl, | ||
162 | grp->name, | ||
163 | function)) { | ||
164 | dev_err(pctl->dev, "unsupported function %s on pin %s", | ||
165 | function, group); | ||
166 | continue; | ||
167 | } | ||
168 | |||
169 | (*map)[i].type = PIN_MAP_TYPE_MUX_GROUP; | ||
170 | (*map)[i].data.mux.group = group; | ||
171 | (*map)[i].data.mux.function = function; | ||
172 | |||
173 | i++; | ||
174 | |||
175 | (*map)[i].type = PIN_MAP_TYPE_CONFIGS_GROUP; | ||
176 | (*map)[i].data.configs.group_or_pin = group; | ||
177 | |||
178 | if (of_find_property(node, "allwinner,drive", NULL)) | ||
179 | configlen++; | ||
180 | if (of_find_property(node, "allwinner,pull", NULL)) | ||
181 | configlen++; | ||
182 | |||
183 | pinconfig = kzalloc(configlen * sizeof(*pinconfig), GFP_KERNEL); | ||
184 | |||
185 | if (!of_property_read_u32(node, "allwinner,drive", &val)) { | ||
186 | u16 strength = (val + 1) * 10; | ||
187 | pinconfig[j++] = | ||
188 | pinconf_to_config_packed(PIN_CONFIG_DRIVE_STRENGTH, | ||
189 | strength); | ||
190 | } | ||
191 | |||
192 | if (!of_property_read_u32(node, "allwinner,pull", &val)) { | ||
193 | enum pin_config_param pull = PIN_CONFIG_END; | ||
194 | if (val == 1) | ||
195 | pull = PIN_CONFIG_BIAS_PULL_UP; | ||
196 | else if (val == 2) | ||
197 | pull = PIN_CONFIG_BIAS_PULL_DOWN; | ||
198 | pinconfig[j++] = pinconf_to_config_packed(pull, 0); | ||
199 | } | ||
200 | |||
201 | (*map)[i].data.configs.configs = pinconfig; | ||
202 | (*map)[i].data.configs.num_configs = configlen; | ||
203 | |||
204 | i++; | ||
205 | } | ||
206 | |||
207 | *num_maps = nmaps; | ||
208 | |||
209 | return 0; | ||
210 | } | ||
211 | |||
212 | static void sunxi_pctrl_dt_free_map(struct pinctrl_dev *pctldev, | ||
213 | struct pinctrl_map *map, | ||
214 | unsigned num_maps) | ||
215 | { | ||
216 | int i; | ||
217 | |||
218 | for (i = 0; i < num_maps; i++) { | ||
219 | if (map[i].type == PIN_MAP_TYPE_CONFIGS_GROUP) | ||
220 | kfree(map[i].data.configs.configs); | ||
221 | } | ||
222 | |||
223 | kfree(map); | ||
224 | } | ||
225 | |||
226 | static struct pinctrl_ops sunxi_pctrl_ops = { | ||
227 | .dt_node_to_map = sunxi_pctrl_dt_node_to_map, | ||
228 | .dt_free_map = sunxi_pctrl_dt_free_map, | ||
229 | .get_groups_count = sunxi_pctrl_get_groups_count, | ||
230 | .get_group_name = sunxi_pctrl_get_group_name, | ||
231 | .get_group_pins = sunxi_pctrl_get_group_pins, | ||
232 | }; | ||
233 | |||
234 | static int sunxi_pconf_group_get(struct pinctrl_dev *pctldev, | ||
235 | unsigned group, | ||
236 | unsigned long *config) | ||
237 | { | ||
238 | struct sunxi_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); | ||
239 | |||
240 | *config = pctl->groups[group].config; | ||
241 | |||
242 | return 0; | ||
243 | } | ||
244 | |||
245 | static int sunxi_pconf_group_set(struct pinctrl_dev *pctldev, | ||
246 | unsigned group, | ||
247 | unsigned long config) | ||
248 | { | ||
249 | struct sunxi_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); | ||
250 | struct sunxi_pinctrl_group *g = &pctl->groups[group]; | ||
251 | u32 val, mask; | ||
252 | u16 strength; | ||
253 | u8 dlevel; | ||
254 | |||
255 | switch (pinconf_to_config_param(config)) { | ||
256 | case PIN_CONFIG_DRIVE_STRENGTH: | ||
257 | strength = pinconf_to_config_argument(config); | ||
258 | if (strength > 40) | ||
259 | return -EINVAL; | ||
260 | /* | ||
261 | * We convert from mA to what the register expects: | ||
262 | * 0: 10mA | ||
263 | * 1: 20mA | ||
264 | * 2: 30mA | ||
265 | * 3: 40mA | ||
266 | */ | ||
267 | dlevel = strength / 10 - 1; | ||
268 | val = readl(pctl->membase + sunxi_dlevel_reg(g->pin)); | ||
269 | mask = DLEVEL_PINS_MASK << sunxi_dlevel_offset(g->pin); | ||
270 | writel((val & ~mask) | dlevel << sunxi_dlevel_offset(g->pin), | ||
271 | pctl->membase + sunxi_dlevel_reg(g->pin)); | ||
272 | break; | ||
273 | case PIN_CONFIG_BIAS_PULL_UP: | ||
274 | val = readl(pctl->membase + sunxi_pull_reg(g->pin)); | ||
275 | mask = PULL_PINS_MASK << sunxi_pull_offset(g->pin); | ||
276 | writel((val & ~mask) | 1 << sunxi_pull_offset(g->pin), | ||
277 | pctl->membase + sunxi_pull_reg(g->pin)); | ||
278 | break; | ||
279 | case PIN_CONFIG_BIAS_PULL_DOWN: | ||
280 | val = readl(pctl->membase + sunxi_pull_reg(g->pin)); | ||
281 | mask = PULL_PINS_MASK << sunxi_pull_offset(g->pin); | ||
282 | writel((val & ~mask) | 2 << sunxi_pull_offset(g->pin), | ||
283 | pctl->membase + sunxi_pull_reg(g->pin)); | ||
284 | break; | ||
285 | default: | ||
286 | break; | ||
287 | } | ||
288 | |||
289 | /* cache the config value */ | ||
290 | g->config = config; | ||
291 | |||
292 | return 0; | ||
293 | } | ||
294 | |||
295 | static struct pinconf_ops sunxi_pconf_ops = { | ||
296 | .pin_config_group_get = sunxi_pconf_group_get, | ||
297 | .pin_config_group_set = sunxi_pconf_group_set, | ||
298 | }; | ||
299 | |||
300 | static int sunxi_pmx_get_funcs_cnt(struct pinctrl_dev *pctldev) | ||
301 | { | ||
302 | struct sunxi_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); | ||
303 | |||
304 | return pctl->nfunctions; | ||
305 | } | ||
306 | |||
307 | static const char *sunxi_pmx_get_func_name(struct pinctrl_dev *pctldev, | ||
308 | unsigned function) | ||
309 | { | ||
310 | struct sunxi_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); | ||
311 | |||
312 | return pctl->functions[function].name; | ||
313 | } | ||
314 | |||
315 | static int sunxi_pmx_get_func_groups(struct pinctrl_dev *pctldev, | ||
316 | unsigned function, | ||
317 | const char * const **groups, | ||
318 | unsigned * const num_groups) | ||
319 | { | ||
320 | struct sunxi_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); | ||
321 | |||
322 | *groups = pctl->functions[function].groups; | ||
323 | *num_groups = pctl->functions[function].ngroups; | ||
324 | |||
325 | return 0; | ||
326 | } | ||
327 | |||
328 | static void sunxi_pmx_set(struct pinctrl_dev *pctldev, | ||
329 | unsigned pin, | ||
330 | u8 config) | ||
331 | { | ||
332 | struct sunxi_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); | ||
333 | |||
334 | u32 val = readl(pctl->membase + sunxi_mux_reg(pin)); | ||
335 | u32 mask = MUX_PINS_MASK << sunxi_mux_offset(pin); | ||
336 | writel((val & ~mask) | config << sunxi_mux_offset(pin), | ||
337 | pctl->membase + sunxi_mux_reg(pin)); | ||
338 | } | ||
339 | |||
340 | static int sunxi_pmx_enable(struct pinctrl_dev *pctldev, | ||
341 | unsigned function, | ||
342 | unsigned group) | ||
343 | { | ||
344 | struct sunxi_pinctrl *pctl = pinctrl_dev_get_drvdata(pctldev); | ||
345 | struct sunxi_pinctrl_group *g = pctl->groups + group; | ||
346 | struct sunxi_pinctrl_function *func = pctl->functions + function; | ||
347 | struct sunxi_desc_function *desc = | ||
348 | sunxi_pinctrl_desc_find_function_by_name(pctl, | ||
349 | g->name, | ||
350 | func->name); | ||
351 | |||
352 | if (!desc) | ||
353 | return -EINVAL; | ||
354 | |||
355 | sunxi_pmx_set(pctldev, g->pin, desc->muxval); | ||
356 | |||
357 | return 0; | ||
358 | } | ||
359 | |||
360 | static struct pinmux_ops sunxi_pmx_ops = { | ||
361 | .get_functions_count = sunxi_pmx_get_funcs_cnt, | ||
362 | .get_function_name = sunxi_pmx_get_func_name, | ||
363 | .get_function_groups = sunxi_pmx_get_func_groups, | ||
364 | .enable = sunxi_pmx_enable, | ||
365 | }; | ||
366 | |||
367 | static struct pinctrl_desc sunxi_pctrl_desc = { | ||
368 | .confops = &sunxi_pconf_ops, | ||
369 | .pctlops = &sunxi_pctrl_ops, | ||
370 | .pmxops = &sunxi_pmx_ops, | ||
371 | }; | ||
372 | |||
373 | static struct of_device_id sunxi_pinctrl_match[] = { | ||
374 | {} | ||
375 | }; | ||
376 | MODULE_DEVICE_TABLE(of, sunxi_pinctrl_match); | ||
377 | |||
378 | static int sunxi_pinctrl_add_function(struct sunxi_pinctrl *pctl, | ||
379 | const char *name) | ||
380 | { | ||
381 | struct sunxi_pinctrl_function *func = pctl->functions; | ||
382 | |||
383 | while (func->name) { | ||
384 | /* function already there */ | ||
385 | if (strcmp(func->name, name) == 0) { | ||
386 | func->ngroups++; | ||
387 | return -EEXIST; | ||
388 | } | ||
389 | func++; | ||
390 | } | ||
391 | |||
392 | func->name = name; | ||
393 | func->ngroups = 1; | ||
394 | |||
395 | pctl->nfunctions++; | ||
396 | |||
397 | return 0; | ||
398 | } | ||
399 | |||
400 | static int sunxi_pinctrl_build_state(struct platform_device *pdev) | ||
401 | { | ||
402 | struct sunxi_pinctrl *pctl = platform_get_drvdata(pdev); | ||
403 | int i; | ||
404 | |||
405 | pctl->ngroups = pctl->desc->npins; | ||
406 | |||
407 | /* Allocate groups */ | ||
408 | pctl->groups = devm_kzalloc(&pdev->dev, | ||
409 | pctl->ngroups * sizeof(*pctl->groups), | ||
410 | GFP_KERNEL); | ||
411 | if (!pctl->groups) | ||
412 | return -ENOMEM; | ||
413 | |||
414 | for (i = 0; i < pctl->desc->npins; i++) { | ||
415 | const struct sunxi_desc_pin *pin = pctl->desc->pins + i; | ||
416 | struct sunxi_pinctrl_group *group = pctl->groups + i; | ||
417 | |||
418 | group->name = pin->pin.name; | ||
419 | group->pin = pin->pin.number; | ||
420 | } | ||
421 | |||
422 | /* | ||
423 | * We suppose that we won't have any more functions than pins, | ||
424 | * we'll reallocate that later anyway | ||
425 | */ | ||
426 | pctl->functions = devm_kzalloc(&pdev->dev, | ||
427 | pctl->desc->npins * sizeof(*pctl->functions), | ||
428 | GFP_KERNEL); | ||
429 | if (!pctl->functions) | ||
430 | return -ENOMEM; | ||
431 | |||
432 | /* Count functions and their associated groups */ | ||
433 | for (i = 0; i < pctl->desc->npins; i++) { | ||
434 | const struct sunxi_desc_pin *pin = pctl->desc->pins + i; | ||
435 | struct sunxi_desc_function *func = pin->functions; | ||
436 | |||
437 | while (func->name) { | ||
438 | sunxi_pinctrl_add_function(pctl, func->name); | ||
439 | func++; | ||
440 | } | ||
441 | } | ||
442 | |||
443 | pctl->functions = krealloc(pctl->functions, | ||
444 | pctl->nfunctions * sizeof(*pctl->functions), | ||
445 | GFP_KERNEL); | ||
446 | |||
447 | for (i = 0; i < pctl->desc->npins; i++) { | ||
448 | const struct sunxi_desc_pin *pin = pctl->desc->pins + i; | ||
449 | struct sunxi_desc_function *func = pin->functions; | ||
450 | |||
451 | while (func->name) { | ||
452 | struct sunxi_pinctrl_function *func_item; | ||
453 | const char **func_grp; | ||
454 | |||
455 | func_item = sunxi_pinctrl_find_function_by_name(pctl, | ||
456 | func->name); | ||
457 | if (!func_item) | ||
458 | return -EINVAL; | ||
459 | |||
460 | if (!func_item->groups) { | ||
461 | func_item->groups = | ||
462 | devm_kzalloc(&pdev->dev, | ||
463 | func_item->ngroups * sizeof(*func_item->groups), | ||
464 | GFP_KERNEL); | ||
465 | if (!func_item->groups) | ||
466 | return -ENOMEM; | ||
467 | } | ||
468 | |||
469 | func_grp = func_item->groups; | ||
470 | while (*func_grp) | ||
471 | func_grp++; | ||
472 | |||
473 | *func_grp = pin->pin.name; | ||
474 | func++; | ||
475 | } | ||
476 | } | ||
477 | |||
478 | return 0; | ||
479 | } | ||
480 | |||
481 | static int sunxi_pinctrl_probe(struct platform_device *pdev) | ||
482 | { | ||
483 | struct device_node *node = pdev->dev.of_node; | ||
484 | const struct of_device_id *device; | ||
485 | struct pinctrl_pin_desc *pins; | ||
486 | struct sunxi_pinctrl *pctl; | ||
487 | int i, ret; | ||
488 | |||
489 | pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL); | ||
490 | if (!pctl) | ||
491 | return -ENOMEM; | ||
492 | platform_set_drvdata(pdev, pctl); | ||
493 | |||
494 | pctl->membase = of_iomap(node, 0); | ||
495 | if (!pctl->membase) | ||
496 | return -ENOMEM; | ||
497 | |||
498 | device = of_match_device(sunxi_pinctrl_match, &pdev->dev); | ||
499 | if (!device) | ||
500 | return -ENODEV; | ||
501 | |||
502 | pctl->desc = (struct sunxi_pinctrl_desc *)device->data; | ||
503 | |||
504 | ret = sunxi_pinctrl_build_state(pdev); | ||
505 | if (ret) { | ||
506 | dev_err(&pdev->dev, "dt probe failed: %d\n", ret); | ||
507 | return ret; | ||
508 | } | ||
509 | |||
510 | pins = devm_kzalloc(&pdev->dev, | ||
511 | pctl->desc->npins * sizeof(*pins), | ||
512 | GFP_KERNEL); | ||
513 | if (!pins) | ||
514 | return -ENOMEM; | ||
515 | |||
516 | for (i = 0; i < pctl->desc->npins; i++) | ||
517 | pins[i] = pctl->desc->pins[i].pin; | ||
518 | |||
519 | sunxi_pctrl_desc.name = dev_name(&pdev->dev); | ||
520 | sunxi_pctrl_desc.owner = THIS_MODULE; | ||
521 | sunxi_pctrl_desc.pins = pins; | ||
522 | sunxi_pctrl_desc.npins = pctl->desc->npins; | ||
523 | pctl->dev = &pdev->dev; | ||
524 | pctl->pctl_dev = pinctrl_register(&sunxi_pctrl_desc, | ||
525 | &pdev->dev, pctl); | ||
526 | if (!pctl->pctl_dev) { | ||
527 | dev_err(&pdev->dev, "couldn't register pinctrl driver\n"); | ||
528 | return -EINVAL; | ||
529 | } | ||
530 | |||
531 | dev_info(&pdev->dev, "initialized sunXi pin control driver\n"); | ||
532 | |||
533 | return 0; | ||
534 | } | ||
535 | |||
536 | static struct platform_driver sunxi_pinctrl_driver = { | ||
537 | .probe = sunxi_pinctrl_probe, | ||
538 | .driver = { | ||
539 | .name = "sunxi-pinctrl", | ||
540 | .owner = THIS_MODULE, | ||
541 | .of_match_table = sunxi_pinctrl_match, | ||
542 | }, | ||
543 | }; | ||
544 | module_platform_driver(sunxi_pinctrl_driver); | ||
545 | |||
546 | MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com"); | ||
547 | MODULE_DESCRIPTION("Allwinner A1X pinctrl driver"); | ||
548 | MODULE_LICENSE("GPL"); | ||