dwmac5.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. // SPDX-License-Identifier: (GPL-2.0 OR MIT)
  2. // Copyright (c) 2017 Synopsys, Inc. and/or its affiliates.
  3. // stmmac Support for 5.xx Ethernet QoS cores
  4. #include <linux/bitops.h>
  5. #include <linux/iopoll.h>
  6. #include "common.h"
  7. #include "dwmac4.h"
  8. #include "dwmac5.h"
  9. #include "stmmac.h"
  10. #include "stmmac_ptp.h"
  11. struct dwmac5_error_desc {
  12. bool valid;
  13. const char *desc;
  14. const char *detailed_desc;
  15. };
  16. #define STAT_OFF(field) offsetof(struct stmmac_safety_stats, field)
  17. static void dwmac5_log_error(struct net_device *ndev, u32 value, bool corr,
  18. const char *module_name, const struct dwmac5_error_desc *desc,
  19. unsigned long field_offset, struct stmmac_safety_stats *stats)
  20. {
  21. unsigned long loc, mask;
  22. u8 *bptr = (u8 *)stats;
  23. unsigned long *ptr;
  24. ptr = (unsigned long *)(bptr + field_offset);
  25. mask = value;
  26. for_each_set_bit(loc, &mask, 32) {
  27. netdev_err(ndev, "Found %s error in %s: '%s: %s'\n", corr ?
  28. "correctable" : "uncorrectable", module_name,
  29. desc[loc].desc, desc[loc].detailed_desc);
  30. /* Update counters */
  31. ptr[loc]++;
  32. }
  33. }
  34. static const struct dwmac5_error_desc dwmac5_mac_errors[32]= {
  35. { true, "ATPES", "Application Transmit Interface Parity Check Error" },
  36. { true, "TPES", "TSO Data Path Parity Check Error" },
  37. { true, "RDPES", "Read Descriptor Parity Check Error" },
  38. { true, "MPES", "MTL Data Path Parity Check Error" },
  39. { true, "MTSPES", "MTL TX Status Data Path Parity Check Error" },
  40. { true, "ARPES", "Application Receive Interface Data Path Parity Check Error" },
  41. { true, "CWPES", "CSR Write Data Path Parity Check Error" },
  42. { true, "ASRPES", "AXI Slave Read Data Path Parity Check Error" },
  43. { true, "TTES", "TX FSM Timeout Error" },
  44. { true, "RTES", "RX FSM Timeout Error" },
  45. { true, "CTES", "CSR FSM Timeout Error" },
  46. { true, "ATES", "APP FSM Timeout Error" },
  47. { true, "PTES", "PTP FSM Timeout Error" },
  48. { true, "T125ES", "TX125 FSM Timeout Error" },
  49. { true, "R125ES", "RX125 FSM Timeout Error" },
  50. { true, "RVCTES", "REV MDC FSM Timeout Error" },
  51. { true, "MSTTES", "Master Read/Write Timeout Error" },
  52. { true, "SLVTES", "Slave Read/Write Timeout Error" },
  53. { true, "ATITES", "Application Timeout on ATI Interface Error" },
  54. { true, "ARITES", "Application Timeout on ARI Interface Error" },
  55. { false, "UNKNOWN", "Unknown Error" }, /* 20 */
  56. { false, "UNKNOWN", "Unknown Error" }, /* 21 */
  57. { false, "UNKNOWN", "Unknown Error" }, /* 22 */
  58. { false, "UNKNOWN", "Unknown Error" }, /* 23 */
  59. { true, "FSMPES", "FSM State Parity Error" },
  60. { false, "UNKNOWN", "Unknown Error" }, /* 25 */
  61. { false, "UNKNOWN", "Unknown Error" }, /* 26 */
  62. { false, "UNKNOWN", "Unknown Error" }, /* 27 */
  63. { false, "UNKNOWN", "Unknown Error" }, /* 28 */
  64. { false, "UNKNOWN", "Unknown Error" }, /* 29 */
  65. { false, "UNKNOWN", "Unknown Error" }, /* 30 */
  66. { false, "UNKNOWN", "Unknown Error" }, /* 31 */
  67. };
  68. static void dwmac5_handle_mac_err(struct net_device *ndev,
  69. void __iomem *ioaddr, bool correctable,
  70. struct stmmac_safety_stats *stats)
  71. {
  72. u32 value;
  73. value = readl(ioaddr + MAC_DPP_FSM_INT_STATUS);
  74. writel(value, ioaddr + MAC_DPP_FSM_INT_STATUS);
  75. dwmac5_log_error(ndev, value, correctable, "MAC", dwmac5_mac_errors,
  76. STAT_OFF(mac_errors), stats);
  77. }
  78. static const struct dwmac5_error_desc dwmac5_mtl_errors[32]= {
  79. { true, "TXCES", "MTL TX Memory Error" },
  80. { true, "TXAMS", "MTL TX Memory Address Mismatch Error" },
  81. { true, "TXUES", "MTL TX Memory Error" },
  82. { false, "UNKNOWN", "Unknown Error" }, /* 3 */
  83. { true, "RXCES", "MTL RX Memory Error" },
  84. { true, "RXAMS", "MTL RX Memory Address Mismatch Error" },
  85. { true, "RXUES", "MTL RX Memory Error" },
  86. { false, "UNKNOWN", "Unknown Error" }, /* 7 */
  87. { true, "ECES", "MTL EST Memory Error" },
  88. { true, "EAMS", "MTL EST Memory Address Mismatch Error" },
  89. { true, "EUES", "MTL EST Memory Error" },
  90. { false, "UNKNOWN", "Unknown Error" }, /* 11 */
  91. { true, "RPCES", "MTL RX Parser Memory Error" },
  92. { true, "RPAMS", "MTL RX Parser Memory Address Mismatch Error" },
  93. { true, "RPUES", "MTL RX Parser Memory Error" },
  94. { false, "UNKNOWN", "Unknown Error" }, /* 15 */
  95. { false, "UNKNOWN", "Unknown Error" }, /* 16 */
  96. { false, "UNKNOWN", "Unknown Error" }, /* 17 */
  97. { false, "UNKNOWN", "Unknown Error" }, /* 18 */
  98. { false, "UNKNOWN", "Unknown Error" }, /* 19 */
  99. { false, "UNKNOWN", "Unknown Error" }, /* 20 */
  100. { false, "UNKNOWN", "Unknown Error" }, /* 21 */
  101. { false, "UNKNOWN", "Unknown Error" }, /* 22 */
  102. { false, "UNKNOWN", "Unknown Error" }, /* 23 */
  103. { false, "UNKNOWN", "Unknown Error" }, /* 24 */
  104. { false, "UNKNOWN", "Unknown Error" }, /* 25 */
  105. { false, "UNKNOWN", "Unknown Error" }, /* 26 */
  106. { false, "UNKNOWN", "Unknown Error" }, /* 27 */
  107. { false, "UNKNOWN", "Unknown Error" }, /* 28 */
  108. { false, "UNKNOWN", "Unknown Error" }, /* 29 */
  109. { false, "UNKNOWN", "Unknown Error" }, /* 30 */
  110. { false, "UNKNOWN", "Unknown Error" }, /* 31 */
  111. };
  112. static void dwmac5_handle_mtl_err(struct net_device *ndev,
  113. void __iomem *ioaddr, bool correctable,
  114. struct stmmac_safety_stats *stats)
  115. {
  116. u32 value;
  117. value = readl(ioaddr + MTL_ECC_INT_STATUS);
  118. writel(value, ioaddr + MTL_ECC_INT_STATUS);
  119. dwmac5_log_error(ndev, value, correctable, "MTL", dwmac5_mtl_errors,
  120. STAT_OFF(mtl_errors), stats);
  121. }
  122. static const struct dwmac5_error_desc dwmac5_dma_errors[32]= {
  123. { true, "TCES", "DMA TSO Memory Error" },
  124. { true, "TAMS", "DMA TSO Memory Address Mismatch Error" },
  125. { true, "TUES", "DMA TSO Memory Error" },
  126. { false, "UNKNOWN", "Unknown Error" }, /* 3 */
  127. { false, "UNKNOWN", "Unknown Error" }, /* 4 */
  128. { false, "UNKNOWN", "Unknown Error" }, /* 5 */
  129. { false, "UNKNOWN", "Unknown Error" }, /* 6 */
  130. { false, "UNKNOWN", "Unknown Error" }, /* 7 */
  131. { false, "UNKNOWN", "Unknown Error" }, /* 8 */
  132. { false, "UNKNOWN", "Unknown Error" }, /* 9 */
  133. { false, "UNKNOWN", "Unknown Error" }, /* 10 */
  134. { false, "UNKNOWN", "Unknown Error" }, /* 11 */
  135. { false, "UNKNOWN", "Unknown Error" }, /* 12 */
  136. { false, "UNKNOWN", "Unknown Error" }, /* 13 */
  137. { false, "UNKNOWN", "Unknown Error" }, /* 14 */
  138. { false, "UNKNOWN", "Unknown Error" }, /* 15 */
  139. { false, "UNKNOWN", "Unknown Error" }, /* 16 */
  140. { false, "UNKNOWN", "Unknown Error" }, /* 17 */
  141. { false, "UNKNOWN", "Unknown Error" }, /* 18 */
  142. { false, "UNKNOWN", "Unknown Error" }, /* 19 */
  143. { false, "UNKNOWN", "Unknown Error" }, /* 20 */
  144. { false, "UNKNOWN", "Unknown Error" }, /* 21 */
  145. { false, "UNKNOWN", "Unknown Error" }, /* 22 */
  146. { false, "UNKNOWN", "Unknown Error" }, /* 23 */
  147. { false, "UNKNOWN", "Unknown Error" }, /* 24 */
  148. { false, "UNKNOWN", "Unknown Error" }, /* 25 */
  149. { false, "UNKNOWN", "Unknown Error" }, /* 26 */
  150. { false, "UNKNOWN", "Unknown Error" }, /* 27 */
  151. { false, "UNKNOWN", "Unknown Error" }, /* 28 */
  152. { false, "UNKNOWN", "Unknown Error" }, /* 29 */
  153. { false, "UNKNOWN", "Unknown Error" }, /* 30 */
  154. { false, "UNKNOWN", "Unknown Error" }, /* 31 */
  155. };
  156. static void dwmac5_handle_dma_err(struct net_device *ndev,
  157. void __iomem *ioaddr, bool correctable,
  158. struct stmmac_safety_stats *stats)
  159. {
  160. u32 value;
  161. value = readl(ioaddr + DMA_ECC_INT_STATUS);
  162. writel(value, ioaddr + DMA_ECC_INT_STATUS);
  163. dwmac5_log_error(ndev, value, correctable, "DMA", dwmac5_dma_errors,
  164. STAT_OFF(dma_errors), stats);
  165. }
  166. int dwmac5_safety_feat_config(void __iomem *ioaddr, unsigned int asp)
  167. {
  168. u32 value;
  169. if (!asp)
  170. return -EINVAL;
  171. /* 1. Enable Safety Features */
  172. value = readl(ioaddr + MTL_ECC_CONTROL);
  173. value |= TSOEE; /* TSO ECC */
  174. value |= MRXPEE; /* MTL RX Parser ECC */
  175. value |= MESTEE; /* MTL EST ECC */
  176. value |= MRXEE; /* MTL RX FIFO ECC */
  177. value |= MTXEE; /* MTL TX FIFO ECC */
  178. writel(value, ioaddr + MTL_ECC_CONTROL);
  179. /* 2. Enable MTL Safety Interrupts */
  180. value = readl(ioaddr + MTL_ECC_INT_ENABLE);
  181. value |= RPCEIE; /* RX Parser Memory Correctable Error */
  182. value |= ECEIE; /* EST Memory Correctable Error */
  183. value |= RXCEIE; /* RX Memory Correctable Error */
  184. value |= TXCEIE; /* TX Memory Correctable Error */
  185. writel(value, ioaddr + MTL_ECC_INT_ENABLE);
  186. /* 3. Enable DMA Safety Interrupts */
  187. value = readl(ioaddr + DMA_ECC_INT_ENABLE);
  188. value |= TCEIE; /* TSO Memory Correctable Error */
  189. writel(value, ioaddr + DMA_ECC_INT_ENABLE);
  190. /* Only ECC Protection for External Memory feature is selected */
  191. if (asp <= 0x1)
  192. return 0;
  193. /* 5. Enable Parity and Timeout for FSM */
  194. value = readl(ioaddr + MAC_FSM_CONTROL);
  195. value |= PRTYEN; /* FSM Parity Feature */
  196. value |= TMOUTEN; /* FSM Timeout Feature */
  197. writel(value, ioaddr + MAC_FSM_CONTROL);
  198. /* 4. Enable Data Parity Protection */
  199. value = readl(ioaddr + MTL_DPP_CONTROL);
  200. value |= EDPP;
  201. writel(value, ioaddr + MTL_DPP_CONTROL);
  202. /*
  203. * All the Automotive Safety features are selected without the "Parity
  204. * Port Enable for external interface" feature.
  205. */
  206. if (asp <= 0x2)
  207. return 0;
  208. value |= EPSI;
  209. writel(value, ioaddr + MTL_DPP_CONTROL);
  210. return 0;
  211. }
  212. int dwmac5_safety_feat_irq_status(struct net_device *ndev,
  213. void __iomem *ioaddr, unsigned int asp,
  214. struct stmmac_safety_stats *stats)
  215. {
  216. bool err, corr;
  217. u32 mtl, dma;
  218. int ret = 0;
  219. if (!asp)
  220. return -EINVAL;
  221. mtl = readl(ioaddr + MTL_SAFETY_INT_STATUS);
  222. dma = readl(ioaddr + DMA_SAFETY_INT_STATUS);
  223. err = (mtl & MCSIS) || (dma & MCSIS);
  224. corr = false;
  225. if (err) {
  226. dwmac5_handle_mac_err(ndev, ioaddr, corr, stats);
  227. ret |= !corr;
  228. }
  229. err = (mtl & (MEUIS | MECIS)) || (dma & (MSUIS | MSCIS));
  230. corr = (mtl & MECIS) || (dma & MSCIS);
  231. if (err) {
  232. dwmac5_handle_mtl_err(ndev, ioaddr, corr, stats);
  233. ret |= !corr;
  234. }
  235. err = dma & (DEUIS | DECIS);
  236. corr = dma & DECIS;
  237. if (err) {
  238. dwmac5_handle_dma_err(ndev, ioaddr, corr, stats);
  239. ret |= !corr;
  240. }
  241. return ret;
  242. }
  243. static const struct dwmac5_error {
  244. const struct dwmac5_error_desc *desc;
  245. } dwmac5_all_errors[] = {
  246. { dwmac5_mac_errors },
  247. { dwmac5_mtl_errors },
  248. { dwmac5_dma_errors },
  249. };
  250. int dwmac5_safety_feat_dump(struct stmmac_safety_stats *stats,
  251. int index, unsigned long *count, const char **desc)
  252. {
  253. int module = index / 32, offset = index % 32;
  254. unsigned long *ptr = (unsigned long *)stats;
  255. if (module >= ARRAY_SIZE(dwmac5_all_errors))
  256. return -EINVAL;
  257. if (!dwmac5_all_errors[module].desc[offset].valid)
  258. return -EINVAL;
  259. if (count)
  260. *count = *(ptr + index);
  261. if (desc)
  262. *desc = dwmac5_all_errors[module].desc[offset].desc;
  263. return 0;
  264. }
  265. static int dwmac5_rxp_disable(void __iomem *ioaddr)
  266. {
  267. u32 val;
  268. int ret;
  269. val = readl(ioaddr + MTL_OPERATION_MODE);
  270. val &= ~MTL_FRPE;
  271. writel(val, ioaddr + MTL_OPERATION_MODE);
  272. ret = readl_poll_timeout(ioaddr + MTL_RXP_CONTROL_STATUS, val,
  273. val & RXPI, 1, 10000);
  274. if (ret)
  275. return ret;
  276. return 0;
  277. }
  278. static void dwmac5_rxp_enable(void __iomem *ioaddr)
  279. {
  280. u32 val;
  281. val = readl(ioaddr + MTL_OPERATION_MODE);
  282. val |= MTL_FRPE;
  283. writel(val, ioaddr + MTL_OPERATION_MODE);
  284. }
  285. static int dwmac5_rxp_update_single_entry(void __iomem *ioaddr,
  286. struct stmmac_tc_entry *entry,
  287. int pos)
  288. {
  289. int ret, i;
  290. for (i = 0; i < (sizeof(entry->val) / sizeof(u32)); i++) {
  291. int real_pos = pos * (sizeof(entry->val) / sizeof(u32)) + i;
  292. u32 val;
  293. /* Wait for ready */
  294. ret = readl_poll_timeout(ioaddr + MTL_RXP_IACC_CTRL_STATUS,
  295. val, !(val & STARTBUSY), 1, 10000);
  296. if (ret)
  297. return ret;
  298. /* Write data */
  299. val = *((u32 *)&entry->val + i);
  300. writel(val, ioaddr + MTL_RXP_IACC_DATA);
  301. /* Write pos */
  302. val = real_pos & ADDR;
  303. writel(val, ioaddr + MTL_RXP_IACC_CTRL_STATUS);
  304. /* Write OP */
  305. val |= WRRDN;
  306. writel(val, ioaddr + MTL_RXP_IACC_CTRL_STATUS);
  307. /* Start Write */
  308. val |= STARTBUSY;
  309. writel(val, ioaddr + MTL_RXP_IACC_CTRL_STATUS);
  310. /* Wait for done */
  311. ret = readl_poll_timeout(ioaddr + MTL_RXP_IACC_CTRL_STATUS,
  312. val, !(val & STARTBUSY), 1, 10000);
  313. if (ret)
  314. return ret;
  315. }
  316. return 0;
  317. }
  318. static struct stmmac_tc_entry *
  319. dwmac5_rxp_get_next_entry(struct stmmac_tc_entry *entries, unsigned int count,
  320. u32 curr_prio)
  321. {
  322. struct stmmac_tc_entry *entry;
  323. u32 min_prio = ~0x0;
  324. int i, min_prio_idx;
  325. bool found = false;
  326. for (i = count - 1; i >= 0; i--) {
  327. entry = &entries[i];
  328. /* Do not update unused entries */
  329. if (!entry->in_use)
  330. continue;
  331. /* Do not update already updated entries (i.e. fragments) */
  332. if (entry->in_hw)
  333. continue;
  334. /* Let last entry be updated last */
  335. if (entry->is_last)
  336. continue;
  337. /* Do not return fragments */
  338. if (entry->is_frag)
  339. continue;
  340. /* Check if we already checked this prio */
  341. if (entry->prio < curr_prio)
  342. continue;
  343. /* Check if this is the minimum prio */
  344. if (entry->prio < min_prio) {
  345. min_prio = entry->prio;
  346. min_prio_idx = i;
  347. found = true;
  348. }
  349. }
  350. if (found)
  351. return &entries[min_prio_idx];
  352. return NULL;
  353. }
  354. int dwmac5_rxp_config(void __iomem *ioaddr, struct stmmac_tc_entry *entries,
  355. unsigned int count)
  356. {
  357. struct stmmac_tc_entry *entry, *frag;
  358. int i, ret, nve = 0;
  359. u32 curr_prio = 0;
  360. u32 old_val, val;
  361. /* Force disable RX */
  362. old_val = readl(ioaddr + GMAC_CONFIG);
  363. val = old_val & ~GMAC_CONFIG_RE;
  364. writel(val, ioaddr + GMAC_CONFIG);
  365. /* Disable RX Parser */
  366. ret = dwmac5_rxp_disable(ioaddr);
  367. if (ret)
  368. goto re_enable;
  369. /* Set all entries as NOT in HW */
  370. for (i = 0; i < count; i++) {
  371. entry = &entries[i];
  372. entry->in_hw = false;
  373. }
  374. /* Update entries by reverse order */
  375. while (1) {
  376. entry = dwmac5_rxp_get_next_entry(entries, count, curr_prio);
  377. if (!entry)
  378. break;
  379. curr_prio = entry->prio;
  380. frag = entry->frag_ptr;
  381. /* Set special fragment requirements */
  382. if (frag) {
  383. entry->val.af = 0;
  384. entry->val.rf = 0;
  385. entry->val.nc = 1;
  386. entry->val.ok_index = nve + 2;
  387. }
  388. ret = dwmac5_rxp_update_single_entry(ioaddr, entry, nve);
  389. if (ret)
  390. goto re_enable;
  391. entry->table_pos = nve++;
  392. entry->in_hw = true;
  393. if (frag && !frag->in_hw) {
  394. ret = dwmac5_rxp_update_single_entry(ioaddr, frag, nve);
  395. if (ret)
  396. goto re_enable;
  397. frag->table_pos = nve++;
  398. frag->in_hw = true;
  399. }
  400. }
  401. if (!nve)
  402. goto re_enable;
  403. /* Update all pass entry */
  404. for (i = 0; i < count; i++) {
  405. entry = &entries[i];
  406. if (!entry->is_last)
  407. continue;
  408. ret = dwmac5_rxp_update_single_entry(ioaddr, entry, nve);
  409. if (ret)
  410. goto re_enable;
  411. entry->table_pos = nve++;
  412. }
  413. /* Assume n. of parsable entries == n. of valid entries */
  414. val = (nve << 16) & NPE;
  415. val |= nve & NVE;
  416. writel(val, ioaddr + MTL_RXP_CONTROL_STATUS);
  417. /* Enable RX Parser */
  418. dwmac5_rxp_enable(ioaddr);
  419. re_enable:
  420. /* Re-enable RX */
  421. writel(old_val, ioaddr + GMAC_CONFIG);
  422. return ret;
  423. }
  424. int dwmac5_flex_pps_config(void __iomem *ioaddr, int index,
  425. struct stmmac_pps_cfg *cfg, bool enable,
  426. u32 sub_second_inc, u32 systime_flags)
  427. {
  428. u32 tnsec = readl(ioaddr + MAC_PPSx_TARGET_TIME_NSEC(index));
  429. u32 val = readl(ioaddr + MAC_PPS_CONTROL);
  430. u64 period;
  431. if (!cfg->available)
  432. return -EINVAL;
  433. if (tnsec & TRGTBUSY0)
  434. return -EBUSY;
  435. if (!sub_second_inc || !systime_flags)
  436. return -EINVAL;
  437. val &= ~PPSx_MASK(index);
  438. if (!enable) {
  439. val |= PPSCMDx(index, 0x5);
  440. writel(val, ioaddr + MAC_PPS_CONTROL);
  441. return 0;
  442. }
  443. val |= PPSCMDx(index, 0x2);
  444. val |= TRGTMODSELx(index, 0x2);
  445. val |= PPSEN0;
  446. writel(cfg->start.tv_sec, ioaddr + MAC_PPSx_TARGET_TIME_SEC(index));
  447. if (!(systime_flags & PTP_TCR_TSCTRLSSR))
  448. cfg->start.tv_nsec = (cfg->start.tv_nsec * 1000) / 465;
  449. writel(cfg->start.tv_nsec, ioaddr + MAC_PPSx_TARGET_TIME_NSEC(index));
  450. period = cfg->period.tv_sec * 1000000000;
  451. period += cfg->period.tv_nsec;
  452. do_div(period, sub_second_inc);
  453. if (period <= 1)
  454. return -EINVAL;
  455. writel(period - 1, ioaddr + MAC_PPSx_INTERVAL(index));
  456. period >>= 1;
  457. if (period <= 1)
  458. return -EINVAL;
  459. writel(period - 1, ioaddr + MAC_PPSx_WIDTH(index));
  460. /* Finally, activate it */
  461. writel(val, ioaddr + MAC_PPS_CONTROL);
  462. return 0;
  463. }