From e9e5c4b4576e8418cddb127f3659b061ed7af4ac Mon Sep 17 00:00:00 2001 From: ozaki-r Date: Wed, 26 Feb 2014 03:58:33 +0000 Subject: [PATCH] Make cpsw driver work without uboot support On some eval boards such as BeagleBone, the cpsw device is initialized rightly by the uboot of the boards so that the cpsw driver doesn't need to do some initializations but works fine. The patch adds initializations to make the driver work solely. It also adds support for 1000BaseT (RGMII) PHY that is equipped on some boards, e.g., CKB-3352. Reviewed by christos@ --- sys/arch/arm/omap/if_cpsw.c | 242 ++++++++++++++++++++++++++++++++- sys/arch/arm/omap/if_cpswreg.h | 81 +++++++++++ 2 files changed, 318 insertions(+), 5 deletions(-) diff --git a/sys/arch/arm/omap/if_cpsw.c b/sys/arch/arm/omap/if_cpsw.c index f556099f8287..ba9af7569830 100644 --- a/sys/arch/arm/omap/if_cpsw.c +++ b/sys/arch/arm/omap/if_cpsw.c @@ -1,4 +1,4 @@ -/* $NetBSD: if_cpsw.c,v 1.4 2013/12/18 12:53:26 skrll Exp $ */ +/* $NetBSD: if_cpsw.c,v 1.5 2014/02/26 03:58:33 ozaki-r Exp $ */ /* * Copyright (c) 2013 Jonathan A. Kollasch @@ -53,7 +53,7 @@ */ #include -__KERNEL_RCSID(1, "$NetBSD: if_cpsw.c,v 1.4 2013/12/18 12:53:26 skrll Exp $"); +__KERNEL_RCSID(1, "$NetBSD: if_cpsw.c,v 1.5 2014/02/26 03:58:33 ozaki-r Exp $"); #include #include @@ -122,6 +122,7 @@ struct cpsw_softc { bus_addr_t sc_rxdescs_pa; struct ethercom sc_ec; struct mii_data sc_mii; + bool sc_phy_has_1000t; callout_t sc_tick_ch; void *sc_ih; struct cpsw_ring_data *sc_rdp; @@ -163,6 +164,11 @@ static int cpsw_rxintr(void *); static int cpsw_txintr(void *); static int cpsw_miscintr(void *); +/* ALE support */ +#define CPSW_MAX_ALE_ENTRIES 1024 + +static int cpsw_ale_update_addresses(struct cpsw_softc *, int purge); + CFATTACH_DECL_NEW(cpsw, sizeof(struct cpsw_softc), cpsw_match, cpsw_attach, NULL, NULL); @@ -318,6 +324,18 @@ cpsw_match(device_t parent, cfdata_t cf, void *aux) return 0; } +static bool +cpsw_phy_has_1000t(struct cpsw_softc * const sc) +{ + struct ifmedia_entry *ifm; + + TAILQ_FOREACH(ifm, &sc->sc_mii.mii_media.ifm_list, ifm_list) { + if (IFM_SUBTYPE(ifm->ifm_media) == IFM_1000_T) + return true; + } + return false; +} + static void cpsw_attach(device_t parent, device_t self, void *aux) { @@ -469,13 +487,28 @@ cpsw_attach(device_t parent, device_t self, void *aux) sc->sc_ec.ec_mii = &sc->sc_mii; ifmedia_init(&sc->sc_mii.mii_media, 0, ether_mediachange, ether_mediastatus); + + /* Initialize MDIO */ + cpsw_write_4(sc, MDIOCONTROL, MDIOCTL_ENABLE | MDIOCTL_FAULTENB | MDIOCTL_CLKDIV(0xff)); + /* Clear ALE */ + cpsw_write_4(sc, CPSW_ALE_CONTROL, ALECTL_CLEAR_TABLE); + mii_attach(self, &sc->sc_mii, 0xffffffff, MII_PHY_ANY, 0, 0); if (LIST_FIRST(&sc->sc_mii.mii_phys) == NULL) { aprint_error_dev(self, "no PHY found!\n"); + sc->sc_phy_has_1000t = false; ifmedia_add(&sc->sc_mii.mii_media, IFM_ETHER|IFM_MANUAL, 0, NULL); ifmedia_set(&sc->sc_mii.mii_media, IFM_ETHER|IFM_MANUAL); } else { + sc->sc_phy_has_1000t = cpsw_phy_has_1000t(sc); + if (sc->sc_phy_has_1000t) { + aprint_normal_dev(sc->sc_dev, "1000baseT PHY found. setting RGMII Mode\n"); + /* Select the Interface RGMII Mode in the Control Module */ + sitara_cm_reg_write_4(CPSW_GMII_SEL, + GMIISEL_GMII2_SEL(RGMII_MODE) | GMIISEL_GMII1_SEL(RGMII_MODE)); + } + ifmedia_set(&sc->sc_mii.mii_media, IFM_ETHER|IFM_AUTO); } @@ -791,6 +824,8 @@ cpsw_init(struct ifnet *ifp) /* Reset and init Sliver port 1 and 2 */ for (i = 0; i < 2; i++) { + uint32_t macctl; + /* Reset */ cpsw_write_4(sc, CPSW_SL_SOFT_RESET(i), 1); while(cpsw_read_4(sc, CPSW_SL_SOFT_RESET(i)) & 1); @@ -805,9 +840,12 @@ cpsw_init(struct ifnet *ifp) cpsw_write_4(sc, CPSW_PORT_P_SA_LO(i+1), sc->sc_enaddr[4] | (sc->sc_enaddr[5] << 8)); - /* Set MACCONTROL for ports 0,1: FULLDUPLEX(1), GMII_EN(5), - IFCTL_A(15), IFCTL_B(16) FIXME */ - cpsw_write_4(sc, CPSW_SL_MACCONTROL(i), 1 | (1<<5) | (1<<15)); + /* Set MACCONTROL for ports 0,1 */ + macctl = SLMACCTL_FULLDUPLEX | SLMACCTL_GMII_EN | + SLMACCTL_IFCTL_A; + if (sc->sc_phy_has_1000t) + macctl |= SLMACCTL_GIG; + cpsw_write_4(sc, CPSW_SL_MACCONTROL(i), macctl); /* Set ALE port to forwarding(3) */ cpsw_write_4(sc, CPSW_ALE_PORTCTL(i+1), 3); @@ -820,6 +858,9 @@ cpsw_init(struct ifnet *ifp) /* Set ALE port to forwarding(3) */ cpsw_write_4(sc, CPSW_ALE_PORTCTL(0), 3); + /* Initialize addrs */ + cpsw_ale_update_addresses(sc, 1); + cpsw_write_4(sc, CPSW_SS_PTYPE, 0); cpsw_write_4(sc, CPSW_SS_STAT_PORT_EN, 7); @@ -1242,3 +1283,194 @@ cpsw_miscintr(void *arg) return 1; } + +/* + * + * ALE support routines. + * + */ + +static void +cpsw_ale_entry_init(uint32_t *ale_entry) +{ + ale_entry[0] = ale_entry[1] = ale_entry[2] = 0; +} + +static void +cpsw_ale_entry_set_mac(uint32_t *ale_entry, const uint8_t *mac) +{ + ale_entry[0] = mac[2] << 24 | mac[3] << 16 | mac[4] << 8 | mac[5]; + ale_entry[1] = mac[0] << 8 | mac[1]; +} + +static void +cpsw_ale_entry_set_bcast_mac(uint32_t *ale_entry) +{ + ale_entry[0] = 0xffffffff; + ale_entry[1] = 0x0000ffff; +} + +static void +cpsw_ale_entry_set(uint32_t *ale_entry, ale_entry_filed_t field, uint32_t val) +{ + /* Entry type[61:60] is addr entry(1), Mcast fwd state[63:62] is fw(3)*/ + switch (field) { + case ALE_ENTRY_TYPE: + /* [61:60] */ + ale_entry[1] |= (val & 0x3) << 28; + break; + case ALE_MCAST_FWD_STATE: + /* [63:62] */ + ale_entry[1] |= (val & 0x3) << 30; + break; + case ALE_PORT_MASK: + /* [68:66] */ + ale_entry[2] |= (val & 0x7) << 2; + break; + case ALE_PORT_NUMBER: + /* [67:66] */ + ale_entry[2] |= (val & 0x3) << 2; + break; + default: + panic("Invalid ALE entry field: %d\n", field); + } + + return; +} + +static bool +cpsw_ale_entry_mac_match(const uint32_t *ale_entry, const uint8_t *mac) +{ + return (((ale_entry[1] >> 8) & 0xff) == mac[0]) && + (((ale_entry[1] >> 0) & 0xff) == mac[1]) && + (((ale_entry[0] >>24) & 0xff) == mac[2]) && + (((ale_entry[0] >>16) & 0xff) == mac[3]) && + (((ale_entry[0] >> 8) & 0xff) == mac[4]) && + (((ale_entry[0] >> 0) & 0xff) == mac[5]); +} + +static void +cpsw_ale_set_outgoing_mac(struct cpsw_softc *sc, int port, const uint8_t *mac) +{ + cpsw_write_4(sc, CPSW_PORT_P_SA_HI(port), + mac[3] << 24 | mac[2] << 16 | mac[1] << 8 | mac[0]); + cpsw_write_4(sc, CPSW_PORT_P_SA_LO(port), + mac[5] << 8 | mac[4]); +} + +static void +cpsw_ale_read_entry(struct cpsw_softc *sc, uint16_t idx, uint32_t *ale_entry) +{ + cpsw_write_4(sc, CPSW_ALE_TBLCTL, idx & 1023); + ale_entry[0] = cpsw_read_4(sc, CPSW_ALE_TBLW0); + ale_entry[1] = cpsw_read_4(sc, CPSW_ALE_TBLW1); + ale_entry[2] = cpsw_read_4(sc, CPSW_ALE_TBLW2); +} + +static void +cpsw_ale_write_entry(struct cpsw_softc *sc, uint16_t idx, uint32_t *ale_entry) +{ + cpsw_write_4(sc, CPSW_ALE_TBLW0, ale_entry[0]); + cpsw_write_4(sc, CPSW_ALE_TBLW1, ale_entry[1]); + cpsw_write_4(sc, CPSW_ALE_TBLW2, ale_entry[2]); + cpsw_write_4(sc, CPSW_ALE_TBLCTL, 1 << 31 | (idx & 1023)); +} + +static int +cpsw_ale_remove_all_mc_entries(struct cpsw_softc *sc) +{ + int i; + uint32_t ale_entry[3]; + + /* First two entries are link address and broadcast. */ + for (i = 2; i < CPSW_MAX_ALE_ENTRIES; i++) { + cpsw_ale_read_entry(sc, i, ale_entry); + if (((ale_entry[1] >> 28) & 3) == 1 && /* Address entry */ + ((ale_entry[1] >> 8) & 1) == 1) { /* MCast link addr */ + ale_entry[0] = ale_entry[1] = ale_entry[2] = 0; + cpsw_ale_write_entry(sc, i, ale_entry); + } + } + return CPSW_MAX_ALE_ENTRIES; +} + +static int +cpsw_ale_mc_entry_set(struct cpsw_softc *sc, uint8_t portmask, uint8_t *mac) +{ + int free_index = -1, matching_index = -1, i; + uint32_t ale_entry[3]; + + /* Find a matching entry or a free entry. */ + for (i = 0; i < CPSW_MAX_ALE_ENTRIES; i++) { + cpsw_ale_read_entry(sc, i, ale_entry); + + /* Entry Type[61:60] is 0 for free entry */ + if (free_index < 0 && ((ale_entry[1] >> 28) & 3) == 0) { + free_index = i; + } + + if (cpsw_ale_entry_mac_match(ale_entry, mac)) { + matching_index = i; + break; + } + } + + if (matching_index < 0) { + if (free_index < 0) + return ENOMEM; + i = free_index; + } + + cpsw_ale_entry_init(ale_entry); + + cpsw_ale_entry_set_mac(ale_entry, mac); + cpsw_ale_entry_set(ale_entry, ALE_ENTRY_TYPE, ALE_TYPE_ADDRESS); + cpsw_ale_entry_set(ale_entry, ALE_MCAST_FWD_STATE, ALE_FWSTATE_FWONLY); + cpsw_ale_entry_set(ale_entry, ALE_PORT_MASK, portmask); + + cpsw_ale_write_entry(sc, i, ale_entry); + + return 0; +} + +static int +cpsw_ale_update_addresses(struct cpsw_softc *sc, int purge) +{ + uint8_t *mac = sc->sc_enaddr; + uint32_t ale_entry[3]; + int i; + struct ethercom * const ec = &sc->sc_ec; + struct ether_multi *ifma; + + cpsw_ale_entry_init(ale_entry); + /* Route incoming packets for our MAC address to Port 0 (host). */ + /* For simplicity, keep this entry at table index 0 in the ALE. */ + cpsw_ale_entry_set_mac(ale_entry, mac); + cpsw_ale_entry_set(ale_entry, ALE_ENTRY_TYPE, ALE_TYPE_ADDRESS); + cpsw_ale_entry_set(ale_entry, ALE_PORT_NUMBER, 0); + cpsw_ale_write_entry(sc, 0, ale_entry); + + /* Set outgoing MAC Address for Ports 1 and 2. */ + for (i = 1; i < 3; ++i) + cpsw_ale_set_outgoing_mac(sc, i, mac); + + /* Keep the broadcast address at table entry 1. */ + cpsw_ale_entry_init(ale_entry); + cpsw_ale_entry_set_bcast_mac(ale_entry); + cpsw_ale_entry_set(ale_entry, ALE_ENTRY_TYPE, ALE_TYPE_ADDRESS); + cpsw_ale_entry_set(ale_entry, ALE_MCAST_FWD_STATE, ALE_FWSTATE_FWONLY); + cpsw_ale_entry_set(ale_entry, ALE_PORT_MASK, ALE_PORT_MASK_ALL); + cpsw_ale_write_entry(sc, 1, ale_entry); + + /* SIOCDELMULTI doesn't specify the particular address + being removed, so we have to remove all and rebuild. */ + if (purge) + cpsw_ale_remove_all_mc_entries(sc); + + /* Set other multicast addrs desired. */ + LIST_FOREACH(ifma, &ec->ec_multiaddrs, enm_list) { + cpsw_ale_mc_entry_set(sc, ALE_PORT_MASK_ALL, ifma->enm_addrlo); + } + + return 0; +} diff --git a/sys/arch/arm/omap/if_cpswreg.h b/sys/arch/arm/omap/if_cpswreg.h index 282c2dfe7b88..31999d51731d 100644 --- a/sys/arch/arm/omap/if_cpswreg.h +++ b/sys/arch/arm/omap/if_cpswreg.h @@ -34,6 +34,7 @@ #define CPSW_SS_SOFT_RESET (CPSW_SS_OFFSET + 0x08) #define CPSW_SS_STAT_PORT_EN (CPSW_SS_OFFSET + 0x0C) #define CPSW_SS_PTYPE (CPSW_SS_OFFSET + 0x10) +#define CPSW_SS_RGMII_CTL (CPSW_SS_OFFSET + 0x88) #define CPSW_PORT_OFFSET 0x0100 #define CPSW_PORT_P_TX_PRI_MAP(p) (CPSW_PORT_OFFSET + 0x118 + ((p-1) * 0x100)) @@ -42,6 +43,8 @@ #define CPSW_PORT_P_SA_LO(p) (CPSW_PORT_OFFSET + 0x120 + ((p-1) * 0x100)) #define CPSW_PORT_P_SA_HI(p) (CPSW_PORT_OFFSET + 0x124 + ((p-1) * 0x100)) +#define CPSW_GMII_SEL 0x0650 + #define CPSW_CPDMA_OFFSET 0x0800 #define CPSW_CPDMA_TX_CONTROL (CPSW_CPDMA_OFFSET + 0x04) #define CPSW_CPDMA_TX_TEARDOWN (CPSW_CPDMA_OFFSET + 0x08) @@ -133,4 +136,82 @@ struct cpsw_cpdma_bd { #define CPSW_INTROFF_TX 2 #define CPSW_INTROFF_MISC 3 +/* MDIOCONTROL Register Field */ +#define MDIOCTL_IDLE __BIT32(31) +#define MDIOCTL_ENABLE __BIT32(30) +#define MDIOCTL_HIGHEST_USER_CHANNEL(val) ((0xf & (val)) << 24) +#define MDIOCTL_PREAMBLE __BIT32(20) +#define MDIOCTL_FAULT __BIT32(19) +#define MDIOCTL_FAULTENB __BIT32(18) +#define MDIOCTL_INTTESTENB __BIT32(17) +#define MDIOCTL_CLKDIV(val) (0xff & (val)) + +/* ALE Control Register Field */ +#define ALECTL_ENABLE_ALE __BIT32(31) +#define ALECTL_CLEAR_TABLE __BIT32(30) +#define ALECTL_AGE_OUT_NOW __BIT32(29) +#define ALECTL_EN_P0_UNI_FLOOD __BIT32(8) +#define ALECTL_LEARN_NO_VID __BIT32(7) +#define ALECTL_EN_VID0_MODE __BIT32(6) +#define ALECTL_ENABLE_OUI_DENY __BIT32(5) +#define ALECTL_BYPASS __BIT32(4) +#define ALECTL_RATE_LIMIT_TX __BIT32(3) +#define ALECTL_VLAN_AWARE __BIT32(2) +#define ALECTL_ENABLE_AUTH_MODE __BIT32(1) +#define ALECTL_ENABLE_RATE_LIMIT __BIT32(0) + +/* GMII_SEL Register Field */ +#define GMIISEL_RMII2_IO_CLK_EN __BIT32(7) +#define GMIISEL_RMII1_IO_CLK_EN __BIT32(6) +#define GMIISEL_RGMII2_IDMODE __BIT32(5) +#define GMIISEL_RGMII1_IDMODE __BIT32(4) +#define GMIISEL_GMII2_SEL(val) ((0x3 & (val)) << 2) +#define GMIISEL_GMII1_SEL(val) ((0x3 & (val)) << 0) +#define GMII_MODE 0 +#define RMII_MODE 1 +#define RGMII_MODE 2 + +/* Sliver MACCONTROL Register Field */ +#define SLMACCTL_RX_CMF_EN __BIT32(24) +#define SLMACCTL_RX_CSF_EN __BIT32(23) +#define SLMACCTL_RX_CEF_EN __BIT32(22) +#define SLMACCTL_TX_SHORT_GAP_LIM_EN __BIT32(21) +#define SLMACCTL_EXT_EN __BIT32(18) +#define SLMACCTL_GIG_FORCE __BIT32(17) +#define SLMACCTL_IFCTL_B __BIT32(16) +#define SLMACCTL_IFCTL_A __BIT32(15) +#define SLMACCTL_CMD_IDLE __BIT32(11) +#define SLMACCTL_TX_SHORT_GAP_EN __BIT32(10) +#define SLMACCTL_GIG __BIT32(7) +#define SLMACCTL_TX_PACE __BIT32(6) +#define SLMACCTL_GMII_EN __BIT32(5) +#define SLMACCTL_TX_FLOW_EN __BIT32(4) +#define SLMACCTL_RX_FLOW_EN __BIT32(3) +#define SLMACCTL_MTEST __BIT32(2) +#define SLMACCTL_LOOPBACK __BIT32(1) +#define SLMACCTL_FULLDUPLEX __BIT32(0) + +/* ALE Address Table Entry Field */ +typedef enum { + ALE_ENTRY_TYPE, + ALE_MCAST_FWD_STATE, + ALE_PORT_MASK, + ALE_PORT_NUMBER, +} ale_entry_filed_t; + +#define ALE_TYPE_FREE 0 +#define ALE_TYPE_ADDRESS 1 +#define ALE_TYPE_VLAN 2 +#define ALE_TYPE_VLAN_ADDRESS 3 + +/* + * The port state(s) required for the received port on a destination address lookup + * in order for the multicast packet to be forwarded to the transmit port(s) + */ +#define ALE_FWSTATE_ALL 1 /* Blocking/Forwarding/Learning */ +#define ALE_FWSTATE_NOBLOCK 2 /* Forwarding/Learning */ +#define ALE_FWSTATE_FWONLY 3 /* Forwarding */ + +#define ALE_PORT_MASK_ALL 7 + #endif /*_IF_CPSWREG_H */