blob: 986889b6a1f6089e45ff762f21bb7ee6e1facbdb [file] [log] [blame]
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +01001/* IIO - useful set of util functionality
2 *
3 * Copyright (c) 2008 Jonathan Cameron
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
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +010010/* Made up value to limit allocation sizes */
11#include <string.h>
12#include <stdlib.h>
Jonathan Camerone58537c2010-10-08 12:14:14 +010013#include <ctype.h>
14#include <stdio.h>
15#include <stdint.h>
Lars-Peter Clausenbc9f35d2011-10-26 17:27:44 +010016#include <dirent.h>
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +010017
18#define IIO_MAX_NAME_LENGTH 30
19
Jonathan Cameron1aa04272011-08-30 12:32:47 +010020#define FORMAT_SCAN_ELEMENTS_DIR "%s/scan_elements"
Jonathan Camerone58537c2010-10-08 12:14:14 +010021#define FORMAT_TYPE_FILE "%s_type"
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +010022
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +010023const char *iio_dir = "/sys/bus/iio/devices/";
24
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +010025/**
Jonathan Camerone58537c2010-10-08 12:14:14 +010026 * iioutils_break_up_name() - extract generic name from full channel name
27 * @full_name: the full channel name
28 * @generic_name: the output generic channel name
29 **/
30static int iioutils_break_up_name(const char *full_name,
31 char **generic_name)
32{
33 char *current;
34 char *w, *r;
35 char *working;
36 current = strdup(full_name);
37 working = strtok(current, "_\0");
38 w = working;
39 r = working;
40
Michael Hennerich8b68bb22011-03-08 08:55:48 +010041 while (*r != '\0') {
Jonathan Camerone58537c2010-10-08 12:14:14 +010042 if (!isdigit(*r)) {
43 *w = *r;
44 w++;
45 }
46 r++;
47 }
48 *w = '\0';
49 *generic_name = strdup(working);
50 free(current);
51
52 return 0;
53}
54
55/**
56 * struct iio_channel_info - information about a given channel
57 * @name: channel name
58 * @generic_name: general name for channel type
59 * @scale: scale factor to be applied for conversion to si units
60 * @offset: offset to be applied for conversion to si units
61 * @index: the channel index in the buffer output
62 * @bytes: number of bytes occupied in buffer output
63 * @mask: a bit mask for the raw output
64 * @is_signed: is the raw value stored signed
65 * @enabled: is this channel enabled
66 **/
67struct iio_channel_info {
68 char *name;
69 char *generic_name;
70 float scale;
71 float offset;
72 unsigned index;
73 unsigned bytes;
74 unsigned bits_used;
Jonathan Cameron52615d42011-05-18 14:41:19 +010075 unsigned shift;
Jonathan Camerone58537c2010-10-08 12:14:14 +010076 uint64_t mask;
77 unsigned is_signed;
78 unsigned enabled;
79 unsigned location;
80};
81
82/**
83 * iioutils_get_type() - find and process _type attribute data
84 * @is_signed: output whether channel is signed
85 * @bytes: output how many bytes the channel storage occupies
86 * @mask: output a bit mask for the raw data
87 * @device_dir: the iio device directory
88 * @name: the channel name
89 * @generic_name: the channel type name
90 **/
91inline int iioutils_get_type(unsigned *is_signed,
92 unsigned *bytes,
93 unsigned *bits_used,
Jonathan Cameron52615d42011-05-18 14:41:19 +010094 unsigned *shift,
Jonathan Camerone58537c2010-10-08 12:14:14 +010095 uint64_t *mask,
96 const char *device_dir,
97 const char *name,
98 const char *generic_name)
99{
100 FILE *sysfsfp;
101 int ret;
102 DIR *dp;
103 char *scan_el_dir, *builtname, *builtname_generic, *filename = 0;
104 char signchar;
Michael Hennerichfc7f95a2011-02-24 16:34:54 +0100105 unsigned padint;
Jonathan Camerone58537c2010-10-08 12:14:14 +0100106 const struct dirent *ent;
107
108 ret = asprintf(&scan_el_dir, FORMAT_SCAN_ELEMENTS_DIR, device_dir);
109 if (ret < 0) {
110 ret = -ENOMEM;
111 goto error_ret;
112 }
113 ret = asprintf(&builtname, FORMAT_TYPE_FILE, name);
114 if (ret < 0) {
115 ret = -ENOMEM;
116 goto error_free_scan_el_dir;
117 }
118 ret = asprintf(&builtname_generic, FORMAT_TYPE_FILE, generic_name);
119 if (ret < 0) {
120 ret = -ENOMEM;
121 goto error_free_builtname;
122 }
123
124 dp = opendir(scan_el_dir);
125 if (dp == NULL) {
126 ret = -errno;
127 goto error_free_builtname_generic;
128 }
129 while (ent = readdir(dp), ent != NULL)
130 /*
131 * Do we allow devices to override a generic name with
132 * a specific one?
133 */
134 if ((strcmp(builtname, ent->d_name) == 0) ||
135 (strcmp(builtname_generic, ent->d_name) == 0)) {
136 ret = asprintf(&filename,
137 "%s/%s", scan_el_dir, ent->d_name);
138 if (ret < 0) {
139 ret = -ENOMEM;
140 goto error_closedir;
141 }
142 sysfsfp = fopen(filename, "r");
143 if (sysfsfp == NULL) {
144 printf("failed to open %s\n", filename);
145 ret = -errno;
146 goto error_free_filename;
147 }
Jonathan Camerona7f7c362011-12-04 19:10:58 +0000148
149 ret = fscanf(sysfsfp,
150 "%ce:%c%u/%u>>%u",
151 &endianchar,
152 &signchar,
153 bits_used,
154 &padint, shift);
155 if (ret < 0) {
156 printf("failed to pass scan type description\n");
157 return ret;
158 }
Jonathan Camerone58537c2010-10-08 12:14:14 +0100159 *bytes = padint / 8;
Michael Hennerichfc7f95a2011-02-24 16:34:54 +0100160 if (*bits_used == 64)
Jonathan Camerone58537c2010-10-08 12:14:14 +0100161 *mask = ~0;
162 else
163 *mask = (1 << *bits_used) - 1;
164 if (signchar == 's')
165 *is_signed = 1;
166 else
167 *is_signed = 0;
Jonathan Camerona7f7c362011-12-04 19:10:58 +0000168 fclose(sysfsfp);
169 free(filename);
170
171 filename = 0;
Jonathan Camerone58537c2010-10-08 12:14:14 +0100172 }
173error_free_filename:
174 if (filename)
175 free(filename);
176error_closedir:
177 closedir(dp);
178error_free_builtname_generic:
179 free(builtname_generic);
180error_free_builtname:
181 free(builtname);
182error_free_scan_el_dir:
183 free(scan_el_dir);
184error_ret:
185 return ret;
186}
187
188inline int iioutils_get_param_float(float *output,
189 const char *param_name,
190 const char *device_dir,
191 const char *name,
192 const char *generic_name)
193{
194 FILE *sysfsfp;
195 int ret;
196 DIR *dp;
197 char *builtname, *builtname_generic;
198 char *filename = NULL;
199 const struct dirent *ent;
200
201 ret = asprintf(&builtname, "%s_%s", name, param_name);
202 if (ret < 0) {
203 ret = -ENOMEM;
204 goto error_ret;
205 }
206 ret = asprintf(&builtname_generic,
207 "%s_%s", generic_name, param_name);
208 if (ret < 0) {
209 ret = -ENOMEM;
210 goto error_free_builtname;
211 }
212 dp = opendir(device_dir);
213 if (dp == NULL) {
214 ret = -errno;
215 goto error_free_builtname_generic;
216 }
217 while (ent = readdir(dp), ent != NULL)
218 if ((strcmp(builtname, ent->d_name) == 0) ||
219 (strcmp(builtname_generic, ent->d_name) == 0)) {
220 ret = asprintf(&filename,
221 "%s/%s", device_dir, ent->d_name);
222 if (ret < 0) {
223 ret = -ENOMEM;
224 goto error_closedir;
225 }
226 sysfsfp = fopen(filename, "r");
227 if (!sysfsfp) {
228 ret = -errno;
229 goto error_free_filename;
230 }
231 fscanf(sysfsfp, "%f", output);
232 break;
233 }
234error_free_filename:
235 if (filename)
236 free(filename);
237error_closedir:
238 closedir(dp);
239error_free_builtname_generic:
240 free(builtname_generic);
241error_free_builtname:
242 free(builtname);
243error_ret:
244 return ret;
245}
246
Michael Hennerich8b68bb22011-03-08 08:55:48 +0100247/**
248 * bsort_channel_array_by_index() - reorder so that the array is in index order
249 *
250 **/
251
252inline void bsort_channel_array_by_index(struct iio_channel_info **ci_array,
253 int cnt)
254{
255
256 struct iio_channel_info temp;
257 int x, y;
258
259 for (x = 0; x < cnt; x++)
260 for (y = 0; y < (cnt - 1); y++)
261 if ((*ci_array)[y].index > (*ci_array)[y+1].index) {
262 temp = (*ci_array)[y + 1];
263 (*ci_array)[y + 1] = (*ci_array)[y];
264 (*ci_array)[y] = temp;
265 }
266}
Jonathan Camerone58537c2010-10-08 12:14:14 +0100267
268/**
269 * build_channel_array() - function to figure out what channels are present
270 * @device_dir: the IIO device directory in sysfs
271 * @
272 **/
273inline int build_channel_array(const char *device_dir,
274 struct iio_channel_info **ci_array,
275 int *counter)
276{
277 DIR *dp;
278 FILE *sysfsfp;
Michael Hennerich8b68bb22011-03-08 08:55:48 +0100279 int count, temp, i;
Jonathan Camerone58537c2010-10-08 12:14:14 +0100280 struct iio_channel_info *current;
281 int ret;
282 const struct dirent *ent;
283 char *scan_el_dir;
284 char *filename;
285
286 *counter = 0;
287 ret = asprintf(&scan_el_dir, FORMAT_SCAN_ELEMENTS_DIR, device_dir);
288 if (ret < 0) {
289 ret = -ENOMEM;
290 goto error_ret;
291 }
292 dp = opendir(scan_el_dir);
293 if (dp == NULL) {
294 ret = -errno;
295 goto error_free_name;
296 }
297 while (ent = readdir(dp), ent != NULL)
298 if (strcmp(ent->d_name + strlen(ent->d_name) - strlen("_en"),
299 "_en") == 0) {
300 ret = asprintf(&filename,
301 "%s/%s", scan_el_dir, ent->d_name);
302 if (ret < 0) {
303 ret = -ENOMEM;
304 goto error_close_dir;
305 }
306 sysfsfp = fopen(filename, "r");
307 if (sysfsfp == NULL) {
308 ret = -errno;
309 free(filename);
310 goto error_close_dir;
311 }
312 fscanf(sysfsfp, "%u", &ret);
313 if (ret == 1)
314 (*counter)++;
315 fclose(sysfsfp);
316 free(filename);
317 }
Michael Hennerich8b68bb22011-03-08 08:55:48 +0100318 *ci_array = malloc(sizeof(**ci_array) * (*counter));
Jonathan Camerone58537c2010-10-08 12:14:14 +0100319 if (*ci_array == NULL) {
320 ret = -ENOMEM;
321 goto error_close_dir;
322 }
323 seekdir(dp, 0);
Michael Hennerich7ccd4502011-02-24 16:34:53 +0100324 count = 0;
Jonathan Camerone58537c2010-10-08 12:14:14 +0100325 while (ent = readdir(dp), ent != NULL) {
326 if (strcmp(ent->d_name + strlen(ent->d_name) - strlen("_en"),
327 "_en") == 0) {
328 current = &(*ci_array)[count++];
329 ret = asprintf(&filename,
330 "%s/%s", scan_el_dir, ent->d_name);
331 if (ret < 0) {
332 ret = -ENOMEM;
333 /* decrement count to avoid freeing name */
334 count--;
335 goto error_cleanup_array;
336 }
337 sysfsfp = fopen(filename, "r");
338 if (sysfsfp == NULL) {
339 free(filename);
340 ret = -errno;
341 goto error_cleanup_array;
342 }
343 fscanf(sysfsfp, "%u", &current->enabled);
344 fclose(sysfsfp);
Michael Hennerich8b68bb22011-03-08 08:55:48 +0100345
346 if (!current->enabled) {
347 free(filename);
348 count--;
349 continue;
350 }
351
Jonathan Camerone58537c2010-10-08 12:14:14 +0100352 current->scale = 1.0;
353 current->offset = 0;
354 current->name = strndup(ent->d_name,
355 strlen(ent->d_name) -
356 strlen("_en"));
357 if (current->name == NULL) {
358 free(filename);
359 ret = -ENOMEM;
360 goto error_cleanup_array;
361 }
362 /* Get the generic and specific name elements */
363 ret = iioutils_break_up_name(current->name,
364 &current->generic_name);
365 if (ret) {
366 free(filename);
367 goto error_cleanup_array;
368 }
369 ret = asprintf(&filename,
370 "%s/%s_index",
371 scan_el_dir,
372 current->name);
373 if (ret < 0) {
374 free(filename);
375 ret = -ENOMEM;
376 goto error_cleanup_array;
377 }
378 sysfsfp = fopen(filename, "r");
379 fscanf(sysfsfp, "%u", &current->index);
380 fclose(sysfsfp);
381 free(filename);
382 /* Find the scale */
383 ret = iioutils_get_param_float(&current->scale,
384 "scale",
385 device_dir,
386 current->name,
387 current->generic_name);
388 if (ret < 0)
389 goto error_cleanup_array;
390 ret = iioutils_get_param_float(&current->offset,
391 "offset",
392 device_dir,
393 current->name,
394 current->generic_name);
395 if (ret < 0)
396 goto error_cleanup_array;
397 ret = iioutils_get_type(&current->is_signed,
398 &current->bytes,
399 &current->bits_used,
Jonathan Cameron52615d42011-05-18 14:41:19 +0100400 &current->shift,
Jonathan Camerone58537c2010-10-08 12:14:14 +0100401 &current->mask,
402 device_dir,
403 current->name,
404 current->generic_name);
405 }
406 }
Michael Hennerich8b68bb22011-03-08 08:55:48 +0100407
Jonathan Camerone58537c2010-10-08 12:14:14 +0100408 closedir(dp);
Michael Hennerich8b68bb22011-03-08 08:55:48 +0100409 /* reorder so that the array is in index order */
410 bsort_channel_array_by_index(ci_array, *counter);
Jonathan Camerone58537c2010-10-08 12:14:14 +0100411
412 return 0;
413
414error_cleanup_array:
roel kluin267024a2011-01-01 18:01:51 +0000415 for (i = count - 1; i >= 0; i--)
Jonathan Camerone58537c2010-10-08 12:14:14 +0100416 free((*ci_array)[i].name);
417 free(*ci_array);
418error_close_dir:
419 closedir(dp);
420error_free_name:
421 free(scan_el_dir);
422error_ret:
423 return ret;
424}
425
426/**
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100427 * find_type_by_name() - function to match top level types by name
428 * @name: top level type instance name
429 * @type: the type of top level instance being sort
430 *
431 * Typical types this is used for are device and trigger.
432 **/
433inline int find_type_by_name(const char *name, const char *type)
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100434{
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100435 const struct dirent *ent;
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100436 int number, numstrlen;
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100437
438 FILE *nameFile;
439 DIR *dp;
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100440 char thisname[IIO_MAX_NAME_LENGTH];
441 char *filename;
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100442
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100443 dp = opendir(iio_dir);
444 if (dp == NULL) {
445 printf("No industrialio devices available");
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100446 return -ENODEV;
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100447 }
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100448
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100449 while (ent = readdir(dp), ent != NULL) {
450 if (strcmp(ent->d_name, ".") != 0 &&
451 strcmp(ent->d_name, "..") != 0 &&
452 strlen(ent->d_name) > strlen(type) &&
453 strncmp(ent->d_name, type, strlen(type)) == 0) {
454 numstrlen = sscanf(ent->d_name + strlen(type),
455 "%d",
456 &number);
457 /* verify the next character is not a colon */
458 if (strncmp(ent->d_name + strlen(type) + numstrlen,
459 ":",
460 1) != 0) {
461 filename = malloc(strlen(iio_dir)
462 + strlen(type)
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100463 + numstrlen
Barry Songb6ee30a2010-05-25 17:40:04 +0800464 + 6);
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100465 if (filename == NULL)
466 return -ENOMEM;
467 sprintf(filename, "%s%s%d/name",
468 iio_dir,
469 type,
470 number);
471 nameFile = fopen(filename, "r");
472 if (!nameFile)
473 continue;
474 free(filename);
475 fscanf(nameFile, "%s", thisname);
476 if (strcmp(name, thisname) == 0)
477 return number;
478 fclose(nameFile);
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100479 }
480 }
481 }
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100482 return -ENODEV;
483}
484
485inline int _write_sysfs_int(char *filename, char *basedir, int val, int verify)
486{
487 int ret;
488 FILE *sysfsfp;
489 int test;
490 char *temp = malloc(strlen(basedir) + strlen(filename) + 2);
491 if (temp == NULL)
492 return -ENOMEM;
493 sprintf(temp, "%s/%s", basedir, filename);
494 sysfsfp = fopen(temp, "w");
495 if (sysfsfp == NULL) {
496 printf("failed to open %s\n", temp);
497 ret = -errno;
498 goto error_free;
499 }
500 fprintf(sysfsfp, "%d", val);
501 fclose(sysfsfp);
502 if (verify) {
503 sysfsfp = fopen(temp, "r");
504 if (sysfsfp == NULL) {
505 printf("failed to open %s\n", temp);
506 ret = -errno;
507 goto error_free;
508 }
509 fscanf(sysfsfp, "%d", &test);
510 if (test != val) {
511 printf("Possible failure in int write %d to %s%s\n",
512 val,
513 basedir,
514 filename);
515 ret = -1;
516 }
517 }
518error_free:
519 free(temp);
520 return ret;
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100521}
522
523int write_sysfs_int(char *filename, char *basedir, int val)
524{
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100525 return _write_sysfs_int(filename, basedir, val, 0);
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100526}
527
Jonathan Cameroneaf86ff2010-05-04 14:43:04 +0100528int write_sysfs_int_and_verify(char *filename, char *basedir, int val)
529{
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100530 return _write_sysfs_int(filename, basedir, val, 1);
Jonathan Cameroneaf86ff2010-05-04 14:43:04 +0100531}
532
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100533int _write_sysfs_string(char *filename, char *basedir, char *val, int verify)
534{
Jonathan Camerone58537c2010-10-08 12:14:14 +0100535 int ret = 0;
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100536 FILE *sysfsfp;
537 char *temp = malloc(strlen(basedir) + strlen(filename) + 2);
538 if (temp == NULL) {
539 printf("Memory allocation failed\n");
540 return -ENOMEM;
541 }
542 sprintf(temp, "%s/%s", basedir, filename);
543 sysfsfp = fopen(temp, "w");
544 if (sysfsfp == NULL) {
545 printf("Could not open %s\n", temp);
546 ret = -errno;
547 goto error_free;
548 }
549 fprintf(sysfsfp, "%s", val);
550 fclose(sysfsfp);
551 if (verify) {
552 sysfsfp = fopen(temp, "r");
553 if (sysfsfp == NULL) {
Jonathan Camerone58537c2010-10-08 12:14:14 +0100554 printf("could not open file to verify\n");
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100555 ret = -errno;
556 goto error_free;
557 }
558 fscanf(sysfsfp, "%s", temp);
559 if (strcmp(temp, val) != 0) {
560 printf("Possible failure in string write of %s "
561 "Should be %s "
Lucas De Marchi25985ed2011-03-30 22:57:33 -0300562 "written to %s\%s\n",
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100563 temp,
564 val,
565 basedir,
566 filename);
567 ret = -1;
568 }
569 }
570error_free:
571 free(temp);
572
573 return ret;
574}
Jonathan Camerone58537c2010-10-08 12:14:14 +0100575
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100576/**
577 * write_sysfs_string_and_verify() - string write, readback and verify
578 * @filename: name of file to write to
579 * @basedir: the sysfs directory in which the file is to be found
580 * @val: the string to write
581 **/
582int write_sysfs_string_and_verify(char *filename, char *basedir, char *val)
583{
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100584 return _write_sysfs_string(filename, basedir, val, 1);
585}
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100586
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100587int write_sysfs_string(char *filename, char *basedir, char *val)
588{
589 return _write_sysfs_string(filename, basedir, val, 0);
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100590}
591
592int read_sysfs_posint(char *filename, char *basedir)
593{
594 int ret;
595 FILE *sysfsfp;
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100596 char *temp = malloc(strlen(basedir) + strlen(filename) + 2);
597 if (temp == NULL) {
598 printf("Memory allocation failed");
599 return -ENOMEM;
600 }
601 sprintf(temp, "%s/%s", basedir, filename);
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100602 sysfsfp = fopen(temp, "r");
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100603 if (sysfsfp == NULL) {
604 ret = -errno;
605 goto error_free;
606 }
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100607 fscanf(sysfsfp, "%d\n", &ret);
608 fclose(sysfsfp);
Jonathan Cameron9d8ae6c2010-05-04 14:43:13 +0100609error_free:
610 free(temp);
611 return ret;
612}
613
614int read_sysfs_float(char *filename, char *basedir, float *val)
615{
616 float ret = 0;
617 FILE *sysfsfp;
618 char *temp = malloc(strlen(basedir) + strlen(filename) + 2);
619 if (temp == NULL) {
620 printf("Memory allocation failed");
621 return -ENOMEM;
622 }
623 sprintf(temp, "%s/%s", basedir, filename);
624 sysfsfp = fopen(temp, "r");
625 if (sysfsfp == NULL) {
626 ret = -errno;
627 goto error_free;
628 }
629 fscanf(sysfsfp, "%f\n", val);
630 fclose(sysfsfp);
631error_free:
632 free(temp);
Jonathan Cameronc57f1ba2009-08-18 18:06:32 +0100633 return ret;
634}