blob: 1a7097029ae706d4c9b8f049d3309da0315afe06 [file] [log] [blame]
Marek Behún61d74e82018-04-24 17:21:25 +02001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Marvell Armada 37xx SoC Peripheral clocks
4 *
Marek Behúnd63726e2022-06-01 17:17:06 +02005 * Marek Behún <kabel@kernel.org>
Marek Behún61d74e82018-04-24 17:21:25 +02006 *
7 * Based on Linux driver by:
8 * Gregory CLEMENT <gregory.clement@free-electrons.com>
9 */
10
11#include <common.h>
12#include <malloc.h>
13#include <clk-uclass.h>
14#include <clk.h>
15#include <dm.h>
16#include <asm/io.h>
17#include <asm/arch/cpu.h>
Simon Glass9bc15642020-02-03 07:36:16 -070018#include <dm/device_compat.h>
Simon Glass4dcacfc2020-05-10 11:40:13 -060019#include <linux/bitops.h>
Marek Behún61d74e82018-04-24 17:21:25 +020020
21#define TBG_SEL 0x0
22#define DIV_SEL0 0x4
23#define DIV_SEL1 0x8
24#define DIV_SEL2 0xC
25#define CLK_SEL 0x10
26#define CLK_DIS 0x14
27
28enum a37xx_periph_parent {
29 TBG_A_P = 0,
30 TBG_B_P = 1,
31 TBG_A_S = 2,
32 TBG_B_S = 3,
33 MAX_TBG_PARENTS = 4,
34 XTAL = 4,
35 MAX_PARENTS = 5,
36};
37
38static const struct {
39 const char *name;
40 enum a37xx_periph_parent parent;
41} a37xx_periph_parent_names[] = {
42 { "TBG-A-P", TBG_A_P },
43 { "TBG-B-P", TBG_B_P },
44 { "TBG-A-S", TBG_A_S },
45 { "TBG-B-S", TBG_B_S },
46 { "xtal", XTAL },
47};
48
49struct clk_periph;
50
51struct a37xx_periphclk {
52 void __iomem *reg;
53
54 ulong parents[MAX_PARENTS];
55
56 const struct clk_periph *clks;
57 bool clk_has_periph_parent[16];
58 int clk_parent[16];
59
60 int count;
61};
62
63struct clk_div_table {
64 u32 div;
65 u32 val;
66};
67
68struct clk_periph {
69 const char *name;
70
71 const char *parent_name;
72
73 u32 disable_bit;
74 int mux_shift;
75
76 const struct clk_div_table *div_table[2];
77 s32 div_reg_off[2];
78 u32 div_mask[2];
79 int div_shift[2];
80
81 unsigned can_gate : 1;
82 unsigned can_mux : 1;
83 unsigned dividers : 2;
84};
85
86static const struct clk_div_table div_table1[] = {
87 { 1, 1 },
88 { 2, 2 },
89 { 0, 0 },
90};
91
92static const struct clk_div_table div_table2[] = {
Marek Behún9c75dd12020-04-15 00:59:18 +020093 { 2, 0 },
94 { 4, 1 },
Marek Behún61d74e82018-04-24 17:21:25 +020095 { 0, 0 },
96};
97
98static const struct clk_div_table div_table6[] = {
99 { 1, 1 },
100 { 2, 2 },
101 { 3, 3 },
102 { 4, 4 },
103 { 5, 5 },
104 { 6, 6 },
105 { 0, 0 },
106};
107
108#define CLK_FULL_DD(_n, _d, _mux, _r0, _r1, _s0, _s1) \
109 { \
110 .name = #_n, \
111 .disable_bit = BIT(_d), \
112 .mux_shift = _mux, \
113 .div_table[0] = div_table6, \
114 .div_table[1] = div_table6, \
115 .div_reg_off[0] = _r0, \
116 .div_reg_off[1] = _r1, \
117 .div_shift[0] = _s0, \
118 .div_shift[1] = _s1, \
119 .div_mask[0] = 7, \
120 .div_mask[1] = 7, \
121 .can_gate = 1, \
122 .can_mux = 1, \
123 .dividers = 2, \
124 }
125
126#define CLK_FULL(_n, _d, _mux, _r, _s, _m, _t) \
127 { \
128 .name = #_n, \
129 .disable_bit = BIT(_d), \
130 .mux_shift = _mux, \
131 .div_table[0] = _t, \
132 .div_reg_off[0] = _r, \
133 .div_shift[0] = _s, \
134 .div_mask[0] = _m, \
135 .can_gate = 1, \
136 .can_mux = 1, \
137 .dividers = 1, \
138 }
139
140#define CLK_GATE_DIV(_n, _d, _r, _s, _m, _t, _p) \
141 { \
142 .name = #_n, \
143 .parent_name = _p, \
144 .disable_bit = BIT(_d), \
145 .div_table[0] = _t, \
146 .div_reg_off[0] = _r, \
147 .div_shift[0] = _s, \
148 .div_mask[0] = _m, \
149 .can_gate = 1, \
150 .dividers = 1, \
151 }
152
153#define CLK_GATE(_n, _d, _p) \
154 { \
155 .name = #_n, \
156 .parent_name = _p, \
157 .disable_bit = BIT(_d), \
158 .can_gate = 1, \
159 }
160
161#define CLK_MUX_DIV(_n, _mux, _r, _s, _m, _t) \
162 { \
163 .name = #_n, \
164 .mux_shift = _mux, \
165 .div_table[0] = _t, \
166 .div_reg_off[0] = _r, \
167 .div_shift[0] = _s, \
168 .div_mask[0] = _m, \
169 .can_mux = 1, \
170 .dividers = 1, \
171 }
172
173#define CLK_MUX_DD(_n, _mux, _r0, _r1, _s0, _s1) \
174 { \
175 .name = #_n, \
176 .mux_shift = _mux, \
177 .div_table[0] = div_table6, \
178 .div_table[1] = div_table6, \
179 .div_reg_off[0] = _r0, \
180 .div_reg_off[1] = _r1, \
181 .div_shift[0] = _s0, \
182 .div_shift[1] = _s1, \
183 .div_mask[0] = 7, \
184 .div_mask[1] = 7, \
185 .can_mux = 1, \
186 .dividers = 2, \
187 }
188
189/* NB periph clocks */
190static const struct clk_periph clks_nb[] = {
191 CLK_FULL_DD(mmc, 2, 0, DIV_SEL2, DIV_SEL2, 16, 13),
192 CLK_FULL_DD(sata_host, 3, 2, DIV_SEL2, DIV_SEL2, 10, 7),
193 CLK_FULL_DD(sec_at, 6, 4, DIV_SEL1, DIV_SEL1, 3, 0),
194 CLK_FULL_DD(sec_dap, 7, 6, DIV_SEL1, DIV_SEL1, 9, 6),
195 CLK_FULL_DD(tscem, 8, 8, DIV_SEL1, DIV_SEL1, 15, 12),
196 CLK_FULL(tscem_tmx, 10, 10, DIV_SEL1, 18, 7, div_table6),
197 CLK_GATE(avs, 11, "xtal"),
198 CLK_FULL_DD(sqf, 12, 12, DIV_SEL1, DIV_SEL1, 27, 24),
199 CLK_FULL_DD(pwm, 13, 14, DIV_SEL0, DIV_SEL0, 3, 0),
200 CLK_GATE(i2c_2, 16, "xtal"),
201 CLK_GATE(i2c_1, 17, "xtal"),
202 CLK_GATE_DIV(ddr_phy, 19, DIV_SEL0, 18, 1, div_table2, "TBG-A-S"),
203 CLK_FULL_DD(ddr_fclk, 21, 16, DIV_SEL0, DIV_SEL0, 15, 12),
204 CLK_FULL(trace, 22, 18, DIV_SEL0, 20, 7, div_table6),
205 CLK_FULL(counter, 23, 20, DIV_SEL0, 23, 7, div_table6),
206 CLK_FULL_DD(eip97, 24, 24, DIV_SEL2, DIV_SEL2, 22, 19),
207 CLK_MUX_DIV(cpu, 22, DIV_SEL0, 28, 7, div_table6),
208 { },
209};
210
211/* SB periph clocks */
212static const struct clk_periph clks_sb[] = {
213 CLK_MUX_DD(gbe_50, 6, DIV_SEL2, DIV_SEL2, 6, 9),
214 CLK_MUX_DD(gbe_core, 8, DIV_SEL1, DIV_SEL1, 18, 21),
215 CLK_MUX_DD(gbe_125, 10, DIV_SEL1, DIV_SEL1, 6, 9),
216 CLK_GATE(gbe1_50, 0, "gbe_50"),
217 CLK_GATE(gbe0_50, 1, "gbe_50"),
218 CLK_GATE(gbe1_125, 2, "gbe_125"),
219 CLK_GATE(gbe0_125, 3, "gbe_125"),
220 CLK_GATE_DIV(gbe1_core, 4, DIV_SEL1, 13, 1, div_table1, "gbe_core"),
221 CLK_GATE_DIV(gbe0_core, 5, DIV_SEL1, 14, 1, div_table1, "gbe_core"),
222 CLK_GATE_DIV(gbe_bm, 12, DIV_SEL1, 0, 1, div_table1, "gbe_core"),
223 CLK_FULL_DD(sdio, 11, 14, DIV_SEL0, DIV_SEL0, 3, 6),
224 CLK_FULL_DD(usb32_usb2_sys, 16, 16, DIV_SEL0, DIV_SEL0, 9, 12),
225 CLK_FULL_DD(usb32_ss_sys, 17, 18, DIV_SEL0, DIV_SEL0, 15, 18),
226 { },
227};
228
Marek Behún7bafd042018-08-17 12:58:52 +0200229static int get_mux(struct a37xx_periphclk *priv, int shift)
Marek Behún61d74e82018-04-24 17:21:25 +0200230{
231 return (readl(priv->reg + TBG_SEL) >> shift) & 3;
232}
233
Marek Behún7bafd042018-08-17 12:58:52 +0200234static void set_mux(struct a37xx_periphclk *priv, int shift, int val)
235{
236 u32 reg;
237
238 reg = readl(priv->reg + TBG_SEL);
239 reg &= ~(3 << shift);
240 reg |= (val & 3) << shift;
241 writel(reg, priv->reg + TBG_SEL);
242}
243
Marek Behún61d74e82018-04-24 17:21:25 +0200244static ulong periph_clk_get_rate(struct a37xx_periphclk *priv, int id);
245
246static ulong get_parent_rate(struct a37xx_periphclk *priv, int id)
247{
248 const struct clk_periph *clk = &priv->clks[id];
249 ulong res;
250
251 if (clk->can_mux) {
252 /* parent is one of TBG clocks */
253 int tbg = get_mux(priv, clk->mux_shift);
254
255 res = priv->parents[tbg];
256 } else if (priv->clk_has_periph_parent[id]) {
257 /* parent is one of other periph clocks */
258
259 if (priv->clk_parent[id] >= priv->count)
260 return -EINVAL;
261
262 res = periph_clk_get_rate(priv, priv->clk_parent[id]);
263 } else {
264 /* otherwise parent is one of TBGs or XTAL */
265
266 if (priv->clk_parent[id] >= MAX_PARENTS)
267 return -EINVAL;
268
269 res = priv->parents[priv->clk_parent[id]];
270 }
271
272 return res;
273}
274
275static ulong get_div(struct a37xx_periphclk *priv,
276 const struct clk_periph *clk, int idx)
277{
278 const struct clk_div_table *i;
279 u32 reg;
280
281 reg = readl(priv->reg + clk->div_reg_off[idx]);
282 reg = (reg >> clk->div_shift[idx]) & clk->div_mask[idx];
283
284 /* find divisor for register value val */
285 for (i = clk->div_table[idx]; i && i->div != 0; ++i)
286 if (i->val == reg)
287 return i->div;
288
289 return 0;
290}
291
Marek Behún7bafd042018-08-17 12:58:52 +0200292static void set_div_val(struct a37xx_periphclk *priv,
293 const struct clk_periph *clk, int idx, int val)
294{
295 u32 reg;
296
297 reg = readl(priv->reg + clk->div_reg_off[idx]);
298 reg &= ~(clk->div_mask[idx] << clk->div_shift[idx]);
299 reg |= (val & clk->div_mask[idx]) << clk->div_shift[idx];
300 writel(reg, priv->reg + clk->div_reg_off[idx]);
301}
302
Marek Behún61d74e82018-04-24 17:21:25 +0200303static ulong periph_clk_get_rate(struct a37xx_periphclk *priv, int id)
304{
305 const struct clk_periph *clk = &priv->clks[id];
306 ulong rate, div;
307 int i;
308
309 rate = get_parent_rate(priv, id);
310 if (rate == -EINVAL)
311 return -EINVAL;
312
313 /* divide the parent rate by dividers */
314 div = 1;
315 for (i = 0; i < clk->dividers; ++i)
316 div *= get_div(priv, clk, i);
317
318 if (!div)
319 return 0;
320
321 return DIV_ROUND_UP(rate, div);
322}
323
324static ulong armada_37xx_periph_clk_get_rate(struct clk *clk)
325{
326 struct a37xx_periphclk *priv = dev_get_priv(clk->dev);
327
328 if (clk->id >= priv->count)
329 return -EINVAL;
330
331 return periph_clk_get_rate(priv, clk->id);
332}
333
334static int periph_clk_enable(struct clk *clk, int enable)
335{
336 struct a37xx_periphclk *priv = dev_get_priv(clk->dev);
337 const struct clk_periph *periph_clk = &priv->clks[clk->id];
338
339 if (clk->id >= priv->count)
340 return -EINVAL;
341
342 if (!periph_clk->can_gate)
Simon Glass29ff16a2021-03-25 10:26:08 +1300343 return -EINVAL;
Marek Behún61d74e82018-04-24 17:21:25 +0200344
345 if (enable)
346 clrbits_le32(priv->reg + CLK_DIS, periph_clk->disable_bit);
347 else
348 setbits_le32(priv->reg + CLK_DIS, periph_clk->disable_bit);
349
350 return 0;
351}
352
353static int armada_37xx_periph_clk_enable(struct clk *clk)
354{
355 return periph_clk_enable(clk, 1);
356}
357
358static int armada_37xx_periph_clk_disable(struct clk *clk)
359{
360 return periph_clk_enable(clk, 0);
361}
362
Marek Behún7bafd042018-08-17 12:58:52 +0200363#define diff(a, b) abs((long)(a) - (long)(b))
364
365static ulong find_best_div(const struct clk_div_table *t0,
366 const struct clk_div_table *t1, ulong parent_rate,
367 ulong req_rate, int *v0, int *v1)
368{
369 const struct clk_div_table *i, *j;
370 ulong rate, best_rate = 0;
371
372 for (i = t0; i && i->div; ++i) {
373 for (j = t1; j && j->div; ++j) {
374 rate = DIV_ROUND_UP(parent_rate, i->div * j->div);
375
376 if (!best_rate ||
377 diff(rate, req_rate) < diff(best_rate, req_rate)) {
378 best_rate = rate;
379 *v0 = i->val;
380 *v1 = j->val;
381 }
382 }
383 }
384
385 return best_rate;
386}
387
388static ulong armada_37xx_periph_clk_set_rate(struct clk *clk, ulong req_rate)
389{
390 struct a37xx_periphclk *priv = dev_get_priv(clk->dev);
391 const struct clk_periph *periph_clk = &priv->clks[clk->id];
392 ulong rate, old_rate, parent_rate;
393 int div_val0 = 0, div_val1 = 0;
394 const struct clk_div_table *t1;
395 static const struct clk_div_table empty_table[2] = {
396 { 1, 0 },
397 { 0, 0 }
398 };
399
400 if (clk->id > priv->count)
401 return -EINVAL;
402
403 old_rate = periph_clk_get_rate(priv, clk->id);
404 if (old_rate == -EINVAL)
405 return -EINVAL;
406
407 if (old_rate == req_rate)
408 return old_rate;
409
410 if (!periph_clk->can_gate || !periph_clk->dividers)
Simon Glass29ff16a2021-03-25 10:26:08 +1300411 return -EINVAL;
Marek Behún7bafd042018-08-17 12:58:52 +0200412
413 parent_rate = get_parent_rate(priv, clk->id);
414 if (parent_rate == -EINVAL)
415 return -EINVAL;
416
417 t1 = empty_table;
418 if (periph_clk->dividers > 1)
419 t1 = periph_clk->div_table[1];
420
421 rate = find_best_div(periph_clk->div_table[0], t1, parent_rate,
422 req_rate, &div_val0, &div_val1);
423
424 periph_clk_enable(clk, 0);
425
426 set_div_val(priv, periph_clk, 0, div_val0);
427 if (periph_clk->dividers > 1)
428 set_div_val(priv, periph_clk, 1, div_val1);
429
430 periph_clk_enable(clk, 1);
431
432 return rate;
433}
434
435static int armada_37xx_periph_clk_set_parent(struct clk *clk,
436 struct clk *parent)
437{
438 struct a37xx_periphclk *priv = dev_get_priv(clk->dev);
439 const struct clk_periph *periph_clk = &priv->clks[clk->id];
440 struct clk check_parent;
441 int ret;
442
443 /* We also check if parent is our TBG clock */
444 if (clk->id > priv->count || parent->id >= MAX_TBG_PARENTS)
445 return -EINVAL;
446
447 if (!periph_clk->can_mux || !periph_clk->can_gate)
Simon Glass29ff16a2021-03-25 10:26:08 +1300448 return -EINVAL;
Marek Behún7bafd042018-08-17 12:58:52 +0200449
450 ret = clk_get_by_index(clk->dev, 0, &check_parent);
451 if (ret < 0)
452 return ret;
453
454 if (parent->dev != check_parent.dev)
455 ret = -EINVAL;
456
457 clk_free(&check_parent);
458 if (ret < 0)
459 return ret;
460
461 periph_clk_enable(clk, 0);
462 set_mux(priv, periph_clk->mux_shift, parent->id);
463 periph_clk_enable(clk, 1);
464
465 return 0;
466}
467
Marek Behúnd8e69422018-04-24 17:21:27 +0200468#if defined(CONFIG_CMD_CLK) && defined(CONFIG_CLK_ARMADA_3720)
469static int armada_37xx_periph_clk_dump(struct udevice *dev)
Marek Behún61d74e82018-04-24 17:21:25 +0200470{
471 struct a37xx_periphclk *priv = dev_get_priv(dev);
472 const struct clk_periph *clks;
473 int i;
474
475 if (!priv)
476 return -ENODEV;
477
478 clks = priv->clks;
479
480 for (i = 0; i < priv->count; ++i)
481 printf(" %s at %lu Hz\n", clks[i].name,
482 periph_clk_get_rate(priv, i));
483 printf("\n");
484
485 return 0;
486}
487
Marek Behúnd8e69422018-04-24 17:21:27 +0200488static int clk_dump(const char *name, int (*func)(struct udevice *))
489{
490 struct udevice *dev;
Igor Prusov1a3427b2023-11-09 13:55:15 +0300491 int ret;
Marek Behúnd8e69422018-04-24 17:21:27 +0200492
493 if (uclass_get_device_by_name(UCLASS_CLK, name, &dev)) {
494 printf("Cannot find device %s\n", name);
495 return -ENODEV;
496 }
497
Igor Prusov1a3427b2023-11-09 13:55:15 +0300498 ret = func(dev);
499 if (ret)
500 printf("Dump failed for %s: %d\n", name, ret);
501
502 return ret;
Marek Behúnd8e69422018-04-24 17:21:27 +0200503}
504
505int armada_37xx_tbg_clk_dump(struct udevice *);
506
Igor Prusov1a3427b2023-11-09 13:55:15 +0300507static void armada37xx_clk_dump(struct udevice __always_unused *dev)
Marek Behúnd8e69422018-04-24 17:21:27 +0200508{
509 printf(" xtal at %u000000 Hz\n\n", get_ref_clk());
510
511 if (clk_dump("tbg@13200", armada_37xx_tbg_clk_dump))
Igor Prusov1a3427b2023-11-09 13:55:15 +0300512 return;
Marek Behúnd8e69422018-04-24 17:21:27 +0200513
514 if (clk_dump("nb-periph-clk@13000",
515 armada_37xx_periph_clk_dump))
Igor Prusov1a3427b2023-11-09 13:55:15 +0300516 return;
Marek Behúnd8e69422018-04-24 17:21:27 +0200517
518 if (clk_dump("sb-periph-clk@18000",
519 armada_37xx_periph_clk_dump))
Igor Prusov1a3427b2023-11-09 13:55:15 +0300520 return;
Marek Behúnd8e69422018-04-24 17:21:27 +0200521}
522#endif
523
Marek Behún61d74e82018-04-24 17:21:25 +0200524static int armada_37xx_periph_clk_probe(struct udevice *dev)
525{
526 struct a37xx_periphclk *priv = dev_get_priv(dev);
527 const struct clk_periph *clks;
528 int ret, i;
529
530 clks = (const struct clk_periph *)dev_get_driver_data(dev);
531 if (!clks)
532 return -ENODEV;
533
534 priv->reg = dev_read_addr_ptr(dev);
535 if (!priv->reg) {
536 dev_err(dev, "no io address\n");
537 return -ENODEV;
538 }
539
540 /* count clk_periph nodes */
541 priv->count = 0;
542 while (clks[priv->count].name)
543 priv->count++;
544
545 priv->clks = clks;
546
547 /* assign parent IDs to nodes which have non-NULL parent_name */
548 for (i = 0; i < priv->count; ++i) {
549 int j;
550
551 if (!clks[i].parent_name)
552 continue;
553
554 /* first try if parent_name is one of TBGs or XTAL */
555 for (j = 0; j < MAX_PARENTS; ++j)
556 if (!strcmp(clks[i].parent_name,
557 a37xx_periph_parent_names[j].name))
558 break;
559
560 if (j < MAX_PARENTS) {
561 priv->clk_has_periph_parent[i] = false;
562 priv->clk_parent[i] =
563 a37xx_periph_parent_names[j].parent;
564 continue;
565 }
566
567 /* else parent_name should be one of other periph clocks */
568 for (j = 0; j < priv->count; ++j) {
569 if (!strcmp(clks[i].parent_name, clks[j].name))
570 break;
571 }
572
573 if (j < priv->count) {
574 priv->clk_has_periph_parent[i] = true;
575 priv->clk_parent[i] = j;
576 continue;
577 }
578
579 dev_err(dev, "undefined parent %s\n", clks[i].parent_name);
580 return -EINVAL;
581 }
582
583 for (i = 0; i < MAX_PARENTS; ++i) {
584 struct clk clk;
585
586 if (i == XTAL) {
587 priv->parents[i] = get_ref_clk() * 1000000;
588 continue;
589 }
590
591 ret = clk_get_by_index(dev, i, &clk);
592 if (ret) {
593 dev_err(dev, "one of parent clocks (%i) missing: %i\n",
594 i, ret);
595 return -ENODEV;
596 }
597
598 priv->parents[i] = clk_get_rate(&clk);
599 clk_free(&clk);
600 }
601
602 return 0;
603}
604
605static const struct clk_ops armada_37xx_periph_clk_ops = {
606 .get_rate = armada_37xx_periph_clk_get_rate,
Marek Behún7bafd042018-08-17 12:58:52 +0200607 .set_rate = armada_37xx_periph_clk_set_rate,
608 .set_parent = armada_37xx_periph_clk_set_parent,
Marek Behún61d74e82018-04-24 17:21:25 +0200609 .enable = armada_37xx_periph_clk_enable,
610 .disable = armada_37xx_periph_clk_disable,
Igor Prusov1a3427b2023-11-09 13:55:15 +0300611#if IS_ENABLED(CONFIG_CMD_CLK)
612 .dump = armada37xx_clk_dump,
613#endif
Marek Behún61d74e82018-04-24 17:21:25 +0200614};
615
616static const struct udevice_id armada_37xx_periph_clk_ids[] = {
617 {
618 .compatible = "marvell,armada-3700-periph-clock-nb",
619 .data = (ulong)clks_nb,
620 },
621 {
622 .compatible = "marvell,armada-3700-periph-clock-sb",
623 .data = (ulong)clks_sb,
624 },
625 {}
626};
627
628U_BOOT_DRIVER(armada_37xx_periph_clk) = {
629 .name = "armada_37xx_periph_clk",
630 .id = UCLASS_CLK,
631 .of_match = armada_37xx_periph_clk_ids,
632 .ops = &armada_37xx_periph_clk_ops,
Simon Glass8a2b47f2020-12-03 16:55:17 -0700633 .priv_auto = sizeof(struct a37xx_periphclk),
Marek Behún61d74e82018-04-24 17:21:25 +0200634 .probe = armada_37xx_periph_clk_probe,
Marek Behún44811682021-05-25 19:42:39 +0200635 .flags = DM_FLAG_PRE_RELOC,
Marek Behún61d74e82018-04-24 17:21:25 +0200636};