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