|
4 | 4 | */
|
5 | 5 |
|
6 | 6 | #include "bcm-phy-lib.h"
|
| 7 | +#include <linux/bitfield.h> |
7 | 8 | #include <linux/brcmphy.h>
|
8 | 9 | #include <linux/export.h>
|
9 | 10 | #include <linux/mdio.h>
|
10 | 11 | #include <linux/module.h>
|
11 | 12 | #include <linux/phy.h>
|
12 | 13 | #include <linux/ethtool.h>
|
| 14 | +#include <linux/ethtool_netlink.h> |
13 | 15 |
|
14 | 16 | #define MII_BCM_CHANNEL_WIDTH 0x2000
|
15 | 17 | #define BCM_CL45VEN_EEE_ADV 0x3c
|
@@ -581,6 +583,193 @@ int bcm_phy_enable_jumbo(struct phy_device *phydev)
|
581 | 583 | }
|
582 | 584 | EXPORT_SYMBOL_GPL(bcm_phy_enable_jumbo);
|
583 | 585 |
|
| 586 | +int __bcm_phy_enable_rdb_access(struct phy_device *phydev) |
| 587 | +{ |
| 588 | + return __bcm_phy_write_exp(phydev, BCM54XX_EXP_REG7E, 0); |
| 589 | +} |
| 590 | +EXPORT_SYMBOL_GPL(__bcm_phy_enable_rdb_access); |
| 591 | + |
| 592 | +int __bcm_phy_enable_legacy_access(struct phy_device *phydev) |
| 593 | +{ |
| 594 | + return __bcm_phy_write_rdb(phydev, BCM54XX_RDB_REG0087, |
| 595 | + BCM54XX_ACCESS_MODE_LEGACY_EN); |
| 596 | +} |
| 597 | +EXPORT_SYMBOL_GPL(__bcm_phy_enable_legacy_access); |
| 598 | + |
| 599 | +static int _bcm_phy_cable_test_start(struct phy_device *phydev, bool is_rdb) |
| 600 | +{ |
| 601 | + u16 mask, set; |
| 602 | + int ret; |
| 603 | + |
| 604 | + /* Auto-negotiation must be enabled for cable diagnostics to work, but |
| 605 | + * don't advertise any capabilities. |
| 606 | + */ |
| 607 | + phy_write(phydev, MII_BMCR, BMCR_ANENABLE); |
| 608 | + phy_write(phydev, MII_ADVERTISE, ADVERTISE_CSMA); |
| 609 | + phy_write(phydev, MII_CTRL1000, 0); |
| 610 | + |
| 611 | + phy_lock_mdio_bus(phydev); |
| 612 | + if (is_rdb) { |
| 613 | + ret = __bcm_phy_enable_legacy_access(phydev); |
| 614 | + if (ret) |
| 615 | + goto out; |
| 616 | + } |
| 617 | + |
| 618 | + mask = BCM54XX_ECD_CTRL_CROSS_SHORT_DIS | BCM54XX_ECD_CTRL_UNIT_MASK; |
| 619 | + set = BCM54XX_ECD_CTRL_RUN | BCM54XX_ECD_CTRL_BREAK_LINK | |
| 620 | + FIELD_PREP(BCM54XX_ECD_CTRL_UNIT_MASK, |
| 621 | + BCM54XX_ECD_CTRL_UNIT_CM); |
| 622 | + |
| 623 | + ret = __bcm_phy_modify_exp(phydev, BCM54XX_EXP_ECD_CTRL, mask, set); |
| 624 | + |
| 625 | +out: |
| 626 | + /* re-enable the RDB access even if there was an error */ |
| 627 | + if (is_rdb) |
| 628 | + ret = __bcm_phy_enable_rdb_access(phydev) ? : ret; |
| 629 | + |
| 630 | + phy_unlock_mdio_bus(phydev); |
| 631 | + |
| 632 | + return ret; |
| 633 | +} |
| 634 | + |
| 635 | +static int bcm_phy_cable_test_report_trans(int result) |
| 636 | +{ |
| 637 | + switch (result) { |
| 638 | + case BCM54XX_ECD_FAULT_TYPE_OK: |
| 639 | + return ETHTOOL_A_CABLE_RESULT_CODE_OK; |
| 640 | + case BCM54XX_ECD_FAULT_TYPE_OPEN: |
| 641 | + return ETHTOOL_A_CABLE_RESULT_CODE_OPEN; |
| 642 | + case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT: |
| 643 | + return ETHTOOL_A_CABLE_RESULT_CODE_SAME_SHORT; |
| 644 | + case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT: |
| 645 | + return ETHTOOL_A_CABLE_RESULT_CODE_CROSS_SHORT; |
| 646 | + case BCM54XX_ECD_FAULT_TYPE_INVALID: |
| 647 | + case BCM54XX_ECD_FAULT_TYPE_BUSY: |
| 648 | + default: |
| 649 | + return ETHTOOL_A_CABLE_RESULT_CODE_UNSPEC; |
| 650 | + } |
| 651 | +} |
| 652 | + |
| 653 | +static bool bcm_phy_distance_valid(int result) |
| 654 | +{ |
| 655 | + switch (result) { |
| 656 | + case BCM54XX_ECD_FAULT_TYPE_OPEN: |
| 657 | + case BCM54XX_ECD_FAULT_TYPE_SAME_SHORT: |
| 658 | + case BCM54XX_ECD_FAULT_TYPE_CROSS_SHORT: |
| 659 | + return true; |
| 660 | + } |
| 661 | + return false; |
| 662 | +} |
| 663 | + |
| 664 | +static int bcm_phy_report_length(struct phy_device *phydev, int pair) |
| 665 | +{ |
| 666 | + int val; |
| 667 | + |
| 668 | + val = __bcm_phy_read_exp(phydev, |
| 669 | + BCM54XX_EXP_ECD_PAIR_A_LENGTH_RESULTS + pair); |
| 670 | + if (val < 0) |
| 671 | + return val; |
| 672 | + |
| 673 | + if (val == BCM54XX_ECD_LENGTH_RESULTS_INVALID) |
| 674 | + return 0; |
| 675 | + |
| 676 | + ethnl_cable_test_fault_length(phydev, pair, val); |
| 677 | + |
| 678 | + return 0; |
| 679 | +} |
| 680 | + |
| 681 | +static int _bcm_phy_cable_test_get_status(struct phy_device *phydev, |
| 682 | + bool *finished, bool is_rdb) |
| 683 | +{ |
| 684 | + int pair_a, pair_b, pair_c, pair_d, ret; |
| 685 | + |
| 686 | + *finished = false; |
| 687 | + |
| 688 | + phy_lock_mdio_bus(phydev); |
| 689 | + |
| 690 | + if (is_rdb) { |
| 691 | + ret = __bcm_phy_enable_legacy_access(phydev); |
| 692 | + if (ret) |
| 693 | + goto out; |
| 694 | + } |
| 695 | + |
| 696 | + ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_CTRL); |
| 697 | + if (ret < 0) |
| 698 | + goto out; |
| 699 | + |
| 700 | + if (ret & BCM54XX_ECD_CTRL_IN_PROGRESS) { |
| 701 | + ret = 0; |
| 702 | + goto out; |
| 703 | + } |
| 704 | + |
| 705 | + ret = __bcm_phy_read_exp(phydev, BCM54XX_EXP_ECD_FAULT_TYPE); |
| 706 | + if (ret < 0) |
| 707 | + goto out; |
| 708 | + |
| 709 | + pair_a = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_A_MASK, ret); |
| 710 | + pair_b = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_B_MASK, ret); |
| 711 | + pair_c = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_C_MASK, ret); |
| 712 | + pair_d = FIELD_GET(BCM54XX_ECD_FAULT_TYPE_PAIR_D_MASK, ret); |
| 713 | + |
| 714 | + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_A, |
| 715 | + bcm_phy_cable_test_report_trans(pair_a)); |
| 716 | + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_B, |
| 717 | + bcm_phy_cable_test_report_trans(pair_b)); |
| 718 | + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_C, |
| 719 | + bcm_phy_cable_test_report_trans(pair_c)); |
| 720 | + ethnl_cable_test_result(phydev, ETHTOOL_A_CABLE_PAIR_D, |
| 721 | + bcm_phy_cable_test_report_trans(pair_d)); |
| 722 | + |
| 723 | + if (bcm_phy_distance_valid(pair_a)) |
| 724 | + bcm_phy_report_length(phydev, 0); |
| 725 | + if (bcm_phy_distance_valid(pair_b)) |
| 726 | + bcm_phy_report_length(phydev, 1); |
| 727 | + if (bcm_phy_distance_valid(pair_c)) |
| 728 | + bcm_phy_report_length(phydev, 2); |
| 729 | + if (bcm_phy_distance_valid(pair_d)) |
| 730 | + bcm_phy_report_length(phydev, 3); |
| 731 | + |
| 732 | + ret = 0; |
| 733 | + *finished = true; |
| 734 | +out: |
| 735 | + /* re-enable the RDB access even if there was an error */ |
| 736 | + if (is_rdb) |
| 737 | + ret = __bcm_phy_enable_rdb_access(phydev) ? : ret; |
| 738 | + |
| 739 | + phy_unlock_mdio_bus(phydev); |
| 740 | + |
| 741 | + return ret; |
| 742 | +} |
| 743 | + |
| 744 | +int bcm_phy_cable_test_start(struct phy_device *phydev) |
| 745 | +{ |
| 746 | + return _bcm_phy_cable_test_start(phydev, false); |
| 747 | +} |
| 748 | +EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start); |
| 749 | + |
| 750 | +int bcm_phy_cable_test_get_status(struct phy_device *phydev, bool *finished) |
| 751 | +{ |
| 752 | + return _bcm_phy_cable_test_get_status(phydev, finished, false); |
| 753 | +} |
| 754 | +EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status); |
| 755 | + |
| 756 | +/* We assume that all PHYs which support RDB access can be switched to legacy |
| 757 | + * mode. If, in the future, this is not true anymore, we have to re-implement |
| 758 | + * this with RDB access. |
| 759 | + */ |
| 760 | +int bcm_phy_cable_test_start_rdb(struct phy_device *phydev) |
| 761 | +{ |
| 762 | + return _bcm_phy_cable_test_start(phydev, true); |
| 763 | +} |
| 764 | +EXPORT_SYMBOL_GPL(bcm_phy_cable_test_start_rdb); |
| 765 | + |
| 766 | +int bcm_phy_cable_test_get_status_rdb(struct phy_device *phydev, |
| 767 | + bool *finished) |
| 768 | +{ |
| 769 | + return _bcm_phy_cable_test_get_status(phydev, finished, true); |
| 770 | +} |
| 771 | +EXPORT_SYMBOL_GPL(bcm_phy_cable_test_get_status_rdb); |
| 772 | + |
584 | 773 | MODULE_DESCRIPTION("Broadcom PHY Library");
|
585 | 774 | MODULE_LICENSE("GPL v2");
|
586 | 775 | MODULE_AUTHOR("Broadcom Corporation");
|
0 commit comments