xref: /openbmc/linux/arch/m68k/mac/misc.c (revision c0c74acb)
1 /*
2  * Miscellaneous Mac68K-specific stuff
3  */
4 
5 #include <linux/types.h>
6 #include <linux/errno.h>
7 #include <linux/kernel.h>
8 #include <linux/delay.h>
9 #include <linux/sched.h>
10 #include <linux/time.h>
11 #include <linux/rtc.h>
12 #include <linux/mm.h>
13 
14 #include <linux/adb.h>
15 #include <linux/cuda.h>
16 #include <linux/pmu.h>
17 
18 #include <linux/uaccess.h>
19 #include <asm/io.h>
20 #include <asm/segment.h>
21 #include <asm/setup.h>
22 #include <asm/macintosh.h>
23 #include <asm/mac_via.h>
24 #include <asm/mac_oss.h>
25 
26 #include <asm/machdep.h>
27 
28 /* Offset between Unix time (1970-based) and Mac time (1904-based) */
29 
30 #define RTC_OFFSET 2082844800
31 
32 static void (*rom_reset)(void);
33 
34 #ifdef CONFIG_ADB_CUDA
35 static long cuda_read_time(void)
36 {
37 	struct adb_request req;
38 	long time;
39 
40 	if (cuda_request(&req, NULL, 2, CUDA_PACKET, CUDA_GET_TIME) < 0)
41 		return 0;
42 	while (!req.complete)
43 		cuda_poll();
44 
45 	time = (req.reply[3] << 24) | (req.reply[4] << 16)
46 		| (req.reply[5] << 8) | req.reply[6];
47 	return time - RTC_OFFSET;
48 }
49 
50 static void cuda_write_time(long data)
51 {
52 	struct adb_request req;
53 	data += RTC_OFFSET;
54 	if (cuda_request(&req, NULL, 6, CUDA_PACKET, CUDA_SET_TIME,
55 			(data >> 24) & 0xFF, (data >> 16) & 0xFF,
56 			(data >> 8) & 0xFF, data & 0xFF) < 0)
57 		return;
58 	while (!req.complete)
59 		cuda_poll();
60 }
61 
62 static __u8 cuda_read_pram(int offset)
63 {
64 	struct adb_request req;
65 	if (cuda_request(&req, NULL, 4, CUDA_PACKET, CUDA_GET_PRAM,
66 			(offset >> 8) & 0xFF, offset & 0xFF) < 0)
67 		return 0;
68 	while (!req.complete)
69 		cuda_poll();
70 	return req.reply[3];
71 }
72 
73 static void cuda_write_pram(int offset, __u8 data)
74 {
75 	struct adb_request req;
76 	if (cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_SET_PRAM,
77 			(offset >> 8) & 0xFF, offset & 0xFF, data) < 0)
78 		return;
79 	while (!req.complete)
80 		cuda_poll();
81 }
82 #else
83 #define cuda_read_time() 0
84 #define cuda_write_time(n)
85 #define cuda_read_pram NULL
86 #define cuda_write_pram NULL
87 #endif
88 
89 #ifdef CONFIG_ADB_PMU68K
90 static long pmu_read_time(void)
91 {
92 	struct adb_request req;
93 	long time;
94 
95 	if (pmu_request(&req, NULL, 1, PMU_READ_RTC) < 0)
96 		return 0;
97 	while (!req.complete)
98 		pmu_poll();
99 
100 	time = (req.reply[1] << 24) | (req.reply[2] << 16)
101 		| (req.reply[3] << 8) | req.reply[4];
102 	return time - RTC_OFFSET;
103 }
104 
105 static void pmu_write_time(long data)
106 {
107 	struct adb_request req;
108 	data += RTC_OFFSET;
109 	if (pmu_request(&req, NULL, 5, PMU_SET_RTC,
110 			(data >> 24) & 0xFF, (data >> 16) & 0xFF,
111 			(data >> 8) & 0xFF, data & 0xFF) < 0)
112 		return;
113 	while (!req.complete)
114 		pmu_poll();
115 }
116 
117 static __u8 pmu_read_pram(int offset)
118 {
119 	struct adb_request req;
120 	if (pmu_request(&req, NULL, 3, PMU_READ_NVRAM,
121 			(offset >> 8) & 0xFF, offset & 0xFF) < 0)
122 		return 0;
123 	while (!req.complete)
124 		pmu_poll();
125 	return req.reply[3];
126 }
127 
128 static void pmu_write_pram(int offset, __u8 data)
129 {
130 	struct adb_request req;
131 	if (pmu_request(&req, NULL, 4, PMU_WRITE_NVRAM,
132 			(offset >> 8) & 0xFF, offset & 0xFF, data) < 0)
133 		return;
134 	while (!req.complete)
135 		pmu_poll();
136 }
137 #else
138 #define pmu_read_time() 0
139 #define pmu_write_time(n)
140 #define pmu_read_pram NULL
141 #define pmu_write_pram NULL
142 #endif
143 
144 /*
145  * VIA PRAM/RTC access routines
146  *
147  * Must be called with interrupts disabled and
148  * the RTC should be enabled.
149  */
150 
151 static __u8 via_pram_readbyte(void)
152 {
153 	int	i,reg;
154 	__u8	data;
155 
156 	reg = via1[vBufB] & ~VIA1B_vRTCClk;
157 
158 	/* Set the RTC data line to be an input. */
159 
160 	via1[vDirB] &= ~VIA1B_vRTCData;
161 
162 	/* The bits of the byte come out in MSB order */
163 
164 	data = 0;
165 	for (i = 0 ; i < 8 ; i++) {
166 		via1[vBufB] = reg;
167 		via1[vBufB] = reg | VIA1B_vRTCClk;
168 		data = (data << 1) | (via1[vBufB] & VIA1B_vRTCData);
169 	}
170 
171 	/* Return RTC data line to output state */
172 
173 	via1[vDirB] |= VIA1B_vRTCData;
174 
175 	return data;
176 }
177 
178 static void via_pram_writebyte(__u8 data)
179 {
180 	int	i,reg,bit;
181 
182 	reg = via1[vBufB] & ~(VIA1B_vRTCClk | VIA1B_vRTCData);
183 
184 	/* The bits of the byte go in in MSB order */
185 
186 	for (i = 0 ; i < 8 ; i++) {
187 		bit = data & 0x80? 1 : 0;
188 		data <<= 1;
189 		via1[vBufB] = reg | bit;
190 		via1[vBufB] = reg | bit | VIA1B_vRTCClk;
191 	}
192 }
193 
194 /*
195  * Execute a VIA PRAM/RTC command. For read commands
196  * data should point to a one-byte buffer for the
197  * resulting data. For write commands it should point
198  * to the data byte to for the command.
199  *
200  * This function disables all interrupts while running.
201  */
202 
203 static void via_pram_command(int command, __u8 *data)
204 {
205 	unsigned long flags;
206 	int	is_read;
207 
208 	local_irq_save(flags);
209 
210 	/* Enable the RTC and make sure the strobe line is high */
211 
212 	via1[vBufB] = (via1[vBufB] | VIA1B_vRTCClk) & ~VIA1B_vRTCEnb;
213 
214 	if (command & 0xFF00) {		/* extended (two-byte) command */
215 		via_pram_writebyte((command & 0xFF00) >> 8);
216 		via_pram_writebyte(command & 0xFF);
217 		is_read = command & 0x8000;
218 	} else {			/* one-byte command */
219 		via_pram_writebyte(command);
220 		is_read = command & 0x80;
221 	}
222 	if (is_read) {
223 		*data = via_pram_readbyte();
224 	} else {
225 		via_pram_writebyte(*data);
226 	}
227 
228 	/* All done, disable the RTC */
229 
230 	via1[vBufB] |= VIA1B_vRTCEnb;
231 
232 	local_irq_restore(flags);
233 }
234 
235 static __u8 via_read_pram(int offset)
236 {
237 	return 0;
238 }
239 
240 static void via_write_pram(int offset, __u8 data)
241 {
242 }
243 
244 /*
245  * Return the current time in seconds since January 1, 1904.
246  *
247  * This only works on machines with the VIA-based PRAM/RTC, which
248  * is basically any machine with Mac II-style ADB.
249  */
250 
251 static long via_read_time(void)
252 {
253 	union {
254 		__u8 cdata[4];
255 		long idata;
256 	} result, last_result;
257 	int count = 1;
258 
259 	via_pram_command(0x81, &last_result.cdata[3]);
260 	via_pram_command(0x85, &last_result.cdata[2]);
261 	via_pram_command(0x89, &last_result.cdata[1]);
262 	via_pram_command(0x8D, &last_result.cdata[0]);
263 
264 	/*
265 	 * The NetBSD guys say to loop until you get the same reading
266 	 * twice in a row.
267 	 */
268 
269 	while (1) {
270 		via_pram_command(0x81, &result.cdata[3]);
271 		via_pram_command(0x85, &result.cdata[2]);
272 		via_pram_command(0x89, &result.cdata[1]);
273 		via_pram_command(0x8D, &result.cdata[0]);
274 
275 		if (result.idata == last_result.idata)
276 			return result.idata - RTC_OFFSET;
277 
278 		if (++count > 10)
279 			break;
280 
281 		last_result.idata = result.idata;
282 	}
283 
284 	pr_err("via_read_time: failed to read a stable value; "
285 	       "got 0x%08lx then 0x%08lx\n",
286 	       last_result.idata, result.idata);
287 
288 	return 0;
289 }
290 
291 /*
292  * Set the current time to a number of seconds since January 1, 1904.
293  *
294  * This only works on machines with the VIA-based PRAM/RTC, which
295  * is basically any machine with Mac II-style ADB.
296  */
297 
298 static void via_write_time(long time)
299 {
300 	union {
301 		__u8  cdata[4];
302 		long  idata;
303 	} data;
304 	__u8	temp;
305 
306 	/* Clear the write protect bit */
307 
308 	temp = 0x55;
309 	via_pram_command(0x35, &temp);
310 
311 	data.idata = time + RTC_OFFSET;
312 	via_pram_command(0x01, &data.cdata[3]);
313 	via_pram_command(0x05, &data.cdata[2]);
314 	via_pram_command(0x09, &data.cdata[1]);
315 	via_pram_command(0x0D, &data.cdata[0]);
316 
317 	/* Set the write protect bit */
318 
319 	temp = 0xD5;
320 	via_pram_command(0x35, &temp);
321 }
322 
323 static void via_shutdown(void)
324 {
325 	if (rbv_present) {
326 		via2[rBufB] &= ~0x04;
327 	} else {
328 		/* Direction of vDirB is output */
329 		via2[vDirB] |= 0x04;
330 		/* Send a value of 0 on that line */
331 		via2[vBufB] &= ~0x04;
332 		mdelay(1000);
333 	}
334 }
335 
336 /*
337  * FIXME: not sure how this is supposed to work exactly...
338  */
339 
340 static void oss_shutdown(void)
341 {
342 	oss->rom_ctrl = OSS_POWEROFF;
343 }
344 
345 #ifdef CONFIG_ADB_CUDA
346 
347 static void cuda_restart(void)
348 {
349 	struct adb_request req;
350 	if (cuda_request(&req, NULL, 2, CUDA_PACKET, CUDA_RESET_SYSTEM) < 0)
351 		return;
352 	while (!req.complete)
353 		cuda_poll();
354 }
355 
356 static void cuda_shutdown(void)
357 {
358 	struct adb_request req;
359 	if (cuda_request(&req, NULL, 2, CUDA_PACKET, CUDA_POWERDOWN) < 0)
360 		return;
361 	while (!req.complete)
362 		cuda_poll();
363 }
364 
365 #endif /* CONFIG_ADB_CUDA */
366 
367 #ifdef CONFIG_ADB_PMU68K
368 
369 void pmu_restart(void)
370 {
371 	struct adb_request req;
372 	if (pmu_request(&req, NULL,
373 			2, PMU_SET_INTR_MASK, PMU_INT_ADB|PMU_INT_TICK) < 0)
374 		return;
375 	while (!req.complete)
376 		pmu_poll();
377 	if (pmu_request(&req, NULL, 1, PMU_RESET) < 0)
378 		return;
379 	while (!req.complete)
380 		pmu_poll();
381 }
382 
383 void pmu_shutdown(void)
384 {
385 	struct adb_request req;
386 	if (pmu_request(&req, NULL,
387 			2, PMU_SET_INTR_MASK, PMU_INT_ADB|PMU_INT_TICK) < 0)
388 		return;
389 	while (!req.complete)
390 		pmu_poll();
391 	if (pmu_request(&req, NULL, 5, PMU_SHUTDOWN, 'M', 'A', 'T', 'T') < 0)
392 		return;
393 	while (!req.complete)
394 		pmu_poll();
395 }
396 
397 #endif
398 
399 /*
400  *-------------------------------------------------------------------
401  * Below this point are the generic routines; they'll dispatch to the
402  * correct routine for the hardware on which we're running.
403  *-------------------------------------------------------------------
404  */
405 
406 void mac_pram_read(int offset, __u8 *buffer, int len)
407 {
408 	__u8 (*func)(int);
409 	int i;
410 
411 	switch(macintosh_config->adb_type) {
412 	case MAC_ADB_PB1:
413 	case MAC_ADB_PB2:
414 		func = pmu_read_pram; break;
415 	case MAC_ADB_EGRET:
416 	case MAC_ADB_CUDA:
417 		func = cuda_read_pram; break;
418 	default:
419 		func = via_read_pram;
420 	}
421 	if (!func)
422 		return;
423 	for (i = 0 ; i < len ; i++) {
424 		buffer[i] = (*func)(offset++);
425 	}
426 }
427 
428 void mac_pram_write(int offset, __u8 *buffer, int len)
429 {
430 	void (*func)(int, __u8);
431 	int i;
432 
433 	switch(macintosh_config->adb_type) {
434 	case MAC_ADB_PB1:
435 	case MAC_ADB_PB2:
436 		func = pmu_write_pram; break;
437 	case MAC_ADB_EGRET:
438 	case MAC_ADB_CUDA:
439 		func = cuda_write_pram; break;
440 	default:
441 		func = via_write_pram;
442 	}
443 	if (!func)
444 		return;
445 	for (i = 0 ; i < len ; i++) {
446 		(*func)(offset++, buffer[i]);
447 	}
448 }
449 
450 void mac_poweroff(void)
451 {
452 	if (oss_present) {
453 		oss_shutdown();
454 	} else if (macintosh_config->adb_type == MAC_ADB_II) {
455 		via_shutdown();
456 #ifdef CONFIG_ADB_CUDA
457 	} else if (macintosh_config->adb_type == MAC_ADB_EGRET ||
458 	           macintosh_config->adb_type == MAC_ADB_CUDA) {
459 		cuda_shutdown();
460 #endif
461 #ifdef CONFIG_ADB_PMU68K
462 	} else if (macintosh_config->adb_type == MAC_ADB_PB1
463 		|| macintosh_config->adb_type == MAC_ADB_PB2) {
464 		pmu_shutdown();
465 #endif
466 	}
467 	local_irq_enable();
468 	printk("It is now safe to turn off your Macintosh.\n");
469 	while(1);
470 }
471 
472 void mac_reset(void)
473 {
474 	if (macintosh_config->adb_type == MAC_ADB_II) {
475 		unsigned long flags;
476 
477 		/* need ROMBASE in booter */
478 		/* indeed, plus need to MAP THE ROM !! */
479 
480 		if (mac_bi_data.rombase == 0)
481 			mac_bi_data.rombase = 0x40800000;
482 
483 		/* works on some */
484 		rom_reset = (void *) (mac_bi_data.rombase + 0xa);
485 
486 		if (macintosh_config->ident == MAC_MODEL_SE30) {
487 			/*
488 			 * MSch: Machines known to crash on ROM reset ...
489 			 */
490 		} else {
491 			local_irq_save(flags);
492 
493 			rom_reset();
494 
495 			local_irq_restore(flags);
496 		}
497 #ifdef CONFIG_ADB_CUDA
498 	} else if (macintosh_config->adb_type == MAC_ADB_EGRET ||
499 	           macintosh_config->adb_type == MAC_ADB_CUDA) {
500 		cuda_restart();
501 #endif
502 #ifdef CONFIG_ADB_PMU68K
503 	} else if (macintosh_config->adb_type == MAC_ADB_PB1
504 		|| macintosh_config->adb_type == MAC_ADB_PB2) {
505 		pmu_restart();
506 #endif
507 	} else if (CPU_IS_030) {
508 
509 		/* 030-specific reset routine.  The idea is general, but the
510 		 * specific registers to reset are '030-specific.  Until I
511 		 * have a non-030 machine, I can't test anything else.
512 		 *  -- C. Scott Ananian <cananian@alumni.princeton.edu>
513 		 */
514 
515 		unsigned long rombase = 0x40000000;
516 
517 		/* make a 1-to-1 mapping, using the transparent tran. reg. */
518 		unsigned long virt = (unsigned long) mac_reset;
519 		unsigned long phys = virt_to_phys(mac_reset);
520 		unsigned long addr = (phys&0xFF000000)|0x8777;
521 		unsigned long offset = phys-virt;
522 		local_irq_disable(); /* lets not screw this up, ok? */
523 		__asm__ __volatile__(".chip 68030\n\t"
524 				     "pmove %0,%/tt0\n\t"
525 				     ".chip 68k"
526 				     : : "m" (addr));
527 		/* Now jump to physical address so we can disable MMU */
528 		__asm__ __volatile__(
529                     ".chip 68030\n\t"
530 		    "lea %/pc@(1f),%/a0\n\t"
531 		    "addl %0,%/a0\n\t"/* fixup target address and stack ptr */
532 		    "addl %0,%/sp\n\t"
533 		    "pflusha\n\t"
534 		    "jmp %/a0@\n\t" /* jump into physical memory */
535 		    "0:.long 0\n\t" /* a constant zero. */
536 		    /* OK.  Now reset everything and jump to reset vector. */
537 		    "1:\n\t"
538 		    "lea %/pc@(0b),%/a0\n\t"
539 		    "pmove %/a0@, %/tc\n\t" /* disable mmu */
540 		    "pmove %/a0@, %/tt0\n\t" /* disable tt0 */
541 		    "pmove %/a0@, %/tt1\n\t" /* disable tt1 */
542 		    "movel #0, %/a0\n\t"
543 		    "movec %/a0, %/vbr\n\t" /* clear vector base register */
544 		    "movec %/a0, %/cacr\n\t" /* disable caches */
545 		    "movel #0x0808,%/a0\n\t"
546 		    "movec %/a0, %/cacr\n\t" /* flush i&d caches */
547 		    "movew #0x2700,%/sr\n\t" /* set up status register */
548 		    "movel %1@(0x0),%/a0\n\t"/* load interrupt stack pointer */
549 		    "movec %/a0, %/isp\n\t"
550 		    "movel %1@(0x4),%/a0\n\t" /* load reset vector */
551 		    "reset\n\t" /* reset external devices */
552 		    "jmp %/a0@\n\t" /* jump to the reset vector */
553 		    ".chip 68k"
554 		    : : "r" (offset), "a" (rombase) : "a0");
555 	}
556 
557 	/* should never get here */
558 	local_irq_enable();
559 	printk ("Restart failed.  Please restart manually.\n");
560 	while(1);
561 }
562 
563 /*
564  * This function translates seconds since 1970 into a proper date.
565  *
566  * Algorithm cribbed from glibc2.1, __offtime().
567  */
568 #define SECS_PER_MINUTE (60)
569 #define SECS_PER_HOUR  (SECS_PER_MINUTE * 60)
570 #define SECS_PER_DAY   (SECS_PER_HOUR * 24)
571 
572 static void unmktime(unsigned long time, long offset,
573 		     int *yearp, int *monp, int *dayp,
574 		     int *hourp, int *minp, int *secp)
575 {
576         /* How many days come before each month (0-12).  */
577 	static const unsigned short int __mon_yday[2][13] =
578 	{
579 		/* Normal years.  */
580 		{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
581 		/* Leap years.  */
582 		{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
583 	};
584 	long int days, rem, y, wday, yday;
585 	const unsigned short int *ip;
586 
587 	days = time / SECS_PER_DAY;
588 	rem = time % SECS_PER_DAY;
589 	rem += offset;
590 	while (rem < 0) {
591 		rem += SECS_PER_DAY;
592 		--days;
593 	}
594 	while (rem >= SECS_PER_DAY) {
595 		rem -= SECS_PER_DAY;
596 		++days;
597 	}
598 	*hourp = rem / SECS_PER_HOUR;
599 	rem %= SECS_PER_HOUR;
600 	*minp = rem / SECS_PER_MINUTE;
601 	*secp = rem % SECS_PER_MINUTE;
602 	/* January 1, 1970 was a Thursday. */
603 	wday = (4 + days) % 7; /* Day in the week. Not currently used */
604 	if (wday < 0) wday += 7;
605 	y = 1970;
606 
607 #define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
608 #define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))
609 #define __isleap(year)	\
610   ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
611 
612 	while (days < 0 || days >= (__isleap (y) ? 366 : 365))
613 	{
614 		/* Guess a corrected year, assuming 365 days per year.  */
615 		long int yg = y + days / 365 - (days % 365 < 0);
616 
617 		/* Adjust DAYS and Y to match the guessed year.  */
618 		days -= ((yg - y) * 365
619 			 + LEAPS_THRU_END_OF (yg - 1)
620 			 - LEAPS_THRU_END_OF (y - 1));
621 		y = yg;
622 	}
623 	*yearp = y - 1900;
624 	yday = days; /* day in the year.  Not currently used. */
625 	ip = __mon_yday[__isleap(y)];
626 	for (y = 11; days < (long int) ip[y]; --y)
627 		continue;
628 	days -= ip[y];
629 	*monp = y;
630 	*dayp = days + 1; /* day in the month */
631 	return;
632 }
633 
634 /*
635  * Read/write the hardware clock.
636  */
637 
638 int mac_hwclk(int op, struct rtc_time *t)
639 {
640 	unsigned long now;
641 
642 	if (!op) { /* read */
643 		switch (macintosh_config->adb_type) {
644 		case MAC_ADB_II:
645 		case MAC_ADB_IOP:
646 			now = via_read_time();
647 			break;
648 		case MAC_ADB_PB1:
649 		case MAC_ADB_PB2:
650 			now = pmu_read_time();
651 			break;
652 		case MAC_ADB_EGRET:
653 		case MAC_ADB_CUDA:
654 			now = cuda_read_time();
655 			break;
656 		default:
657 			now = 0;
658 		}
659 
660 		t->tm_wday = 0;
661 		unmktime(now, 0,
662 			 &t->tm_year, &t->tm_mon, &t->tm_mday,
663 			 &t->tm_hour, &t->tm_min, &t->tm_sec);
664 #if 0
665 		printk("mac_hwclk: read %04d-%02d-%-2d %02d:%02d:%02d\n",
666 			t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
667 			t->tm_hour, t->tm_min, t->tm_sec);
668 #endif
669 	} else { /* write */
670 #if 0
671 		printk("mac_hwclk: tried to write %04d-%02d-%-2d %02d:%02d:%02d\n",
672 			t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
673 			t->tm_hour, t->tm_min, t->tm_sec);
674 #endif
675 
676 		now = mktime(t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
677 			     t->tm_hour, t->tm_min, t->tm_sec);
678 
679 		switch (macintosh_config->adb_type) {
680 		case MAC_ADB_II:
681 		case MAC_ADB_IOP:
682 			via_write_time(now);
683 			break;
684 		case MAC_ADB_EGRET:
685 		case MAC_ADB_CUDA:
686 			cuda_write_time(now);
687 			break;
688 		case MAC_ADB_PB1:
689 		case MAC_ADB_PB2:
690 			pmu_write_time(now);
691 			break;
692 		}
693 	}
694 	return 0;
695 }
696 
697 /*
698  * Set minutes/seconds in the hardware clock
699  */
700 
701 int mac_set_clock_mmss (unsigned long nowtime)
702 {
703 	struct rtc_time now;
704 
705 	mac_hwclk(0, &now);
706 	now.tm_sec = nowtime % 60;
707 	now.tm_min = (nowtime / 60) % 60;
708 	mac_hwclk(1, &now);
709 
710 	return 0;
711 }
712