diff options
Diffstat (limited to 'drivers/net/dsa/bcm_sf2.c')
-rw-r--r-- | drivers/net/dsa/bcm_sf2.c | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c index 9d56515f4c4d..4f32b8a530bf 100644 --- a/drivers/net/dsa/bcm_sf2.c +++ b/drivers/net/dsa/bcm_sf2.c | |||
@@ -25,6 +25,8 @@ | |||
25 | #include <linux/ethtool.h> | 25 | #include <linux/ethtool.h> |
26 | #include <linux/if_bridge.h> | 26 | #include <linux/if_bridge.h> |
27 | #include <linux/brcmphy.h> | 27 | #include <linux/brcmphy.h> |
28 | #include <linux/etherdevice.h> | ||
29 | #include <net/switchdev.h> | ||
28 | 30 | ||
29 | #include "bcm_sf2.h" | 31 | #include "bcm_sf2.h" |
30 | #include "bcm_sf2_regs.h" | 32 | #include "bcm_sf2_regs.h" |
@@ -555,6 +557,236 @@ static int bcm_sf2_sw_br_set_stp_state(struct dsa_switch *ds, int port, | |||
555 | return 0; | 557 | return 0; |
556 | } | 558 | } |
557 | 559 | ||
560 | /* Address Resolution Logic routines */ | ||
561 | static int bcm_sf2_arl_op_wait(struct bcm_sf2_priv *priv) | ||
562 | { | ||
563 | unsigned int timeout = 10; | ||
564 | u32 reg; | ||
565 | |||
566 | do { | ||
567 | reg = core_readl(priv, CORE_ARLA_RWCTL); | ||
568 | if (!(reg & ARL_STRTDN)) | ||
569 | return 0; | ||
570 | |||
571 | usleep_range(1000, 2000); | ||
572 | } while (timeout--); | ||
573 | |||
574 | return -ETIMEDOUT; | ||
575 | } | ||
576 | |||
577 | static int bcm_sf2_arl_rw_op(struct bcm_sf2_priv *priv, unsigned int op) | ||
578 | { | ||
579 | u32 cmd; | ||
580 | |||
581 | if (op > ARL_RW) | ||
582 | return -EINVAL; | ||
583 | |||
584 | cmd = core_readl(priv, CORE_ARLA_RWCTL); | ||
585 | cmd &= ~IVL_SVL_SELECT; | ||
586 | cmd |= ARL_STRTDN; | ||
587 | if (op) | ||
588 | cmd |= ARL_RW; | ||
589 | else | ||
590 | cmd &= ~ARL_RW; | ||
591 | core_writel(priv, cmd, CORE_ARLA_RWCTL); | ||
592 | |||
593 | return bcm_sf2_arl_op_wait(priv); | ||
594 | } | ||
595 | |||
596 | static int bcm_sf2_arl_read(struct bcm_sf2_priv *priv, u64 mac, | ||
597 | u16 vid, struct bcm_sf2_arl_entry *ent, u8 *idx, | ||
598 | bool is_valid) | ||
599 | { | ||
600 | unsigned int i; | ||
601 | int ret; | ||
602 | |||
603 | ret = bcm_sf2_arl_op_wait(priv); | ||
604 | if (ret) | ||
605 | return ret; | ||
606 | |||
607 | /* Read the 4 bins */ | ||
608 | for (i = 0; i < 4; i++) { | ||
609 | u64 mac_vid; | ||
610 | u32 fwd_entry; | ||
611 | |||
612 | mac_vid = core_readq(priv, CORE_ARLA_MACVID_ENTRY(i)); | ||
613 | fwd_entry = core_readl(priv, CORE_ARLA_FWD_ENTRY(i)); | ||
614 | bcm_sf2_arl_to_entry(ent, mac_vid, fwd_entry); | ||
615 | |||
616 | if (ent->is_valid && is_valid) { | ||
617 | *idx = i; | ||
618 | return 0; | ||
619 | } | ||
620 | |||
621 | /* This is the MAC we just deleted */ | ||
622 | if (!is_valid && (mac_vid & mac)) | ||
623 | return 0; | ||
624 | } | ||
625 | |||
626 | return -ENOENT; | ||
627 | } | ||
628 | |||
629 | static int bcm_sf2_arl_op(struct bcm_sf2_priv *priv, int op, int port, | ||
630 | const unsigned char *addr, u16 vid, bool is_valid) | ||
631 | { | ||
632 | struct bcm_sf2_arl_entry ent; | ||
633 | u32 fwd_entry; | ||
634 | u64 mac, mac_vid = 0; | ||
635 | u8 idx = 0; | ||
636 | int ret; | ||
637 | |||
638 | /* Convert the array into a 64-bit MAC */ | ||
639 | mac = bcm_sf2_mac_to_u64(addr); | ||
640 | |||
641 | /* Perform a read for the given MAC and VID */ | ||
642 | core_writeq(priv, mac, CORE_ARLA_MAC); | ||
643 | core_writel(priv, vid, CORE_ARLA_VID); | ||
644 | |||
645 | /* Issue a read operation for this MAC */ | ||
646 | ret = bcm_sf2_arl_rw_op(priv, 1); | ||
647 | if (ret) | ||
648 | return ret; | ||
649 | |||
650 | ret = bcm_sf2_arl_read(priv, mac, vid, &ent, &idx, is_valid); | ||
651 | /* If this is a read, just finish now */ | ||
652 | if (op) | ||
653 | return ret; | ||
654 | |||
655 | /* We could not find a matching MAC, so reset to a new entry */ | ||
656 | if (ret) { | ||
657 | fwd_entry = 0; | ||
658 | idx = 0; | ||
659 | } | ||
660 | |||
661 | memset(&ent, 0, sizeof(ent)); | ||
662 | ent.port = port; | ||
663 | ent.is_valid = is_valid; | ||
664 | ent.vid = vid; | ||
665 | ent.is_static = true; | ||
666 | memcpy(ent.mac, addr, ETH_ALEN); | ||
667 | bcm_sf2_arl_from_entry(&mac_vid, &fwd_entry, &ent); | ||
668 | |||
669 | core_writeq(priv, mac_vid, CORE_ARLA_MACVID_ENTRY(idx)); | ||
670 | core_writel(priv, fwd_entry, CORE_ARLA_FWD_ENTRY(idx)); | ||
671 | |||
672 | ret = bcm_sf2_arl_rw_op(priv, 0); | ||
673 | if (ret) | ||
674 | return ret; | ||
675 | |||
676 | /* Re-read the entry to check */ | ||
677 | return bcm_sf2_arl_read(priv, mac, vid, &ent, &idx, is_valid); | ||
678 | } | ||
679 | |||
680 | static int bcm_sf2_sw_fdb_prepare(struct dsa_switch *ds, int port, | ||
681 | const struct switchdev_obj_port_fdb *fdb, | ||
682 | struct switchdev_trans *trans) | ||
683 | { | ||
684 | /* We do not need to do anything specific here yet */ | ||
685 | return 0; | ||
686 | } | ||
687 | |||
688 | static int bcm_sf2_sw_fdb_add(struct dsa_switch *ds, int port, | ||
689 | const struct switchdev_obj_port_fdb *fdb, | ||
690 | struct switchdev_trans *trans) | ||
691 | { | ||
692 | struct bcm_sf2_priv *priv = ds_to_priv(ds); | ||
693 | |||
694 | return bcm_sf2_arl_op(priv, 0, port, fdb->addr, fdb->vid, true); | ||
695 | } | ||
696 | |||
697 | static int bcm_sf2_sw_fdb_del(struct dsa_switch *ds, int port, | ||
698 | const struct switchdev_obj_port_fdb *fdb) | ||
699 | { | ||
700 | struct bcm_sf2_priv *priv = ds_to_priv(ds); | ||
701 | |||
702 | return bcm_sf2_arl_op(priv, 0, port, fdb->addr, fdb->vid, false); | ||
703 | } | ||
704 | |||
705 | static int bcm_sf2_arl_search_wait(struct bcm_sf2_priv *priv) | ||
706 | { | ||
707 | unsigned timeout = 1000; | ||
708 | u32 reg; | ||
709 | |||
710 | do { | ||
711 | reg = core_readl(priv, CORE_ARLA_SRCH_CTL); | ||
712 | if (!(reg & ARLA_SRCH_STDN)) | ||
713 | return 0; | ||
714 | |||
715 | if (reg & ARLA_SRCH_VLID) | ||
716 | return 0; | ||
717 | |||
718 | usleep_range(1000, 2000); | ||
719 | } while (timeout--); | ||
720 | |||
721 | return -ETIMEDOUT; | ||
722 | } | ||
723 | |||
724 | static void bcm_sf2_arl_search_rd(struct bcm_sf2_priv *priv, u8 idx, | ||
725 | struct bcm_sf2_arl_entry *ent) | ||
726 | { | ||
727 | u64 mac_vid; | ||
728 | u32 fwd_entry; | ||
729 | |||
730 | mac_vid = core_readq(priv, CORE_ARLA_SRCH_RSLT_MACVID(idx)); | ||
731 | fwd_entry = core_readl(priv, CORE_ARLA_SRCH_RSLT(idx)); | ||
732 | bcm_sf2_arl_to_entry(ent, mac_vid, fwd_entry); | ||
733 | } | ||
734 | |||
735 | static int bcm_sf2_sw_fdb_copy(struct net_device *dev, int port, | ||
736 | const struct bcm_sf2_arl_entry *ent, | ||
737 | struct switchdev_obj_port_fdb *fdb, | ||
738 | int (*cb)(struct switchdev_obj *obj)) | ||
739 | { | ||
740 | if (!ent->is_valid) | ||
741 | return 0; | ||
742 | |||
743 | if (port != ent->port) | ||
744 | return 0; | ||
745 | |||
746 | ether_addr_copy(fdb->addr, ent->mac); | ||
747 | fdb->vid = ent->vid; | ||
748 | fdb->ndm_state = ent->is_static ? NUD_NOARP : NUD_REACHABLE; | ||
749 | |||
750 | return cb(&fdb->obj); | ||
751 | } | ||
752 | |||
753 | static int bcm_sf2_sw_fdb_dump(struct dsa_switch *ds, int port, | ||
754 | struct switchdev_obj_port_fdb *fdb, | ||
755 | int (*cb)(struct switchdev_obj *obj)) | ||
756 | { | ||
757 | struct bcm_sf2_priv *priv = ds_to_priv(ds); | ||
758 | struct net_device *dev = ds->ports[port]; | ||
759 | struct bcm_sf2_arl_entry results[2]; | ||
760 | unsigned int count = 0; | ||
761 | int ret; | ||
762 | |||
763 | /* Start search operation */ | ||
764 | core_writel(priv, ARLA_SRCH_STDN, CORE_ARLA_SRCH_CTL); | ||
765 | |||
766 | do { | ||
767 | ret = bcm_sf2_arl_search_wait(priv); | ||
768 | if (ret) | ||
769 | return ret; | ||
770 | |||
771 | /* Read both entries, then return their values back */ | ||
772 | bcm_sf2_arl_search_rd(priv, 0, &results[0]); | ||
773 | ret = bcm_sf2_sw_fdb_copy(dev, port, &results[0], fdb, cb); | ||
774 | if (ret) | ||
775 | return ret; | ||
776 | |||
777 | bcm_sf2_arl_search_rd(priv, 1, &results[1]); | ||
778 | ret = bcm_sf2_sw_fdb_copy(dev, port, &results[1], fdb, cb); | ||
779 | if (ret) | ||
780 | return ret; | ||
781 | |||
782 | if (!results[0].is_valid && !results[1].is_valid) | ||
783 | break; | ||
784 | |||
785 | } while (count++ < CORE_ARLA_NUM_ENTRIES); | ||
786 | |||
787 | return 0; | ||
788 | } | ||
789 | |||
558 | static irqreturn_t bcm_sf2_switch_0_isr(int irq, void *dev_id) | 790 | static irqreturn_t bcm_sf2_switch_0_isr(int irq, void *dev_id) |
559 | { | 791 | { |
560 | struct bcm_sf2_priv *priv = dev_id; | 792 | struct bcm_sf2_priv *priv = dev_id; |
@@ -1076,6 +1308,10 @@ static struct dsa_switch_driver bcm_sf2_switch_driver = { | |||
1076 | .port_join_bridge = bcm_sf2_sw_br_join, | 1308 | .port_join_bridge = bcm_sf2_sw_br_join, |
1077 | .port_leave_bridge = bcm_sf2_sw_br_leave, | 1309 | .port_leave_bridge = bcm_sf2_sw_br_leave, |
1078 | .port_stp_update = bcm_sf2_sw_br_set_stp_state, | 1310 | .port_stp_update = bcm_sf2_sw_br_set_stp_state, |
1311 | .port_fdb_prepare = bcm_sf2_sw_fdb_prepare, | ||
1312 | .port_fdb_add = bcm_sf2_sw_fdb_add, | ||
1313 | .port_fdb_del = bcm_sf2_sw_fdb_del, | ||
1314 | .port_fdb_dump = bcm_sf2_sw_fdb_dump, | ||
1079 | }; | 1315 | }; |
1080 | 1316 | ||
1081 | static int __init bcm_sf2_init(void) | 1317 | static int __init bcm_sf2_init(void) |