1// SPDX-License-Identifier: GPL-2.0 2/* 3 * Copyright (C) 2018 Marvell International Ltd. 4 */ 5 6#include <dm.h> 7#include <errno.h> 8#include <malloc.h> 9#include <misc.h> 10#include <net.h> 11 12#include <linux/bitops.h> 13#include <linux/delay.h> 14#include <linux/list.h> 15 16#include <asm/arch/board.h> 17#include <asm/io.h> 18 19#include "cgx_intf.h" 20#include "cgx.h" 21#include "nix.h" 22 23static u64 cgx_rd_scrx(u8 cgx, u8 lmac, u8 index) 24{ 25 u64 addr; 26 27 addr = (index == 1) ? CGX_CMR_SCRATCH1 : CGX_CMR_SCRATCH0; 28 addr += CGX_SHIFT(cgx) + CMR_SHIFT(lmac); 29 return readq(addr); 30} 31 32static void cgx_wr_scrx(u8 cgx, u8 lmac, u8 index, u64 val) 33{ 34 u64 addr; 35 36 addr = (index == 1) ? CGX_CMR_SCRATCH1 : CGX_CMR_SCRATCH0; 37 addr += CGX_SHIFT(cgx) + CMR_SHIFT(lmac); 38 writeq(val, addr); 39} 40 41static u64 cgx_rd_scr0(u8 cgx, u8 lmac) 42{ 43 return cgx_rd_scrx(cgx, lmac, 0); 44} 45 46static u64 cgx_rd_scr1(u8 cgx, u8 lmac) 47{ 48 return cgx_rd_scrx(cgx, lmac, 1); 49} 50 51static void cgx_wr_scr0(u8 cgx, u8 lmac, u64 val) 52{ 53 return cgx_wr_scrx(cgx, lmac, 0, val); 54} 55 56static void cgx_wr_scr1(u8 cgx, u8 lmac, u64 val) 57{ 58 return cgx_wr_scrx(cgx, lmac, 1, val); 59} 60 61static inline void set_ownership(u8 cgx, u8 lmac, u8 val) 62{ 63 union cgx_scratchx1 scr1; 64 65 scr1.u = cgx_rd_scr1(cgx, lmac); 66 scr1.s.own_status = val; 67 cgx_wr_scr1(cgx, lmac, scr1.u); 68} 69 70static int wait_for_ownership(u8 cgx, u8 lmac) 71{ 72 union cgx_scratchx1 scr1; 73 union cgx_scratchx0 scr0; 74 u64 cmrx_int; 75 int timeout = 5000; 76 77 do { 78 scr1.u = cgx_rd_scr1(cgx, lmac); 79 scr0.u = cgx_rd_scr0(cgx, lmac); 80 /* clear async events if any */ 81 if (scr0.s.evt_sts.evt_type == CGX_EVT_ASYNC && 82 scr0.s.evt_sts.ack) { 83 /* clear interrupt */ 84 cmrx_int = readq(CGX_CMR_INT + 85 CGX_SHIFT(cgx) + CMR_SHIFT(lmac)); 86 cmrx_int |= 0x2; // Overflw bit 87 writeq(cmrx_int, CGX_CMR_INT + 88 CGX_SHIFT(cgx) + CMR_SHIFT(lmac)); 89 90 /* clear ack */ 91 scr0.s.evt_sts.ack = 0; 92 cgx_wr_scr0(cgx, lmac, scr0.u); 93 } 94 95 if (timeout-- < 0) { 96 debug("timeout waiting for ownership\n"); 97 return -ETIMEDOUT; 98 } 99 mdelay(1); 100 } while ((scr1.s.own_status == CGX_OWN_FIRMWARE) && 101 scr0.s.evt_sts.ack); 102 103 return 0; 104} 105 106int cgx_intf_req(u8 cgx, u8 lmac, union cgx_cmd_s cmd_args, u64 *rsp, 107 int use_cmd_id_only) 108{ 109 union cgx_scratchx1 scr1; 110 union cgx_scratchx0 scr0; 111 u64 cmrx_int; 112 int timeout = 500; 113 int err = 0; 114 u8 cmd = cmd_args.cmd.id; 115 116 if (wait_for_ownership(cgx, lmac)) { 117 err = -ETIMEDOUT; 118 goto error; 119 } 120 121 /* send command */ 122 scr1.u = cgx_rd_scr1(cgx, lmac); 123 124 if (use_cmd_id_only) { 125 scr1.s.cmd.id = cmd; 126 } else { 127 cmd_args.own_status = scr1.s.own_status; 128 scr1.s = cmd_args; 129 } 130 cgx_wr_scr1(cgx, lmac, scr1.u); 131 132 set_ownership(cgx, lmac, CGX_OWN_FIRMWARE); 133 134 /* wait for response and ownership */ 135 do { 136 scr0.u = cgx_rd_scr0(cgx, lmac); 137 scr1.u = cgx_rd_scr1(cgx, lmac); 138 mdelay(10); 139 } while (timeout-- && (!scr0.s.evt_sts.ack) && 140 (scr1.s.own_status == CGX_OWN_FIRMWARE)); 141 if (timeout < 0) { 142 debug("%s timeout waiting for ack\n", __func__); 143 err = -ETIMEDOUT; 144 goto error; 145 } 146 147 if (cmd == CGX_CMD_INTF_SHUTDOWN) 148 goto error; 149 150 if (scr0.s.evt_sts.evt_type != CGX_EVT_CMD_RESP) { 151 debug("%s received async event instead of cmd resp event\n", 152 __func__); 153 err = -1; 154 goto error; 155 } 156 if (scr0.s.evt_sts.id != cmd) { 157 debug("%s received resp for cmd %d expected cmd %d\n", 158 __func__, scr0.s.evt_sts.id, cmd); 159 err = -1; 160 goto error; 161 } 162 if (scr0.s.evt_sts.stat != CGX_STAT_SUCCESS) { 163 debug("%s cmd%d failed on cgx%u lmac%u with errcode %d\n", 164 __func__, cmd, cgx, lmac, scr0.s.link_sts.err_type); 165 err = -1; 166 } 167 168error: 169 /* clear interrupt */ 170 cmrx_int = readq(CGX_CMR_INT + CGX_SHIFT(cgx) + CMR_SHIFT(lmac)); 171 cmrx_int |= 0x2; // Overflw bit 172 writeq(cmrx_int, CGX_CMR_INT + CGX_SHIFT(cgx) + CMR_SHIFT(lmac)); 173 174 /* clear ownership and ack */ 175 scr0.s.evt_sts.ack = 0; 176 cgx_wr_scr0(cgx, lmac, scr0.u); 177 178 *rsp = err ? 0 : scr0.u; 179 180 return err; 181} 182 183int cgx_intf_get_mac_addr(u8 cgx, u8 lmac, u8 *mac) 184{ 185 union cgx_scratchx0 scr0; 186 int ret; 187 union cgx_cmd_s cmd; 188 189 cmd.cmd.id = CGX_CMD_GET_MAC_ADDR; 190 191 ret = cgx_intf_req(cgx, lmac, cmd, &scr0.u, 1); 192 if (ret) 193 return -1; 194 195 scr0.u >>= 9; 196 memcpy(mac, &scr0.u, 6); 197 198 return 0; 199} 200 201int cgx_intf_get_ver(u8 cgx, u8 lmac, u8 *ver) 202{ 203 union cgx_scratchx0 scr0; 204 int ret; 205 union cgx_cmd_s cmd; 206 207 cmd.cmd.id = CGX_CMD_GET_FW_VER; 208 209 ret = cgx_intf_req(cgx, lmac, cmd, &scr0.u, 1); 210 if (ret) 211 return -1; 212 213 scr0.u >>= 9; 214 *ver = scr0.u & 0xFFFF; 215 216 return 0; 217} 218 219int cgx_intf_get_link_sts(u8 cgx, u8 lmac, u64 *lnk_sts) 220{ 221 union cgx_scratchx0 scr0; 222 int ret; 223 union cgx_cmd_s cmd; 224 225 cmd.cmd.id = CGX_CMD_GET_LINK_STS; 226 227 ret = cgx_intf_req(cgx, lmac, cmd, &scr0.u, 1); 228 if (ret) 229 return -1; 230 231 scr0.u >>= 9; 232 /* pass the same format as cgx_lnk_sts_s 233 * err_type:10, speed:4, full_duplex:1, link_up:1 234 */ 235 *lnk_sts = scr0.u & 0xFFFF; 236 return 0; 237} 238 239int cgx_intf_link_up_dwn(u8 cgx, u8 lmac, u8 up_dwn, u64 *lnk_sts) 240{ 241 union cgx_scratchx0 scr0; 242 int ret; 243 union cgx_cmd_s cmd; 244 245 cmd.cmd.id = up_dwn ? CGX_CMD_LINK_BRING_UP : CGX_CMD_LINK_BRING_DOWN; 246 247 ret = cgx_intf_req(cgx, lmac, cmd, &scr0.u, 1); 248 if (ret) 249 return -1; 250 251 scr0.u >>= 9; 252 /* pass the same format as cgx_lnk_sts_s 253 * err_type:10, speed:4, full_duplex:1, link_up:1 254 */ 255 *lnk_sts = scr0.u & 0xFFFF; 256 return 0; 257} 258 259void cgx_intf_shutdown(void) 260{ 261 union cgx_scratchx0 scr0; 262 union cgx_cmd_s cmd; 263 264 cmd.cmd.id = CGX_CMD_INTF_SHUTDOWN; 265 266 cgx_intf_req(0, 0, cmd, &scr0.u, 1); 267} 268 269int cgx_intf_prbs(u8 qlm, u8 mode, u32 time, u8 lane) 270{ 271 union cgx_scratchx0 scr0; 272 int ret; 273 union cgx_cmd_s cmd; 274 275 cmd.cmd.id = CGX_CMD_PRBS; 276 277 cmd.prbs_args.qlm = qlm; 278 cmd.prbs_args.mode = mode; 279 cmd.prbs_args.time = time; 280 cmd.prbs_args.lane = lane; 281 282 ret = cgx_intf_req(0, 0, cmd, &scr0.u, 0); 283 if (ret) 284 return -1; 285 286 return 0; 287} 288 289enum cgx_mode { 290 MODE_10G_C2C, 291 MODE_10G_C2M, 292 MODE_10G_KR, 293 MODE_25G_C2C, 294 MODE_25G_2_C2C, 295 MODE_50G_C2C, 296 MODE_50G_4_C2C 297}; 298 299static char intf_speed_to_str[][8] = { 300 "10M", 301 "100M", 302 "1G", 303 "2.5G", 304 "5G", 305 "10G", 306 "20G", 307 "25G", 308 "40G", 309 "50G", 310 "80G", 311 "100G", 312}; 313 314static void mode_to_args(int mode, struct cgx_mode_change_args *args) 315{ 316 args->an = 0; 317 args->duplex = 0; 318 args->port = 0; 319 320 switch (mode) { 321 case MODE_10G_C2C: 322 args->speed = CGX_LINK_10G; 323 args->mode = BIT_ULL(CGX_MODE_10G_C2C_BIT); 324 break; 325 case MODE_10G_C2M: 326 args->speed = CGX_LINK_10G; 327 args->mode = BIT_ULL(CGX_MODE_10G_C2M_BIT); 328 break; 329 case MODE_10G_KR: 330 args->speed = CGX_LINK_10G; 331 args->mode = BIT_ULL(CGX_MODE_10G_KR_BIT); 332 args->an = 1; 333 break; 334 case MODE_25G_C2C: 335 args->speed = CGX_LINK_25G; 336 args->mode = BIT_ULL(CGX_MODE_25G_C2C_BIT); 337 break; 338 case MODE_25G_2_C2C: 339 args->speed = CGX_LINK_25G; 340 args->mode = BIT_ULL(CGX_MODE_25G_2_C2C_BIT); 341 break; 342 case MODE_50G_C2C: 343 args->speed = CGX_LINK_50G; 344 args->mode = BIT_ULL(CGX_MODE_50G_C2C_BIT); 345 break; 346 case MODE_50G_4_C2C: 347 args->speed = CGX_LINK_50G; 348 args->mode = BIT_ULL(CGX_MODE_50G_4_C2C_BIT); 349 } 350} 351 352int cgx_intf_set_mode(struct udevice *ethdev, int mode) 353{ 354 struct rvu_pf *rvu = dev_get_priv(ethdev); 355 struct nix *nix = rvu->nix; 356 union cgx_scratchx0 scr0; 357 int ret; 358 union cgx_cmd_s cmd; 359 360 cmd.cmd.id = CGX_CMD_MODE_CHANGE; 361 362 mode_to_args(mode, &cmd.mode_change_args); 363 364 ret = cgx_intf_req(nix->lmac->cgx->cgx_id, nix->lmac->lmac_id, 365 cmd, &scr0.u, 0); 366 if (ret) { 367 printf("Mode change command failed for %s\n", ethdev->name); 368 return -1; 369 } 370 371 cmd.cmd.id = CGX_CMD_GET_LINK_STS; 372 ret = cgx_intf_req(nix->lmac->cgx->cgx_id, nix->lmac->lmac_id, 373 cmd, &scr0.u, 1); 374 if (ret) { 375 printf("Get Link Status failed for %s\n", ethdev->name); 376 return -1; 377 } 378 379 printf("Current Link Status: "); 380 if (scr0.s.link_sts.speed) { 381 printf("%s\n", intf_speed_to_str[scr0.s.link_sts.speed]); 382 switch (scr0.s.link_sts.fec) { 383 case 0: 384 printf("FEC_NONE\n"); 385 break; 386 case 1: 387 printf("FEC_BASE_R\n"); 388 break; 389 case 2: 390 printf("FEC_RS\n"); 391 break; 392 } 393 printf("Auto Negotiation %sabled\n", 394 scr0.s.link_sts.an ? "En" : "Dis"); 395 printf("%s Duplex\n", 396 scr0.s.link_sts.full_duplex ? "Full" : "Half"); 397 } else { 398 printf("Down\n"); 399 } 400 return 0; 401} 402 403int cgx_intf_get_mode(struct udevice *ethdev) 404{ 405 struct rvu_pf *rvu = dev_get_priv(ethdev); 406 struct nix *nix = rvu->nix; 407 union cgx_scratchx0 scr0; 408 int ret; 409 union cgx_cmd_s cmd; 410 411 cmd.cmd.id = CGX_CMD_GET_LINK_STS; 412 ret = cgx_intf_req(nix->lmac->cgx->cgx_id, nix->lmac->lmac_id, 413 cmd, &scr0.u, 1); 414 if (ret) { 415 printf("Get link status failed for %s\n", ethdev->name); 416 return -1; 417 } 418 printf("Current Interface Mode: "); 419 switch (scr0.s.link_sts.mode) { 420 case CGX_MODE_10G_C2C_BIT: 421 printf("10G_C2C\n"); 422 break; 423 case CGX_MODE_10G_C2M_BIT: 424 printf("10G_C2M\n"); 425 break; 426 case CGX_MODE_10G_KR_BIT: 427 printf("10G_KR\n"); 428 break; 429 case CGX_MODE_25G_C2C_BIT: 430 printf("25G_C2C\n"); 431 break; 432 case CGX_MODE_25G_2_C2C_BIT: 433 printf("25G_2_C2C\n"); 434 break; 435 case CGX_MODE_50G_C2C_BIT: 436 printf("50G_C2C\n"); 437 break; 438 case CGX_MODE_50G_4_C2C_BIT: 439 printf("50G_4_C2C\n"); 440 break; 441 default: 442 printf("Unknown\n"); 443 break; 444 } 445 return 0; 446} 447 448int cgx_intf_get_fec(struct udevice *ethdev) 449{ 450 struct rvu_pf *rvu = dev_get_priv(ethdev); 451 struct nix *nix = rvu->nix; 452 union cgx_scratchx0 scr0; 453 int ret; 454 union cgx_cmd_s cmd; 455 456 cmd.cmd.id = CGX_CMD_GET_SUPPORTED_FEC; 457 458 ret = cgx_intf_req(nix->lmac->cgx->cgx_id, nix->lmac->lmac_id, 459 cmd, &scr0.u, 1); 460 if (ret) { 461 printf("Get supported FEC failed for %s\n", ethdev->name); 462 return -1; 463 } 464 465 printf("Supported FEC type: "); 466 switch (scr0.s.supported_fec.fec) { 467 case 0: 468 printf("FEC_NONE\n"); 469 break; 470 case 1: 471 printf("FEC_BASE_R\n"); 472 break; 473 case 2: 474 printf("FEC_RS\n"); 475 break; 476 case 3: 477 printf("FEC_BASE_R FEC_RS\n"); 478 break; 479 } 480 481 cmd.cmd.id = CGX_CMD_GET_LINK_STS; 482 ret = cgx_intf_req(nix->lmac->cgx->cgx_id, nix->lmac->lmac_id, 483 cmd, &scr0.u, 1); 484 if (ret) { 485 printf("Get active fec failed for %s\n", ethdev->name); 486 return -1; 487 } 488 printf("Active FEC type: "); 489 switch (scr0.s.link_sts.fec) { 490 case 0: 491 printf("FEC_NONE\n"); 492 break; 493 case 1: 494 printf("FEC_BASE_R\n"); 495 break; 496 case 2: 497 printf("FEC_RS\n"); 498 break; 499 } 500 return 0; 501} 502 503int cgx_intf_set_fec(struct udevice *ethdev, int type) 504{ 505 struct rvu_pf *rvu = dev_get_priv(ethdev); 506 struct nix *nix = rvu->nix; 507 union cgx_scratchx0 scr0; 508 int ret; 509 union cgx_cmd_s cmd; 510 511 cmd.cmd.id = CGX_CMD_SET_FEC; 512 cmd.fec_args.fec = type; 513 514 ret = cgx_intf_req(nix->lmac->cgx->cgx_id, nix->lmac->lmac_id, 515 cmd, &scr0.u, 0); 516 if (ret) { 517 printf("Set FEC type %d failed for %s\n", type, ethdev->name); 518 return -1; 519 } 520 return 0; 521} 522 523int cgx_intf_get_phy_mod_type(struct udevice *ethdev) 524{ 525 struct rvu_pf *rvu = dev_get_priv(ethdev); 526 struct nix *nix = rvu->nix; 527 union cgx_scratchx0 scr0; 528 int ret; 529 union cgx_cmd_s cmd; 530 531 cmd.cmd.id = CGX_CMD_GET_PHY_MOD_TYPE; 532 533 ret = cgx_intf_req(nix->lmac->cgx->cgx_id, nix->lmac->lmac_id, 534 cmd, &scr0.u, 1); 535 if (ret) { 536 printf("Get PHYMOD type failed for %s\n", ethdev->name); 537 return -1; 538 } 539 printf("Current phy mod type %s\n", 540 scr0.s.phy_mod_type.mod ? "PAM4" : "NRZ"); 541 return 0; 542} 543 544int cgx_intf_set_phy_mod_type(struct udevice *ethdev, int type) 545{ 546 struct rvu_pf *rvu = dev_get_priv(ethdev); 547 struct nix *nix = rvu->nix; 548 union cgx_scratchx0 scr0; 549 int ret; 550 union cgx_cmd_s cmd; 551 552 cmd.cmd.id = CGX_CMD_SET_PHY_MOD_TYPE; 553 cmd.phy_mod_args.mod = type; 554 555 ret = cgx_intf_req(nix->lmac->cgx->cgx_id, nix->lmac->lmac_id, 556 cmd, &scr0.u, 0); 557 if (ret) { 558 printf("Set PHYMOD type %d failed for %s\n", type, 559 ethdev->name); 560 return -1; 561 } 562 563 return 0; 564} 565 566int cgx_intf_set_an_lbk(struct udevice *ethdev, int enable) 567{ 568 struct rvu_pf *rvu = dev_get_priv(ethdev); 569 struct nix *nix = rvu->nix; 570 union cgx_scratchx0 scr0; 571 int ret; 572 union cgx_cmd_s cmd; 573 574 cmd.cmd.id = CGX_CMD_AN_LOOPBACK; 575 cmd.cmd_args.enable = enable; 576 577 ret = cgx_intf_req(nix->lmac->cgx->cgx_id, nix->lmac->lmac_id, 578 cmd, &scr0.u, 0); 579 if (ret) { 580 printf("Set AN loopback command failed on %s\n", ethdev->name); 581 return -1; 582 } 583 printf("AN loopback %s for %s\n", enable ? "set" : "clear", 584 ethdev->name); 585 586 return 0; 587} 588 589int cgx_intf_get_ignore(struct udevice *ethdev, int cgx, int lmac) 590{ 591 struct rvu_pf *rvu; 592 struct nix *nix; 593 union cgx_scratchx0 scr0; 594 int ret, cgx_id = cgx, lmac_id = lmac; 595 union cgx_cmd_s cmd; 596 597 if (ethdev) { 598 rvu = dev_get_priv(ethdev); 599 nix = rvu->nix; 600 cgx_id = nix->lmac->cgx->cgx_id; 601 lmac_id = nix->lmac->lmac_id; 602 } 603 cmd.cmd.id = CGX_CMD_GET_PERSIST_IGNORE; 604 605 ret = cgx_intf_req(cgx_id, lmac_id, cmd, &scr0.u, 1); 606 if (ret) { 607 if (ethdev) 608 printf("Get ignore command failed for %s\n", 609 ethdev->name); 610 else 611 printf("Get ignore command failed for CGX%d LMAC%d\n", 612 cgx_id, lmac_id); 613 return -1; 614 } 615 if (ethdev) 616 printf("Persist settings %signored for %s\n", 617 scr0.s.persist.ignore ? "" : "not ", ethdev->name); 618 else 619 printf("Persist settings %signored for CGX%d LMAC%d\n", 620 scr0.s.persist.ignore ? "" : "not ", cgx_id, lmac_id); 621 622 return 0; 623} 624 625int cgx_intf_set_ignore(struct udevice *ethdev, int cgx, int lmac, int ignore) 626{ 627 struct rvu_pf *rvu; 628 struct nix *nix; 629 union cgx_scratchx0 scr0; 630 int ret, cgx_id = cgx, lmac_id = lmac; 631 union cgx_cmd_s cmd; 632 633 if (ethdev) { 634 rvu = dev_get_priv(ethdev); 635 nix = rvu->nix; 636 cgx_id = nix->lmac->cgx->cgx_id; 637 lmac_id = nix->lmac->lmac_id; 638 } 639 cmd.cmd.id = CGX_CMD_SET_PERSIST_IGNORE; 640 cmd.persist_args.ignore = ignore; 641 642 ret = cgx_intf_req(cgx_id, lmac_id, cmd, &scr0.u, 0); 643 if (ret) { 644 if (ethdev) 645 printf("Set ignore command failed for %s\n", 646 ethdev->name); 647 else 648 printf("Set ignore command failed for CGX%d LMAC%d\n", 649 cgx_id, lmac_id); 650 return -1; 651 } 652 653 return 0; 654} 655 656int cgx_intf_set_macaddr(struct udevice *ethdev) 657{ 658 struct rvu_pf *rvu = dev_get_priv(ethdev); 659 struct nix *nix = rvu->nix; 660 union cgx_scratchx0 scr0; 661 int ret; 662 union cgx_cmd_s cmd; 663 u64 mac, tmp; 664 665 memcpy((void *)&tmp, nix->lmac->mac_addr, 6); 666 mac = swab64(tmp) >> 16; 667 cmd.cmd.id = CGX_CMD_SET_MAC_ADDR; 668 cmd.mac_args.addr = mac; 669 cmd.mac_args.pf_id = rvu->pfid; 670 671 ret = cgx_intf_req(nix->lmac->cgx->cgx_id, nix->lmac->lmac_id, 672 cmd, &scr0.u, 0); 673 if (ret) { 674 printf("Set user mac addr failed for %s\n", ethdev->name); 675 return -1; 676 } 677 678 return 0; 679} 680 681int cgx_intf_display_eye(u8 qlm, u8 lane) 682{ 683 union cgx_scratchx0 scr0; 684 int ret; 685 union cgx_cmd_s cmd; 686 687 cmd.cmd.id = CGX_CMD_DISPLAY_EYE; 688 689 cmd.dsp_eye_args.qlm = qlm; 690 cmd.dsp_eye_args.lane = lane; 691 692 ret = cgx_intf_req(0, 0, cmd, &scr0.u, 0); 693 if (ret) 694 return -1; 695 696 return 0; 697} 698 699int cgx_intf_display_serdes(u8 qlm, u8 lane) 700{ 701 union cgx_scratchx0 scr0; 702 int ret; 703 union cgx_cmd_s cmd; 704 705 cmd.cmd.id = CGX_CMD_DISPLAY_SERDES; 706 707 cmd.dsp_eye_args.qlm = qlm; 708 cmd.dsp_eye_args.lane = lane; 709 710 ret = cgx_intf_req(0, 0, cmd, &scr0.u, 0); 711 if (ret) 712 return -1; 713 714 return 0; 715} 716