diff options
| author | Guenter Roeck <linux@roeck-us.net> | 2015-03-26 21:36:35 -0400 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2015-03-29 16:23:53 -0400 |
| commit | facd95b2e0ec02ccf6d13f4d08c060628dca0862 (patch) | |
| tree | 114bf401b08530583b16839c1c22f9fe220fa4de /drivers/net/dsa | |
| parent | b0019b70d02bae9757b5b9237f38fb8cf2899009 (diff) | |
net: dsa: mv88e6xxx: Add Hardware bridging support
Bridge support is similar for all chips supported by the mv88e6xxx code,
so add the code there.
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Tested-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/dsa')
| -rw-r--r-- | drivers/net/dsa/mv88e6xxx.c | 271 | ||||
| -rw-r--r-- | drivers/net/dsa/mv88e6xxx.h | 28 |
2 files changed, 292 insertions, 7 deletions
diff --git a/drivers/net/dsa/mv88e6xxx.c b/drivers/net/dsa/mv88e6xxx.c index d8f13327a438..17aa74f64233 100644 --- a/drivers/net/dsa/mv88e6xxx.c +++ b/drivers/net/dsa/mv88e6xxx.c | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | */ | 9 | */ |
| 10 | 10 | ||
| 11 | #include <linux/delay.h> | 11 | #include <linux/delay.h> |
| 12 | #include <linux/if_bridge.h> | ||
| 12 | #include <linux/jiffies.h> | 13 | #include <linux/jiffies.h> |
| 13 | #include <linux/list.h> | 14 | #include <linux/list.h> |
| 14 | #include <linux/module.h> | 15 | #include <linux/module.h> |
| @@ -644,6 +645,31 @@ int mv88e6xxx_eeprom_busy_wait(struct dsa_switch *ds) | |||
| 644 | return mv88e6xxx_wait(ds, REG_GLOBAL2, 0x14, 0x8000); | 645 | return mv88e6xxx_wait(ds, REG_GLOBAL2, 0x14, 0x8000); |
| 645 | } | 646 | } |
| 646 | 647 | ||
| 648 | /* Must be called with SMI lock held */ | ||
| 649 | static int _mv88e6xxx_wait(struct dsa_switch *ds, int reg, int offset, u16 mask) | ||
| 650 | { | ||
| 651 | unsigned long timeout = jiffies + HZ / 10; | ||
| 652 | |||
| 653 | while (time_before(jiffies, timeout)) { | ||
| 654 | int ret; | ||
| 655 | |||
| 656 | ret = _mv88e6xxx_reg_read(ds, reg, offset); | ||
| 657 | if (ret < 0) | ||
| 658 | return ret; | ||
| 659 | if (!(ret & mask)) | ||
| 660 | return 0; | ||
| 661 | |||
| 662 | usleep_range(1000, 2000); | ||
| 663 | } | ||
| 664 | return -ETIMEDOUT; | ||
| 665 | } | ||
| 666 | |||
| 667 | /* Must be called with SMI lock held */ | ||
| 668 | static int _mv88e6xxx_atu_wait(struct dsa_switch *ds) | ||
| 669 | { | ||
| 670 | return _mv88e6xxx_wait(ds, REG_GLOBAL, 0x0b, ATU_BUSY); | ||
| 671 | } | ||
| 672 | |||
| 647 | int mv88e6xxx_phy_read_indirect(struct dsa_switch *ds, int addr, int regnum) | 673 | int mv88e6xxx_phy_read_indirect(struct dsa_switch *ds, int addr, int regnum) |
| 648 | { | 674 | { |
| 649 | int ret; | 675 | int ret; |
| @@ -717,10 +743,236 @@ int mv88e6xxx_set_eee(struct dsa_switch *ds, int port, | |||
| 717 | return 0; | 743 | return 0; |
| 718 | } | 744 | } |
| 719 | 745 | ||
| 746 | static int _mv88e6xxx_atu_cmd(struct dsa_switch *ds, int fid, u16 cmd) | ||
| 747 | { | ||
| 748 | int ret; | ||
| 749 | |||
| 750 | ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, 0x01, fid); | ||
| 751 | if (ret < 0) | ||
| 752 | return ret; | ||
| 753 | |||
| 754 | ret = _mv88e6xxx_reg_write(ds, REG_GLOBAL, 0x0b, cmd); | ||
| 755 | if (ret < 0) | ||
| 756 | return ret; | ||
| 757 | |||
| 758 | return _mv88e6xxx_atu_wait(ds); | ||
| 759 | } | ||
| 760 | |||
| 761 | static int _mv88e6xxx_flush_fid(struct dsa_switch *ds, int fid) | ||
| 762 | { | ||
| 763 | int ret; | ||
| 764 | |||
| 765 | ret = _mv88e6xxx_atu_wait(ds); | ||
| 766 | if (ret < 0) | ||
| 767 | return ret; | ||
| 768 | |||
| 769 | return _mv88e6xxx_atu_cmd(ds, fid, ATU_CMD_FLUSH_NONSTATIC_FID); | ||
| 770 | } | ||
| 771 | |||
| 772 | static int mv88e6xxx_set_port_state(struct dsa_switch *ds, int port, u8 state) | ||
| 773 | { | ||
| 774 | struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); | ||
| 775 | int reg, ret; | ||
| 776 | u8 oldstate; | ||
| 777 | |||
| 778 | mutex_lock(&ps->smi_mutex); | ||
| 779 | |||
| 780 | reg = _mv88e6xxx_reg_read(ds, REG_PORT(port), 0x04); | ||
| 781 | if (reg < 0) | ||
| 782 | goto abort; | ||
| 783 | |||
| 784 | oldstate = reg & PSTATE_MASK; | ||
| 785 | if (oldstate != state) { | ||
| 786 | /* Flush forwarding database if we're moving a port | ||
| 787 | * from Learning or Forwarding state to Disabled or | ||
| 788 | * Blocking or Listening state. | ||
| 789 | */ | ||
| 790 | if (oldstate >= PSTATE_LEARNING && state <= PSTATE_BLOCKING) { | ||
| 791 | ret = _mv88e6xxx_flush_fid(ds, ps->fid[port]); | ||
| 792 | if (ret) | ||
| 793 | goto abort; | ||
| 794 | } | ||
| 795 | reg = (reg & ~PSTATE_MASK) | state; | ||
| 796 | ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), 0x04, reg); | ||
| 797 | } | ||
| 798 | |||
| 799 | abort: | ||
| 800 | mutex_unlock(&ps->smi_mutex); | ||
| 801 | return ret; | ||
| 802 | } | ||
| 803 | |||
| 804 | /* Must be called with smi lock held */ | ||
| 805 | static int _mv88e6xxx_update_port_config(struct dsa_switch *ds, int port) | ||
| 806 | { | ||
| 807 | struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); | ||
| 808 | u8 fid = ps->fid[port]; | ||
| 809 | u16 reg = fid << 12; | ||
| 810 | |||
| 811 | if (dsa_is_cpu_port(ds, port)) | ||
| 812 | reg |= ds->phys_port_mask; | ||
| 813 | else | ||
| 814 | reg |= (ps->bridge_mask[fid] | | ||
| 815 | (1 << dsa_upstream_port(ds))) & ~(1 << port); | ||
| 816 | |||
| 817 | return _mv88e6xxx_reg_write(ds, REG_PORT(port), 0x06, reg); | ||
| 818 | } | ||
| 819 | |||
| 820 | /* Must be called with smi lock held */ | ||
| 821 | static int _mv88e6xxx_update_bridge_config(struct dsa_switch *ds, int fid) | ||
| 822 | { | ||
| 823 | struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); | ||
| 824 | int port; | ||
| 825 | u32 mask; | ||
| 826 | int ret; | ||
| 827 | |||
| 828 | mask = ds->phys_port_mask; | ||
| 829 | while (mask) { | ||
| 830 | port = __ffs(mask); | ||
| 831 | mask &= ~(1 << port); | ||
| 832 | if (ps->fid[port] != fid) | ||
| 833 | continue; | ||
| 834 | |||
| 835 | ret = _mv88e6xxx_update_port_config(ds, port); | ||
| 836 | if (ret) | ||
| 837 | return ret; | ||
| 838 | } | ||
| 839 | |||
| 840 | return _mv88e6xxx_flush_fid(ds, fid); | ||
| 841 | } | ||
| 842 | |||
| 843 | /* Bridge handling functions */ | ||
| 844 | |||
| 845 | int mv88e6xxx_join_bridge(struct dsa_switch *ds, int port, u32 br_port_mask) | ||
| 846 | { | ||
| 847 | struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); | ||
| 848 | int ret = 0; | ||
| 849 | u32 nmask; | ||
| 850 | int fid; | ||
| 851 | |||
| 852 | /* If the bridge group is not empty, join that group. | ||
| 853 | * Otherwise create a new group. | ||
| 854 | */ | ||
| 855 | fid = ps->fid[port]; | ||
| 856 | nmask = br_port_mask & ~(1 << port); | ||
| 857 | if (nmask) | ||
| 858 | fid = ps->fid[__ffs(nmask)]; | ||
| 859 | |||
| 860 | nmask = ps->bridge_mask[fid] | (1 << port); | ||
| 861 | if (nmask != br_port_mask) { | ||
| 862 | netdev_err(ds->ports[port], | ||
| 863 | "join: Bridge port mask mismatch fid=%d mask=0x%x expected 0x%x\n", | ||
| 864 | fid, br_port_mask, nmask); | ||
| 865 | return -EINVAL; | ||
| 866 | } | ||
| 867 | |||
| 868 | mutex_lock(&ps->smi_mutex); | ||
| 869 | |||
| 870 | ps->bridge_mask[fid] = br_port_mask; | ||
| 871 | |||
| 872 | if (fid != ps->fid[port]) { | ||
| 873 | ps->fid_mask |= 1 << ps->fid[port]; | ||
| 874 | ps->fid[port] = fid; | ||
| 875 | ret = _mv88e6xxx_update_bridge_config(ds, fid); | ||
| 876 | } | ||
| 877 | |||
| 878 | mutex_unlock(&ps->smi_mutex); | ||
| 879 | |||
| 880 | return ret; | ||
| 881 | } | ||
| 882 | |||
| 883 | int mv88e6xxx_leave_bridge(struct dsa_switch *ds, int port, u32 br_port_mask) | ||
| 884 | { | ||
| 885 | struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); | ||
| 886 | u8 fid, newfid; | ||
| 887 | int ret; | ||
| 888 | |||
| 889 | fid = ps->fid[port]; | ||
| 890 | |||
| 891 | if (ps->bridge_mask[fid] != br_port_mask) { | ||
| 892 | netdev_err(ds->ports[port], | ||
| 893 | "leave: Bridge port mask mismatch fid=%d mask=0x%x expected 0x%x\n", | ||
| 894 | fid, br_port_mask, ps->bridge_mask[fid]); | ||
| 895 | return -EINVAL; | ||
| 896 | } | ||
| 897 | |||
| 898 | /* If the port was the last port of a bridge, we are done. | ||
| 899 | * Otherwise assign a new fid to the port, and fix up | ||
| 900 | * the bridge configuration. | ||
| 901 | */ | ||
| 902 | if (br_port_mask == (1 << port)) | ||
| 903 | return 0; | ||
| 904 | |||
| 905 | mutex_lock(&ps->smi_mutex); | ||
| 906 | |||
| 907 | newfid = __ffs(ps->fid_mask); | ||
| 908 | ps->fid[port] = newfid; | ||
| 909 | ps->fid_mask &= (1 << newfid); | ||
| 910 | ps->bridge_mask[fid] &= ~(1 << port); | ||
| 911 | ps->bridge_mask[newfid] = 1 << port; | ||
| 912 | |||
| 913 | ret = _mv88e6xxx_update_bridge_config(ds, fid); | ||
| 914 | if (!ret) | ||
| 915 | ret = _mv88e6xxx_update_bridge_config(ds, newfid); | ||
| 916 | |||
| 917 | mutex_unlock(&ps->smi_mutex); | ||
| 918 | |||
| 919 | return ret; | ||
| 920 | } | ||
| 921 | |||
| 922 | int mv88e6xxx_port_stp_update(struct dsa_switch *ds, int port, u8 state) | ||
| 923 | { | ||
| 924 | struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); | ||
| 925 | int stp_state; | ||
| 926 | |||
| 927 | switch (state) { | ||
| 928 | case BR_STATE_DISABLED: | ||
| 929 | stp_state = PSTATE_DISABLED; | ||
| 930 | break; | ||
| 931 | case BR_STATE_BLOCKING: | ||
| 932 | case BR_STATE_LISTENING: | ||
| 933 | stp_state = PSTATE_BLOCKING; | ||
| 934 | break; | ||
| 935 | case BR_STATE_LEARNING: | ||
| 936 | stp_state = PSTATE_LEARNING; | ||
| 937 | break; | ||
| 938 | case BR_STATE_FORWARDING: | ||
| 939 | default: | ||
| 940 | stp_state = PSTATE_FORWARDING; | ||
| 941 | break; | ||
| 942 | } | ||
| 943 | |||
| 944 | netdev_dbg(ds->ports[port], "port state %d [%d]\n", state, stp_state); | ||
| 945 | |||
| 946 | /* mv88e6xxx_port_stp_update may be called with softirqs disabled, | ||
| 947 | * so we can not update the port state directly but need to schedule it. | ||
| 948 | */ | ||
| 949 | ps->port_state[port] = stp_state; | ||
| 950 | set_bit(port, &ps->port_state_update_mask); | ||
| 951 | schedule_work(&ps->bridge_work); | ||
| 952 | |||
| 953 | return 0; | ||
| 954 | } | ||
| 955 | |||
| 956 | static void mv88e6xxx_bridge_work(struct work_struct *work) | ||
| 957 | { | ||
| 958 | struct mv88e6xxx_priv_state *ps; | ||
| 959 | struct dsa_switch *ds; | ||
| 960 | int port; | ||
| 961 | |||
| 962 | ps = container_of(work, struct mv88e6xxx_priv_state, bridge_work); | ||
| 963 | ds = ((struct dsa_switch *)ps) - 1; | ||
| 964 | |||
| 965 | while (ps->port_state_update_mask) { | ||
| 966 | port = __ffs(ps->port_state_update_mask); | ||
| 967 | clear_bit(port, &ps->port_state_update_mask); | ||
| 968 | mv88e6xxx_set_port_state(ds, port, ps->port_state[port]); | ||
| 969 | } | ||
| 970 | } | ||
| 971 | |||
| 720 | int mv88e6xxx_setup_port_common(struct dsa_switch *ds, int port) | 972 | int mv88e6xxx_setup_port_common(struct dsa_switch *ds, int port) |
| 721 | { | 973 | { |
| 722 | struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); | 974 | struct mv88e6xxx_priv_state *ps = ds_to_priv(ds); |
| 723 | int ret, reg; | 975 | int ret, fid; |
| 724 | 976 | ||
| 725 | mutex_lock(&ps->smi_mutex); | 977 | mutex_lock(&ps->smi_mutex); |
| 726 | 978 | ||
| @@ -736,13 +988,14 @@ int mv88e6xxx_setup_port_common(struct dsa_switch *ds, int port) | |||
| 736 | * ports, and allow each of the 'real' ports to only talk to | 988 | * ports, and allow each of the 'real' ports to only talk to |
| 737 | * the upstream port. | 989 | * the upstream port. |
| 738 | */ | 990 | */ |
| 739 | reg = (port & 0xf) << 12; | 991 | fid = __ffs(ps->fid_mask); |
| 740 | if (dsa_is_cpu_port(ds, port)) | 992 | ps->fid[port] = fid; |
| 741 | reg |= ds->phys_port_mask; | 993 | ps->fid_mask &= ~(1 << fid); |
| 742 | else | 994 | |
| 743 | reg |= 1 << dsa_upstream_port(ds); | 995 | if (!dsa_is_cpu_port(ds, port)) |
| 996 | ps->bridge_mask[fid] = 1 << port; | ||
| 744 | 997 | ||
| 745 | ret = _mv88e6xxx_reg_write(ds, REG_PORT(port), 0x06, reg); | 998 | ret = _mv88e6xxx_update_port_config(ds, port); |
| 746 | if (ret) | 999 | if (ret) |
| 747 | goto abort; | 1000 | goto abort; |
| 748 | 1001 | ||
| @@ -763,6 +1016,10 @@ int mv88e6xxx_setup_common(struct dsa_switch *ds) | |||
| 763 | mutex_init(&ps->stats_mutex); | 1016 | mutex_init(&ps->stats_mutex); |
| 764 | mutex_init(&ps->phy_mutex); | 1017 | mutex_init(&ps->phy_mutex); |
| 765 | 1018 | ||
| 1019 | ps->fid_mask = (1 << DSA_MAX_PORTS) - 1; | ||
| 1020 | |||
| 1021 | INIT_WORK(&ps->bridge_work, mv88e6xxx_bridge_work); | ||
| 1022 | |||
| 766 | return 0; | 1023 | return 0; |
| 767 | } | 1024 | } |
| 768 | 1025 | ||
diff --git a/drivers/net/dsa/mv88e6xxx.h b/drivers/net/dsa/mv88e6xxx.h index a4df4968594e..8e215ebc8d34 100644 --- a/drivers/net/dsa/mv88e6xxx.h +++ b/drivers/net/dsa/mv88e6xxx.h | |||
| @@ -15,6 +15,20 @@ | |||
| 15 | #define REG_GLOBAL 0x1b | 15 | #define REG_GLOBAL 0x1b |
| 16 | #define REG_GLOBAL2 0x1c | 16 | #define REG_GLOBAL2 0x1c |
| 17 | 17 | ||
| 18 | /* ATU commands */ | ||
| 19 | |||
| 20 | #define ATU_BUSY 0x8000 | ||
| 21 | |||
| 22 | #define ATU_CMD_FLUSH_NONSTATIC_FID (ATU_BUSY | 0x6000) | ||
| 23 | |||
| 24 | /* port states */ | ||
| 25 | |||
| 26 | #define PSTATE_MASK 0x03 | ||
| 27 | #define PSTATE_DISABLED 0x00 | ||
| 28 | #define PSTATE_BLOCKING 0x01 | ||
| 29 | #define PSTATE_LEARNING 0x02 | ||
| 30 | #define PSTATE_FORWARDING 0x03 | ||
| 31 | |||
| 18 | struct mv88e6xxx_priv_state { | 32 | struct mv88e6xxx_priv_state { |
| 19 | /* When using multi-chip addressing, this mutex protects | 33 | /* When using multi-chip addressing, this mutex protects |
| 20 | * access to the indirect access registers. (In single-chip | 34 | * access to the indirect access registers. (In single-chip |
| @@ -49,6 +63,17 @@ struct mv88e6xxx_priv_state { | |||
| 49 | struct mutex eeprom_mutex; | 63 | struct mutex eeprom_mutex; |
| 50 | 64 | ||
| 51 | int id; /* switch product id */ | 65 | int id; /* switch product id */ |
| 66 | |||
| 67 | /* hw bridging */ | ||
| 68 | |||
| 69 | u32 fid_mask; | ||
| 70 | u8 fid[DSA_MAX_PORTS]; | ||
| 71 | u16 bridge_mask[DSA_MAX_PORTS]; | ||
| 72 | |||
| 73 | unsigned long port_state_update_mask; | ||
| 74 | u8 port_state[DSA_MAX_PORTS]; | ||
| 75 | |||
| 76 | struct work_struct bridge_work; | ||
| 52 | }; | 77 | }; |
| 53 | 78 | ||
| 54 | struct mv88e6xxx_hw_stat { | 79 | struct mv88e6xxx_hw_stat { |
| @@ -93,6 +118,9 @@ int mv88e6xxx_phy_write_indirect(struct dsa_switch *ds, int addr, int regnum, | |||
| 93 | int mv88e6xxx_get_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e); | 118 | int mv88e6xxx_get_eee(struct dsa_switch *ds, int port, struct ethtool_eee *e); |
| 94 | int mv88e6xxx_set_eee(struct dsa_switch *ds, int port, | 119 | int mv88e6xxx_set_eee(struct dsa_switch *ds, int port, |
| 95 | struct phy_device *phydev, struct ethtool_eee *e); | 120 | struct phy_device *phydev, struct ethtool_eee *e); |
| 121 | int mv88e6xxx_join_bridge(struct dsa_switch *ds, int port, u32 br_port_mask); | ||
| 122 | int mv88e6xxx_leave_bridge(struct dsa_switch *ds, int port, u32 br_port_mask); | ||
| 123 | int mv88e6xxx_port_stp_update(struct dsa_switch *ds, int port, u8 state); | ||
| 96 | 124 | ||
| 97 | extern struct dsa_switch_driver mv88e6131_switch_driver; | 125 | extern struct dsa_switch_driver mv88e6131_switch_driver; |
| 98 | extern struct dsa_switch_driver mv88e6123_61_65_switch_driver; | 126 | extern struct dsa_switch_driver mv88e6123_61_65_switch_driver; |
