1 /*
2  * Copyright (c) 2023 Cypress Semiconductor Corporation (an Infineon company) or
3  * an affiliate of Cypress Semiconductor Corporation
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 /**
9  * @brief Counter driver for Infineon CAT1 MCU family.
10  */
11 
12 #define DT_DRV_COMPAT infineon_cat1_counter
13 
14 #include <zephyr/drivers/counter.h>
15 #include <zephyr/drivers/pinctrl.h>
16 #include <cyhal_timer.h>
17 #include <cyhal_gpio.h>
18 
19 #include <cyhal_tcpwm_common.h>
20 #include <zephyr/dt-bindings/pinctrl/ifx_cat1-pinctrl.h>
21 
22 #include <zephyr/logging/log.h>
23 LOG_MODULE_REGISTER(ifx_cat1_counter, CONFIG_COUNTER_LOG_LEVEL);
24 
25 struct ifx_cat1_counter_config {
26 	struct counter_config_info counter_info;
27 	TCPWM_CNT_Type *reg_addr;
28 	cyhal_gpio_t external_pin;
29 	IRQn_Type irqn;
30 	uint8_t irq_priority;
31 };
32 
33 struct ifx_cat1_counter_data {
34 	cyhal_timer_t counter_obj;
35 	cyhal_timer_cfg_t counter_cfg;
36 	struct counter_alarm_cfg alarm_cfg_counter;
37 	struct counter_top_cfg top_value_cfg_counter;
38 	uint32_t guard_period;
39 	cyhal_resource_inst_t hw_resource;
40 	cyhal_source_t signal_source;
41 	bool alarm_irq_flag;
42 };
43 
44 static const cy_stc_tcpwm_counter_config_t cyhal_timer_default_config = {
45 	.period = 32768,
46 	.clockPrescaler = CY_TCPWM_COUNTER_PRESCALER_DIVBY_1,
47 	.runMode = CY_TCPWM_COUNTER_CONTINUOUS,
48 	.countDirection = CY_TCPWM_COUNTER_COUNT_UP,
49 	.compareOrCapture = CY_TCPWM_COUNTER_MODE_CAPTURE,
50 	.compare0 = 16384,
51 	.compare1 = 16384,
52 	.enableCompareSwap = false,
53 	.interruptSources = CY_TCPWM_INT_NONE,
54 	.captureInputMode = 0x3U,
55 	.captureInput = CY_TCPWM_INPUT_0,
56 	.reloadInputMode = 0x3U,
57 	.reloadInput = CY_TCPWM_INPUT_0,
58 	.startInputMode = 0x3U,
59 	.startInput = CY_TCPWM_INPUT_0,
60 	.stopInputMode = 0x3U,
61 	.stopInput = CY_TCPWM_INPUT_0,
62 	.countInputMode = 0x3U,
63 	.countInput = CY_TCPWM_INPUT_1,
64 };
65 
get_hw_block_info(TCPWM_CNT_Type * reg_addr,cyhal_resource_inst_t * hw_resource)66 static int get_hw_block_info(TCPWM_CNT_Type *reg_addr, cyhal_resource_inst_t *hw_resource)
67 {
68 	uint32_t i;
69 
70 	for (i = 0u; i < _CYHAL_TCPWM_INSTANCES; i++) {
71 		uintptr_t base = POINTER_TO_UINT(_CYHAL_TCPWM_DATA[i].base);
72 		uintptr_t cnt = POINTER_TO_UINT(_CYHAL_TCPWM_DATA[i].base->CNT);
73 		uintptr_t reg_addr_ptr = POINTER_TO_UINT(reg_addr);
74 		uintptr_t end_addr = base + sizeof(TCPWM_Type);
75 
76 		if ((reg_addr_ptr > base) && (reg_addr_ptr < end_addr)) {
77 
78 			hw_resource->type = CYHAL_RSC_TCPWM;
79 			hw_resource->block_num = i;
80 			hw_resource->channel_num = ((reg_addr_ptr - cnt) / sizeof(TCPWM_CNT_Type));
81 
82 			if (hw_resource->channel_num >= _CYHAL_TCPWM_DATA[i].num_channels) {
83 				return -EINVAL;
84 			}
85 			return 0;
86 		}
87 	}
88 	return -EINVAL;
89 }
90 
ifx_cat1_counter_event_callback(void * callback_arg,cyhal_timer_event_t event)91 static void ifx_cat1_counter_event_callback(void *callback_arg, cyhal_timer_event_t event)
92 {
93 	const struct device *dev = (const struct device *)callback_arg;
94 	struct ifx_cat1_counter_data *const data = dev->data;
95 	const struct ifx_cat1_counter_config *const config = dev->config;
96 
97 	/* Alarm compare/capture event */
98 	if ((data->alarm_cfg_counter.callback != NULL) &&
99 	    (((CYHAL_TIMER_IRQ_CAPTURE_COMPARE & event) == CYHAL_TIMER_IRQ_CAPTURE_COMPARE) ||
100 	     data->alarm_irq_flag)) {
101 		/* Alarm works as one-shot, so disable event */
102 		cyhal_timer_enable_event(&data->counter_obj, CYHAL_TIMER_IRQ_CAPTURE_COMPARE,
103 					 config->irq_priority, false);
104 
105 		/* Call User callback for Alarm */
106 		data->alarm_cfg_counter.callback(dev, 1, cyhal_timer_read(&data->counter_obj),
107 						 data->alarm_cfg_counter.user_data);
108 		data->alarm_irq_flag = false;
109 	}
110 
111 	/* Top_value terminal count event */
112 	if ((data->top_value_cfg_counter.callback != NULL) &&
113 	    ((CYHAL_TIMER_IRQ_TERMINAL_COUNT & event) == CYHAL_TIMER_IRQ_TERMINAL_COUNT)) {
114 
115 		/* Call User callback for top value */
116 		data->top_value_cfg_counter.callback(dev, data->top_value_cfg_counter.user_data);
117 	}
118 
119 	/* NOTE: cyhal handles cleaning of interrupts */
120 }
121 
ifx_cat1_counter_set_int_pending(const struct device * dev)122 static void ifx_cat1_counter_set_int_pending(const struct device *dev)
123 {
124 	__ASSERT_NO_MSG(dev != NULL);
125 
126 	struct ifx_cat1_counter_data *const data = dev->data;
127 	const struct ifx_cat1_counter_config *const config = dev->config;
128 
129 	cyhal_timer_enable_event(&data->counter_obj, CYHAL_TIMER_IRQ_CAPTURE_COMPARE,
130 				 config->irq_priority, true);
131 	Cy_TCPWM_SetInterrupt(data->counter_obj.tcpwm.base,
132 			      _CYHAL_TCPWM_CNT_NUMBER(data->counter_obj.tcpwm.resource),
133 			      CY_TCPWM_INT_ON_CC0);
134 }
135 
ifx_cat1_counter_init(const struct device * dev)136 static int ifx_cat1_counter_init(const struct device *dev)
137 {
138 	__ASSERT_NO_MSG(dev != NULL);
139 
140 	cy_rslt_t rslt;
141 	struct ifx_cat1_counter_data *data = dev->data;
142 	const struct ifx_cat1_counter_config *config = dev->config;
143 
144 	/* Dedicate Counter HW resource */
145 	if (get_hw_block_info(config->reg_addr, &data->hw_resource) != 0) {
146 		return -EIO;
147 	}
148 
149 	cyhal_timer_configurator_t timer_configurator = {
150 		.resource = &data->hw_resource,
151 		.config = &cyhal_timer_default_config,
152 	};
153 
154 	/* Initialize timer */
155 	rslt = cyhal_timer_init_cfg(&data->counter_obj, &timer_configurator);
156 	if (rslt != CY_RSLT_SUCCESS) {
157 		return -EIO;
158 	}
159 
160 	/* Initialize counter structure */
161 	data->alarm_irq_flag = false;
162 	data->counter_cfg.compare_value = 0;
163 	data->counter_cfg.period = config->counter_info.max_top_value;
164 	data->counter_cfg.direction = CYHAL_TIMER_DIR_UP;
165 	data->counter_cfg.is_compare = true;
166 	data->counter_cfg.is_continuous = true;
167 	data->counter_cfg.value = 0;
168 
169 	/* Configure timer */
170 	rslt = cyhal_timer_configure(&data->counter_obj, &data->counter_cfg);
171 	if (rslt != CY_RSLT_SUCCESS) {
172 		return -EIO;
173 	}
174 
175 	if (config->external_pin == NC) {
176 		/* Configure frequency */
177 		rslt = cyhal_timer_set_frequency(&data->counter_obj, config->counter_info.freq);
178 		if (rslt != CY_RSLT_SUCCESS) {
179 			return -EIO;
180 		}
181 	} else {
182 		rslt = cyhal_gpio_init(config->external_pin, CYHAL_GPIO_DIR_INPUT,
183 				       CYHAL_GPIO_DRIVE_NONE, 0);
184 		if (rslt != CY_RSLT_SUCCESS) {
185 			LOG_ERR("External pin configuration error");
186 			return -EIO;
187 		}
188 
189 		rslt = cyhal_gpio_enable_output(config->external_pin, CYHAL_SIGNAL_TYPE_EDGE,
190 						(cyhal_source_t *)&data->signal_source);
191 		if (rslt != CY_RSLT_SUCCESS) {
192 			if (rslt != CY_RSLT_SUCCESS) {
193 				LOG_ERR("error in the enabling of Counter input pin output");
194 				return -EIO;
195 			}
196 		}
197 
198 		rslt = cyhal_timer_connect_digital(&data->counter_obj, data->signal_source,
199 						   CYHAL_TIMER_INPUT_COUNT);
200 		if (rslt != CY_RSLT_SUCCESS) {
201 			LOG_ERR("Error connecting signal source");
202 			return -EIO;
203 		}
204 	}
205 
206 	/* Register timer event callback */
207 	cyhal_timer_register_callback(&data->counter_obj, ifx_cat1_counter_event_callback,
208 				      (void *)dev);
209 
210 	return 0;
211 }
212 
ifx_cat1_counter_start(const struct device * dev)213 static int ifx_cat1_counter_start(const struct device *dev)
214 {
215 	__ASSERT_NO_MSG(dev != NULL);
216 
217 	struct ifx_cat1_counter_data *const data = dev->data;
218 
219 	if (cyhal_timer_start(&data->counter_obj) != CY_RSLT_SUCCESS) {
220 		return -EIO;
221 	}
222 	return 0;
223 }
224 
ifx_cat1_counter_stop(const struct device * dev)225 static int ifx_cat1_counter_stop(const struct device *dev)
226 {
227 	__ASSERT_NO_MSG(dev != NULL);
228 
229 	struct ifx_cat1_counter_data *const data = dev->data;
230 
231 	if (cyhal_timer_stop(&data->counter_obj) != CY_RSLT_SUCCESS) {
232 		return -EIO;
233 	}
234 	return 0;
235 }
236 
ifx_cat1_counter_get_value(const struct device * dev,uint32_t * ticks)237 static int ifx_cat1_counter_get_value(const struct device *dev, uint32_t *ticks)
238 {
239 	__ASSERT_NO_MSG(dev != NULL);
240 	__ASSERT_NO_MSG(ticks != NULL);
241 
242 	struct ifx_cat1_counter_data *const data = dev->data;
243 
244 	*ticks = cyhal_timer_read(&data->counter_obj);
245 
246 	return 0;
247 }
248 
ifx_cat1_counter_set_top_value(const struct device * dev,const struct counter_top_cfg * cfg)249 static int ifx_cat1_counter_set_top_value(const struct device *dev,
250 					  const struct counter_top_cfg *cfg)
251 {
252 	__ASSERT_NO_MSG(dev != NULL);
253 	__ASSERT_NO_MSG(cfg != NULL);
254 
255 	cy_rslt_t rslt;
256 	struct ifx_cat1_counter_data *const data = dev->data;
257 	const struct ifx_cat1_counter_config *const config = dev->config;
258 	bool ticks_gt_period;
259 
260 	data->top_value_cfg_counter = *cfg;
261 	data->counter_cfg.period = cfg->ticks;
262 
263 	/* Check new top value limit */
264 	if (cfg->ticks > config->counter_info.max_top_value) {
265 		return -ENOTSUP;
266 	}
267 
268 	ticks_gt_period = cfg->ticks > data->counter_cfg.period;
269 	/* Checks if new period value is not less then old period value */
270 	if (!(cfg->flags & COUNTER_TOP_CFG_DONT_RESET)) {
271 		data->counter_cfg.value = 0u;
272 	} else if (ticks_gt_period && (cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE)) {
273 		data->counter_cfg.value = 0u;
274 	} else {
275 		/* cyhal_timer_configure resets timer counter register to value
276 		 * defined in config structure 'counter_cfg.value', so update
277 		 * counter value with current value of counter (read by
278 		 * cyhal_timer_read function).
279 		 */
280 		data->counter_cfg.value = cyhal_timer_read(&data->counter_obj);
281 	}
282 
283 	if ((ticks_gt_period == false) ||
284 	    ((ticks_gt_period == true) && (cfg->flags & COUNTER_TOP_CFG_RESET_WHEN_LATE))) {
285 
286 		/* Reconfigure timer */
287 		if (config->external_pin == NC) {
288 			rslt = cyhal_timer_configure(&data->counter_obj, &data->counter_cfg);
289 			if (rslt != CY_RSLT_SUCCESS) {
290 				return -EIO;
291 			}
292 		} else {
293 			TCPWM_CNT_PERIOD(data->counter_obj.tcpwm.base,
294 					 _CYHAL_TCPWM_CNT_NUMBER(
295 						 data->counter_obj.tcpwm.resource)) = cfg->ticks;
296 		}
297 
298 		/* Register an top_value terminal count event callback handler if
299 		 * callback is not NULL.
300 		 */
301 		if (cfg->callback != NULL) {
302 			cyhal_timer_enable_event(&data->counter_obj, CYHAL_TIMER_IRQ_TERMINAL_COUNT,
303 						 config->irq_priority, true);
304 		}
305 	}
306 	return 0;
307 }
308 
ifx_cat1_counter_get_top_value(const struct device * dev)309 static uint32_t ifx_cat1_counter_get_top_value(const struct device *dev)
310 {
311 	__ASSERT_NO_MSG(dev != NULL);
312 
313 	struct ifx_cat1_counter_data *const data = dev->data;
314 
315 	return data->counter_cfg.period;
316 }
317 
counter_is_bit_mask(uint32_t val)318 static inline bool counter_is_bit_mask(uint32_t val)
319 {
320 	/* Return true if value equals 2^n - 1 */
321 	return !(val & (val + 1U));
322 }
323 
ifx_cat1_counter_ticks_add(uint32_t val1,uint32_t val2,uint32_t top)324 static uint32_t ifx_cat1_counter_ticks_add(uint32_t val1, uint32_t val2, uint32_t top)
325 {
326 	uint32_t to_top;
327 
328 	/* refer to https://tbrindus.ca/how-builtin-expect-works/ for 'likely' usage */
329 	if (likely(counter_is_bit_mask(top))) {
330 		return (val1 + val2) & top;
331 	}
332 
333 	to_top = top - val1;
334 
335 	return (val2 <= to_top) ? (val1 + val2) : (val2 - to_top - 1U);
336 }
337 
ifx_cat1_counter_ticks_sub(uint32_t val,uint32_t old,uint32_t top)338 static uint32_t ifx_cat1_counter_ticks_sub(uint32_t val, uint32_t old, uint32_t top)
339 {
340 	/* refer to https://tbrindus.ca/how-builtin-expect-works/ for 'likely' usage */
341 	if (likely(counter_is_bit_mask(top))) {
342 		return (val - old) & top;
343 	}
344 
345 	/* if top is not 2^n-1 */
346 	return (val >= old) ? (val - old) : (val + top + 1U - old);
347 }
348 
ifx_cat1_counter_set_alarm(const struct device * dev,uint8_t chan_id,const struct counter_alarm_cfg * alarm_cfg)349 static int ifx_cat1_counter_set_alarm(const struct device *dev, uint8_t chan_id,
350 				      const struct counter_alarm_cfg *alarm_cfg)
351 {
352 	ARG_UNUSED(chan_id);
353 	__ASSERT_NO_MSG(dev != NULL);
354 	__ASSERT_NO_MSG(alarm_cfg != NULL);
355 
356 	struct ifx_cat1_counter_data *const data = dev->data;
357 	const struct ifx_cat1_counter_config *const config = dev->config;
358 
359 	uint32_t val = alarm_cfg->ticks;
360 	uint32_t top_val = ifx_cat1_counter_get_top_value(dev);
361 	uint32_t flags = alarm_cfg->flags;
362 	uint32_t max_rel_val;
363 	bool absolute = ((flags & COUNTER_ALARM_CFG_ABSOLUTE) == 0) ? false : true;
364 	bool irq_on_late;
365 
366 	/* Checks if compare value is not less then period value */
367 	if (alarm_cfg->ticks > top_val) {
368 		return -EINVAL;
369 	}
370 
371 	if (absolute) {
372 		max_rel_val = top_val - data->guard_period;
373 		irq_on_late = ((flags & COUNTER_ALARM_CFG_EXPIRE_WHEN_LATE) == 0) ? false : true;
374 	} else {
375 		/* If relative value is smaller than half of the counter range it is assumed
376 		 * that there is a risk of setting value too late and late detection algorithm
377 		 * must be applied. When late setting is detected, interrupt shall be
378 		 * triggered for immediate expiration of the timer. Detection is performed
379 		 * by limiting relative distance between CC and counter.
380 		 *
381 		 * Note that half of counter range is an arbitrary value.
382 		 */
383 		irq_on_late = val < (top_val / 2U);
384 
385 		/* limit max to detect short relative being set too late. */
386 		max_rel_val = irq_on_late ? (top_val / 2U) : top_val;
387 		val = ifx_cat1_counter_ticks_add(cyhal_timer_read(&data->counter_obj), val,
388 						 top_val);
389 	}
390 
391 	/* Decrement value to detect also case when val == counter_read(dev). Otherwise,
392 	 * condition would need to include comparing diff against 0.
393 	 */
394 	uint32_t curr = cyhal_timer_read(&data->counter_obj);
395 	uint32_t diff = ifx_cat1_counter_ticks_sub((val - 1), curr, top_val);
396 
397 	if ((absolute && (val < curr)) || (diff > max_rel_val)) {
398 
399 		/* Interrupt is triggered always for relative alarm and for absolute depending
400 		 * on the flag.
401 		 */
402 		if (irq_on_late) {
403 			data->alarm_irq_flag = true;
404 			ifx_cat1_counter_set_int_pending(dev);
405 		}
406 
407 		if (absolute) {
408 			return -ETIME;
409 		}
410 	} else {
411 		/* Setting new compare value */
412 		cy_rslt_t rslt;
413 
414 		data->alarm_cfg_counter = *alarm_cfg;
415 		data->counter_cfg.compare_value = val;
416 
417 		/* cyhal_timer_configure resets timer counter register to value
418 		 * defined in config structure 'counter_cfg.value', so update
419 		 * counter value with current value of counter (read by
420 		 * cyhal_timer_read function).
421 		 */
422 		data->counter_cfg.value = cyhal_timer_read(&data->counter_obj);
423 
424 		/* Reconfigure timer */
425 		if (config->external_pin == NC) {
426 			rslt = cyhal_timer_configure(&data->counter_obj, &data->counter_cfg);
427 			if (rslt != CY_RSLT_SUCCESS) {
428 				return -EINVAL;
429 			}
430 		} else {
431 			TCPWM_CNT_CC(data->counter_obj.tcpwm.base,
432 				     _CYHAL_TCPWM_CNT_NUMBER(data->counter_obj.tcpwm.resource)) =
433 				data->counter_cfg.compare_value;
434 		}
435 
436 		cyhal_timer_enable_event(&data->counter_obj, CYHAL_TIMER_IRQ_CAPTURE_COMPARE,
437 					 config->irq_priority, true);
438 	}
439 
440 	return 0;
441 }
442 
ifx_cat1_counter_cancel_alarm(const struct device * dev,uint8_t chan_id)443 static int ifx_cat1_counter_cancel_alarm(const struct device *dev, uint8_t chan_id)
444 {
445 	ARG_UNUSED(chan_id);
446 	__ASSERT_NO_MSG(dev != NULL);
447 
448 	struct ifx_cat1_counter_data *const data = dev->data;
449 	const struct ifx_cat1_counter_config *const config = dev->config;
450 
451 	cyhal_timer_enable_event(&data->counter_obj, CYHAL_TIMER_IRQ_CAPTURE_COMPARE,
452 				 config->irq_priority, false);
453 	return 0;
454 }
455 
ifx_cat1_counter_get_pending_int(const struct device * dev)456 static uint32_t ifx_cat1_counter_get_pending_int(const struct device *dev)
457 {
458 	__ASSERT_NO_MSG(dev != NULL);
459 
460 	const struct ifx_cat1_counter_config *const config = dev->config;
461 
462 	return NVIC_GetPendingIRQ(config->irqn);
463 }
464 
ifx_cat1_counter_get_guard_period(const struct device * dev,uint32_t flags)465 static uint32_t ifx_cat1_counter_get_guard_period(const struct device *dev, uint32_t flags)
466 {
467 	ARG_UNUSED(flags);
468 	__ASSERT_NO_MSG(dev != NULL);
469 
470 	struct ifx_cat1_counter_data *const data = dev->data;
471 
472 	return data->guard_period;
473 }
474 
ifx_cat1_counter_set_guard_period(const struct device * dev,uint32_t guard,uint32_t flags)475 static int ifx_cat1_counter_set_guard_period(const struct device *dev, uint32_t guard,
476 					     uint32_t flags)
477 {
478 	ARG_UNUSED(flags);
479 	__ASSERT_NO_MSG(dev != NULL);
480 	__ASSERT_NO_MSG(guard < ifx_cat1_counter_get_top_value(dev));
481 
482 	struct ifx_cat1_counter_data *const data = dev->data;
483 
484 	data->guard_period = guard;
485 	return 0;
486 }
487 
488 static DEVICE_API(counter, counter_api) = {
489 	.start = ifx_cat1_counter_start,
490 	.stop = ifx_cat1_counter_stop,
491 	.get_value = ifx_cat1_counter_get_value,
492 	.set_alarm = ifx_cat1_counter_set_alarm,
493 	.cancel_alarm = ifx_cat1_counter_cancel_alarm,
494 	.set_top_value = ifx_cat1_counter_set_top_value,
495 	.get_pending_int = ifx_cat1_counter_get_pending_int,
496 	.get_top_value = ifx_cat1_counter_get_top_value,
497 	.get_guard_period = ifx_cat1_counter_get_guard_period,
498 	.set_guard_period = ifx_cat1_counter_set_guard_period,
499 };
500 
501 #define DT_INST_GET_CYHAL_GPIO_OR(inst, gpios_prop, default)                                       \
502 	COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, gpios_prop),                                       \
503 		    (DT_GET_CYHAL_GPIO_FROM_DT_GPIOS(DT_INST(inst, DT_DRV_COMPAT), gpios_prop)),   \
504 		    (default))
505 
506 /* Counter driver init macros */
507 #define INFINEON_CAT1_COUNTER_INIT(n)                                                              \
508                                                                                                    \
509 	static struct ifx_cat1_counter_data ifx_cat1_counter##n##_data;                            \
510                                                                                                    \
511 	static const struct ifx_cat1_counter_config ifx_cat1_counter##n##_config = {               \
512 		.counter_info = {.max_top_value = (DT_INST_PROP(n, resolution) == 32)              \
513 							  ? UINT32_MAX                             \
514 							  : UINT16_MAX,                            \
515 				 .freq = DT_INST_PROP_OR(n, clock_frequency, 10000),               \
516 				 .flags = COUNTER_CONFIG_INFO_COUNT_UP,                            \
517 				 .channels = 1},                                                   \
518 		.reg_addr = (TCPWM_CNT_Type *)DT_INST_REG_ADDR(n),                                 \
519 		.irq_priority = DT_INST_IRQ(n, priority),                                          \
520 		.irqn = DT_INST_IRQN(n),                                                           \
521 		.external_pin =                                                                    \
522 			(cyhal_gpio_t)DT_INST_GET_CYHAL_GPIO_OR(n, external_trigger_gpios, NC)};   \
523 	DEVICE_DT_INST_DEFINE(n, ifx_cat1_counter_init, NULL, &ifx_cat1_counter##n##_data,         \
524 			      &ifx_cat1_counter##n##_config, PRE_KERNEL_1,                         \
525 			      CONFIG_COUNTER_INIT_PRIORITY, &counter_api);
526 
527 DT_INST_FOREACH_STATUS_OKAY(INFINEON_CAT1_COUNTER_INIT);
528