|
1 | 1 | // SPDX-License-Identifier: GPL-2.0
|
2 | 2 | /* Microchip LAN937X switch driver main logic
|
3 |
| - * Copyright (C) 2019-2022 Microchip Technology Inc. |
| 3 | + * Copyright (C) 2019-2024 Microchip Technology Inc. |
4 | 4 | */
|
5 | 5 | #include <linux/kernel.h>
|
6 | 6 | #include <linux/module.h>
|
@@ -461,10 +461,66 @@ int lan937x_change_mtu(struct ksz_device *dev, int port, int new_mtu)
|
461 | 461 |
|
462 | 462 | int lan937x_set_ageing_time(struct ksz_device *dev, unsigned int msecs)
|
463 | 463 | {
|
464 |
| - u32 secs = msecs / 1000; |
465 |
| - u32 value; |
| 464 | + u8 data, mult, value8; |
| 465 | + bool in_msec = false; |
| 466 | + u32 max_val, value; |
| 467 | + u32 secs = msecs; |
466 | 468 | int ret;
|
467 | 469 |
|
| 470 | +#define MAX_TIMER_VAL ((1 << 20) - 1) |
| 471 | + |
| 472 | + /* The aging timer comprises a 3-bit multiplier and a 20-bit second |
| 473 | + * value. Either of them cannot be zero. The maximum timer is then |
| 474 | + * 7 * 1048575 = 7340025 seconds. As this value is too large for |
| 475 | + * practical use it can be interpreted as microseconds, making the |
| 476 | + * maximum timer 7340 seconds with finer control. This allows for |
| 477 | + * maximum 122 minutes compared to 29 minutes in KSZ9477 switch. |
| 478 | + */ |
| 479 | + if (msecs % 1000) |
| 480 | + in_msec = true; |
| 481 | + else |
| 482 | + secs /= 1000; |
| 483 | + if (!secs) |
| 484 | + secs = 1; |
| 485 | + |
| 486 | + /* Return error if too large. */ |
| 487 | + else if (secs > 7 * MAX_TIMER_VAL) |
| 488 | + return -EINVAL; |
| 489 | + |
| 490 | + /* Configure how to interpret the number value. */ |
| 491 | + ret = ksz_rmw8(dev, REG_SW_LUE_CTRL_2, SW_AGE_CNT_IN_MICROSEC, |
| 492 | + in_msec ? SW_AGE_CNT_IN_MICROSEC : 0); |
| 493 | + if (ret < 0) |
| 494 | + return ret; |
| 495 | + |
| 496 | + ret = ksz_read8(dev, REG_SW_LUE_CTRL_0, &value8); |
| 497 | + if (ret < 0) |
| 498 | + return ret; |
| 499 | + |
| 500 | + /* Check whether there is need to update the multiplier. */ |
| 501 | + mult = FIELD_GET(SW_AGE_CNT_M, value8); |
| 502 | + max_val = MAX_TIMER_VAL; |
| 503 | + if (mult > 0) { |
| 504 | + /* Try to use the same multiplier already in the register as |
| 505 | + * the hardware default uses multiplier 4 and 75 seconds for |
| 506 | + * 300 seconds. |
| 507 | + */ |
| 508 | + max_val = DIV_ROUND_UP(secs, mult); |
| 509 | + if (max_val > MAX_TIMER_VAL || max_val * mult != secs) |
| 510 | + max_val = MAX_TIMER_VAL; |
| 511 | + } |
| 512 | + |
| 513 | + data = DIV_ROUND_UP(secs, max_val); |
| 514 | + if (mult != data) { |
| 515 | + value8 &= ~SW_AGE_CNT_M; |
| 516 | + value8 |= FIELD_PREP(SW_AGE_CNT_M, data); |
| 517 | + ret = ksz_write8(dev, REG_SW_LUE_CTRL_0, value8); |
| 518 | + if (ret < 0) |
| 519 | + return ret; |
| 520 | + } |
| 521 | + |
| 522 | + secs = DIV_ROUND_UP(secs, data); |
| 523 | + |
468 | 524 | value = FIELD_GET(SW_AGE_PERIOD_7_0_M, secs);
|
469 | 525 |
|
470 | 526 | ret = ksz_write8(dev, REG_SW_AGE_PERIOD__1, value);
|
|
0 commit comments