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 | |
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>
-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; |