# Base git commit: 29b4817d4018 # (Linux 4.8-rc1) # # Author: Russell King (Mon 29 Feb 11:30:52 GMT 2016) # Committer: Russell King (Mon 8 Aug 15:59:52 BST 2016) # # PCI: mvebu: extend PCIe reset duration # # Some PCIe cards (such as ASM1062 based SATA cards) appear to need a # slightly longer delay between reset being released and the first card # access. Increase the default delay from 20ms to 30ms. # # Signed-off-by: Russell King # # 52d3df49cd0f49eb0cbd284ac525dbe9c94f7192 # drivers/pci/host/pci-mvebu.c | 2 +- # 1 file changed, 1 insertion(+), 1 deletion(-) # # Author: Russell King (Mon 14 Sep 10:14:57 BST 2015) # Committer: Russell King (Mon 8 Aug 15:59:51 BST 2016) # # sfp: removal of defs # # dac7d2d5840bb7358fc1988a2ea62186f80cbd63 # include/linux/sfp.h | 32 -------------------------------- # 1 file changed, 32 deletions(-) # # Author: Russell King (Thu 10 Sep 15:01:44 BST 2015) # Committer: Russell King (Mon 8 Aug 15:59:50 BST 2016) # # implement slot capabilities (SSPL) # # 995fd7ed2340b924c63ba17ffee98acdef14ccfc # drivers/pci/host/pci-mvebu.c | 22 ++++++++++++++++++++-- # 1 file changed, 20 insertions(+), 2 deletions(-) # # Author: Russell King (Sun 6 Sep 23:45:17 BST 2015) # Committer: Russell King (Mon 8 Aug 15:59:50 BST 2016) # # mvebu/clearfog updates # # Signed-off-by: Russell King # # ce722aa824847b5dbdba3ff32508b08e1060f6d5 # arch/arm/boot/dts/armada-388-clearfog.dts | 36 +++++----- # drivers/pci/host/pci-mvebu.c | 109 ++++++++++++++++++++++++++++-- # drivers/pci/pcie/aspm.c | 2 + # drivers/pci/pcie/portdrv_core.c | 2 + # 4 files changed, 126 insertions(+), 23 deletions(-) # # Author: Russell King (Fri 28 Aug 19:15:51 BST 2015) # Committer: Russell King (Mon 8 Aug 15:59:49 BST 2016) # # ARM: dts: Add Clearfog A1 rev 2.0 DT file # # Signed-off-by: Russell King # # d91fadfe16744fdc64168cce4582b8db8135b987 # arch/arm/boot/dts/armada-388-clearfog.dts | 50 +++++++++++++++++++++++++++++++ # 1 file changed, 50 insertions(+) # # Author: Russell King (Sat 12 Sep 18:43:39 BST 2015) # Committer: Russell King (Mon 8 Aug 15:59:48 BST 2016) # # ARM: dts: armada388-clearfog: add SFP module support # # Add SFP module support for Clearfog using the SFP phylink support. # # Signed-off-by: Russell King # # a482b0695d267b70afb80dfbbfe756a6720d39e0 # arch/arm/boot/dts/armada-388-clearfog.dts | 44 ++++++++----------------------- # 1 file changed, 11 insertions(+), 33 deletions(-) # # Author: Russell King (Mon 4 Jul 10:17:28 BST 2016) # Committer: Russell King (Mon 8 Aug 15:59:48 BST 2016) # # ARM: dts: armada388-clearfog: number LAN ports properly # # Currently, the ports as seen from the rear number as: # # eth0 sfp lan5 lan4 lan3 lan2 lan1 lan6 # # which is illogical - this came about because the rev 2.0 boards have the # LEDs on the front for the DSA switch (lan5-1) reversed. Rev 2.1 boards # fixed the LED issue, and the Clearfog case numbers the lan ports # increasing from left to right. # # Maintaining this illogical numbering causes confusion, with reports that # "my link isn't coming up" and "my connection negotiates 10base-Half" # both of which are due to people thinking that the port next to the SFP # is lan1. # # Fix this by renumbering the ports to match people's expectations. # # Signed-off-by: Russell King # # aa0a9c273b6a356ea932ac9bbcbbff204677995d # arch/arm/boot/dts/armada-388-clearfog.dts | 8 ++++---- # 1 file changed, 4 insertions(+), 4 deletions(-) # # Author: Russell King (Mon 8 Aug 15:59:46 BST 2016) # Committer: Russell King (Mon 8 Aug 15:59:46 BST 2016) # # Merge branches 'mvebu-cpuidle' and 'phy' into clearfog # # Author: Russell King (Tue 12 Jul 16:45:43 BST 2016) # Committer: Russell King (Mon 8 Aug 15:58:31 BST 2016) # # phy: marvell: 88E1540: add flow control support # # The Marvell PHYs support pause frame advertisments, so we should not be # masking their support off. Add the necessary flag to the Marvell PHY # to allow any MAC level pause frame support to be advertised. # # Signed-off-by: Russell King # # 39341dbf4dc156a923f75e5dbde33d74accc0d3b # drivers/net/phy/marvell.c | 2 +- # 1 file changed, 1 insertion(+), 1 deletion(-) # # Author: Russell King (Tue 12 Jul 16:45:43 BST 2016) # Committer: Russell King (Mon 8 Aug 15:58:31 BST 2016) # # phy: marvell: 88E1111: add flow control support # # The Marvell PHYs support pause frame advertisments, so we should not be # masking their support off. Add the necessary flag to the Marvell PHY # to allow any MAC level pause frame support to be advertised. # # Signed-off-by: Russell King # # 9cddd36dff8a031f65927eead0656ff7889d2d71 # drivers/net/phy/marvell.c | 2 +- # 1 file changed, 1 insertion(+), 1 deletion(-) # # Author: Russell King (Thu 1 Oct 00:34:08 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:31 BST 2016) # # phy: marvell: 88E1512: add flow control support # # The Marvell PHYs support pause frame advertisments, so we should not be # masking their support off. Add the necessary flag to the Marvell PHY # to allow any MAC level pause frame support to be advertised. # # Reviewed-by: Florian Fainelli # Signed-off-by: Russell King # # 6b9c6f617465627c247a0eaff562dd7eec01d351 # drivers/net/phy/marvell.c | 3 ++- # 1 file changed, 2 insertions(+), 1 deletion(-) # # Author: Russell King (Thu 8 Oct 23:49:47 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:30 BST 2016) # # sfp/phylink: hook up eeprom functions # # Signed-off-by: Russell King # # ad99c50223c78e12c81d794b310c700922ba8363 # drivers/net/phy/sfp.c | 19 +++++++++++-------- # 1 file changed, 11 insertions(+), 8 deletions(-) # # Author: Russell King (Thu 1 Oct 23:32:39 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:30 BST 2016) # # net: mvneta: add module EEPROM reading support # # Signed-off-by: Russell King # # 396d53b687bac6284537896613d2bf4a9c571479 # drivers/net/ethernet/marvell/mvneta.c | 18 ++++++++++++++++++ # 1 file changed, 18 insertions(+) # # Author: Russell King (Thu 1 Oct 23:10:05 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:30 BST 2016) # # phylink: add module EEPROM support # # Add support for reading module EEPROMs through phylink. # # Reviewed-by: Florian Fainelli # Signed-off-by: Russell King # # 0c3477cbca12b91f35ffc3f76ebd7705b6aaa9e9 # drivers/net/phy/phylink.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++ # include/linux/phylink.h | 12 +++++++++ # 2 files changed, 78 insertions(+) # # Author: Russell King (Tue 29 Sep 15:17:39 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:30 BST 2016) # # net: mvneta: add EEE support # # Add EEE support to mvneta. This allows us to enable the low power idle # support at MAC level if there is a PHY attached through phylink which # supports LPI. The appropriate ethtool support is provided to allow the # feature to be controlled, including ethtool statistics for EEE wakeup # errors. # # Signed-off-by: Russell King # # e6e6a99e0a839859c8649bc89115fd754f8651d8 # drivers/net/ethernet/marvell/mvneta.c | 87 +++++++++++++++++++++++++++++++++++ # 1 file changed, 87 insertions(+) # # Author: Russell King (Thu 1 Oct 21:19:53 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:29 BST 2016) # # phylink: add EEE support # # Add EEE hooks to phylink to allow the phylib EEE functions for the # connected phy to be safely accessed. # # Signed-off-by: Russell King # # ecbb95bca1550297c84407da7c0b61333d861b39 # drivers/net/phy/phylink.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++- # include/linux/phylink.h | 7 +++++- # 2 files changed, 63 insertions(+), 2 deletions(-) # # Author: Russell King (Tue 12 Jul 00:04:13 BST 2016) # Committer: Russell King (Mon 8 Aug 15:58:29 BST 2016) # # net: mvneta: enable flow control for fixed connections # # Allow symetric flow control to be enabled for fixed link connections as # well as other types of connections by setting the supported and # advertised capability bits. # # Signed-off-by: Russell King # # 0e5bc6e10ac488f152c2f458d9fd5b2a6ca76416 # drivers/net/ethernet/marvell/mvneta.c | 11 +++++++---- # 1 file changed, 7 insertions(+), 4 deletions(-) # # Author: Russell King (Thu 1 Oct 00:34:08 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:29 BST 2016) # # net: mvneta: enable flow control for PHY connections # # Enable flow control support for PHY connections by indicating our # support via the ethtool capabilities. phylink takes care of the # appropriate handling. # # Reviewed-by: Florian Fainelli # Signed-off-by: Russell King # # be33da37b6087ff0b50f222b48e6c293e19316a7 # drivers/net/ethernet/marvell/mvneta.c | 2 ++ # 1 file changed, 2 insertions(+) # # Author: Russell King (Thu 1 Oct 17:41:44 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:29 BST 2016) # # net: mvneta: add flow control support via phylink # # Add flow control support to mvneta, including the ethtool hooks. This # uses the phylink code to calculate the result of autonegotiation where # a phy is attached, and to handle the ethtool settings. # # Signed-off-by: Russell King # # 5974cf0d112fedf9ef724b3e411fb2b880bedb1b # drivers/net/ethernet/marvell/mvneta.c | 28 +++++++++++++++++++++++++++- # 1 file changed, 27 insertions(+), 1 deletion(-) # # Author: Russell King (Thu 1 Oct 20:32:07 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:28 BST 2016) # # phylink: add flow control support # # Add flow control support, including ethtool support, to phylink. We # add support to allow ethtool to get and set the current flow control # settings, and the 802.3 specified resolution for the local and remote # link partner abilities. # # Signed-off-by: Russell King # # 4272a0b14d30d711701641d4cfc2c7041ad43e02 # drivers/net/phy/phylink.c | 133 +++++++++++++++++++++++++++++++++++++++++++++- # include/linux/phylink.h | 8 +++ # 2 files changed, 139 insertions(+), 2 deletions(-) # # Author: Russell King (Thu 1 Oct 19:40:31 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:28 BST 2016) # # net: mvneta: add nway_reset support # # Add ethtool nway_reset support to mvneta via phylink, so that userspace # can request the link in whatever mode to be renegotiated via # ethtool -r ethX. # # Signed-off-by: Russell King # # 27d017bec8f3408fa4399ebff0fe6c69a953e695 # drivers/net/ethernet/marvell/mvneta.c | 8 ++++++++ # 1 file changed, 8 insertions(+) # # Author: Russell King (Thu 1 Oct 20:27:19 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:28 BST 2016) # # phylink: add ethtool nway_reset support # # Add ethtool nway_reset support to phylink, to allow userspace to # request a re-negotiation of the link. # # Reviewed-by: Florian Fainelli # Signed-off-by: Russell King # # 0c6a9fa59812af907b99afb3fd10e932b69880e8 # drivers/net/phy/phylink.c | 14 ++++++++++++++ # include/linux/phylink.h | 1 + # 2 files changed, 15 insertions(+) # # Author: Russell King (Fri 2 Oct 22:46:54 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:28 BST 2016) # # phy: fixed-phy: remove fixed_phy_update_state() # # mvneta is the only user of fixed_phy_update_state(), which has been # converted to use phylink instead. Remove fixed_phy_update_state(). # # Reviewed-by: Florian Fainelli # Signed-off-by: Russell King # # 70796de7a4f8d35d37c1e0ed0558498392e35688 # drivers/net/phy/fixed_phy.c | 31 ------------------------------- # include/linux/phy_fixed.h | 9 --------- # 2 files changed, 40 deletions(-) # # Author: Russell King (Wed 16 Sep 21:27:10 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:27 BST 2016) # # net: mvneta: convert to phylink # # Convert mvneta to use phylink, which models the MAC to PHY link in # a generic, reusable form. # # Signed-off-by: Russell King # # c4881310fe37eb07ddc804a55cf865450f5b2c40 # drivers/net/ethernet/marvell/Kconfig | 2 +- # drivers/net/ethernet/marvell/mvneta.c | 455 +++++++++++++++++----------------- # 2 files changed, 226 insertions(+), 231 deletions(-) # # Author: Russell King (Sun 13 Sep 01:06:31 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:27 BST 2016) # # sfp: display SFP module information # # Signed-off-by: Russell King # # fe17c5959d02b194986ab8070b1691c50bf9ff85 # drivers/net/phy/sfp.c | 247 +++++++++++++++++++++++++++++++++++++++++++++++++- # 1 file changed, 246 insertions(+), 1 deletion(-) # # Author: Russell King (Sat 12 Sep 18:43:39 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:27 BST 2016) # # sfp: add phylink based SFP module support # # Add support for SFP hotpluggable modules via phylink. This supports # both copper and optical SFP modules, which require different Serdes # modes in order to properly negotiate the link. # # Optical SFP modules typically require the Serdes link to be talking # 1000base-X mode - this is the gigabit ethernet mode defined by the # 802.3 standard. # # Copper SFP modules typically integrate a PHY in the module to convert # from Serdes to copper, and the PHY will be configured by the vendor # to either present a 1000base-X Serdes link (for fixed 1000base-T) or # a SGMII Serdes link. However, this is vendor defined, so we instead # detect the PHY, switch the link to SGMII mode, and use traditional # PHY based negotiation. # # Signed-off-by: Russell King # # 0d3524af8bceb1d2b13b3179960d23fe1c1cbc66 # drivers/net/phy/Kconfig | 5 + # drivers/net/phy/Makefile | 1 + # drivers/net/phy/sfp.c | 986 +++++++++++++++++++++++++++++++++++++++++++++++ # include/linux/sfp.h | 339 ++++++++++++++++ # 4 files changed, 1331 insertions(+) # create mode 100644 drivers/net/phy/sfp.c # create mode 100644 include/linux/sfp.h # # Author: Russell King (Thu 24 Sep 11:01:13 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:27 BST 2016) # # phylink: add hooks for SFP support # # Add support to phylink for SFP, which needs to control and configure # the ethernet MAC link state. Specifically, SFP needs to: # # 1. set the negotiation mode between SGMII and 1000base-X # 2. attach and detach the module PHY # 3. prevent the link coming up when errors are reported # # In the absence of a PHY, we also need to set the ethtool port type # according to the module plugged in. # # Reviewed-by: Florian Fainelli # Signed-off-by: Russell King # # df21c0a8a41080beec0f6d25412b0cbbce482e68 # drivers/net/phy/phylink.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++ # include/linux/phylink.h | 6 ++++ # 2 files changed, 88 insertions(+) # # Author: Russell King (Tue 22 Sep 20:52:18 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:26 BST 2016) # # phylink: add phylink infrastructure # # The link between the ethernet MAC and its PHY has become more complex # as the interface evolves. This is especially true with serdes links, # where the part of the PHY is effectively integrated into the MAC. # # Serdes links can be connected to a variety of devices, including SFF # modules soldered down onto the board with the MAC, a SFP cage with # a hotpluggable SFP module which may contain a PHY or directly modulate # the serdes signals onto optical media with or without a PHY, or even # a classical PHY connection. # # Moreover, the negotiation information on serdes links comes in two # varieties - SGMII mode, where the PHY provides its speed/duplex/flow # control information to the MAC, and 1000base-X mode where both ends # exchange their abilities and each resolve the link capabilities. # # This means we need a more flexible means to support these arrangements, # particularly with the hotpluggable nature of SFP, where the PHY can # be attached or detached after the network device has been brought up. # # Ethtool information can come from multiple sources: # - we may have a PHY operating in either SGMII or 1000base-X mode, in # which case we take ethtool/mii data directly from the PHY. # - we may have a optical SFP module without a PHY, with the MAC # operating in 1000base-X mode - the ethtool/mii data needs to come # from the MAC. # - we may have a copper SFP module with a PHY whic can't be accessed, # which means we need to take ethtool/mii data from the MAC. # # Phylink aims to solve this by providing an intermediary between the # MAC and PHY, providing a safe way for PHYs to be hotplugged, and # allowing a SFP driver to reconfigure the serdes connection. # # Phylink also takes over support of fixed link connections, where # the speed/duplex/flow control are fixed, but link status may be # controlled by a GPIO signal. By avoiding the fixed-phy implementation, # phylink can provide a faster response to link events: fixed-phy has # to wait for phylib to operate its state machine, which can take # several seconds. In comparison, phylink takes milliseconds. # # Signed-off-by: Russell King # # fe5769ee7e6d7d2988dfad4f6266175246c48e11 # drivers/net/phy/Kconfig | 10 + # drivers/net/phy/Makefile | 1 + # drivers/net/phy/phy_device.c | 1 + # drivers/net/phy/phylink.c | 804 +++++++++++++++++++++++++++++++++++++++++++ # include/linux/phy.h | 2 + # include/linux/phylink.h | 70 ++++ # 6 files changed, 888 insertions(+) # create mode 100644 drivers/net/phy/phylink.c # create mode 100644 include/linux/phylink.h # # Author: Russell King (Fri 25 Sep 17:43:52 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:26 BST 2016) # # phy: add I2C mdio bus # # Add an I2C MDIO bus bridge library, to allow phylib to access PHYs which # are connected to an I2C bus instead of the more conventional MDIO bus. # Such PHYs can be found in SFP adapters and SFF modules. # # Since PHYs appear at I2C bus address 0x40..0x5f, and 0x50/0x51 are # reserved for SFP EEPROMs/diagnostics, we must not allow the MDIO bus # to access these I2C addresses. # # Signed-off-by: Russell King # # ac0e4dcfb90ec7667b15606bb169f8e45d46723b # drivers/net/phy/Kconfig | 10 +++++ # drivers/net/phy/Makefile | 1 + # drivers/net/phy/mdio-i2c.c | 109 +++++++++++++++++++++++++++++++++++++++++++++ # drivers/net/phy/mdio-i2c.h | 19 ++++++++ # 4 files changed, 139 insertions(+) # create mode 100644 drivers/net/phy/mdio-i2c.c # create mode 100644 drivers/net/phy/mdio-i2c.h # # Author: Russell King (Sun 18 Oct 19:51:10 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:26 BST 2016) # # phy: export phy_speed_to_str() for phylink # # phylink would like to reuse phy_speed_to_str() to convert the speed # to a string. Add a prototype and export this helper function. # # Reviewed-by: Florian Fainelli # Signed-off-by: Russell King # # e71222fc9b327a9a65b9e3965dfdb92c67d98998 # drivers/net/phy/phy.c | 3 ++- # include/linux/phy.h | 1 + # 2 files changed, 3 insertions(+), 1 deletion(-) # # Author: Russell King (Fri 16 Oct 12:18:41 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:26 BST 2016) # # phy: export phy_start_machine() for phylink # # phylink will need phy_start_machine exported, so lets export it as a # GPL symbol. Documentation/networking/phy.txt indicates that this # should be a PHY API function. # # Reviewed-by: Florian Fainelli # Signed-off-by: Russell King # # 6bac8cde886102ecde4722223f48e2778a3935be # drivers/net/phy/phy.c | 1 + # 1 file changed, 1 insertion(+) # # Author: Russell King (Fri 18 Sep 14:42:16 BST 2015) # Committer: Russell King (Mon 8 Aug 15:58:26 BST 2016) # # phy: provide a hook for link up/link down events # # Sometimes, we need to do additional work between the PHY coming up and # marking the carrier present - for example, we may need to wait for the # PHY to MAC link to finish negotiation. This changes phylib to provide # a notification function pointer which avoids the built-in # netif_carrier_on() and netif_carrier_off() functions. # # Standard ->adjust_link functionality is provided by hooking a helper # into the new ->phy_link_change method. # # Reviewed-by: Florian Fainelli # Signed-off-by: Russell King # # a1152a3cbcdadd57be418bac2d05a602c2b7a944 # drivers/net/phy/phy.c | 42 ++++++++++++++++++++++-------------------- # drivers/net/phy/phy_device.c | 14 ++++++++++++++ # include/linux/phy.h | 1 + # 3 files changed, 37 insertions(+), 20 deletions(-) # # Author: Russell King (Sat 3 Oct 09:13:05 BST 2015) # Committer: Russell King (Mon 8 Aug 15:10:02 BST 2016) # # cpuidle: mvebu: indicate failure to enter deeper sleep states # # The cpuidle ->enter method expects the return value to be the sleep # state we entered. Returning negative numbers or other codes is not # permissible since coupled CPU idle was merged. # # At least some of the mvebu_v7_cpu_suspend() implementations return the # value from cpu_suspend(), which returns zero if the CPU vectors back # into the kernel via cpu_resume() (the success case), or the non-zero # return value of the suspend actor, or one (failure cases). # # We do not want to be returning the failure case value back to CPU idle # as that indicates that we successfully entered one of the deeper idle # states. Always return zero instead, indicating that we slept for the # shortest amount of time. # # Signed-off-by: Russell King # # a0b75738e32f6d401f16686f8af3565865a3cae0 # drivers/cpuidle/cpuidle-mvebu-v7.c | 6 +++++- # 1 file changed, 5 insertions(+), 1 deletion(-) # diff --git a/arch/arm/boot/dts/armada-388-clearfog.dts b/arch/arm/boot/dts/armada-388-clearfog.dts index 2e0556af6e5e..72cc39bfda52 100644 --- a/arch/arm/boot/dts/armada-388-clearfog.dts +++ b/arch/arm/boot/dts/armada-388-clearfog.dts @@ -90,16 +90,12 @@ }; ethernet@34000 { + managed = "in-band-status"; phy-mode = "sgmii"; buffer-manager = <&bm>; bm,pool-long = <3>; bm,pool-short = <1>; status = "okay"; - - fixed-link { - speed = <1000>; - full-duplex; - }; }; i2c@11000 { @@ -183,34 +179,6 @@ output-low; line-name = "m.2 devslp"; }; - sfp_los { - /* SFP loss of signal */ - gpio-hog; - gpios = <12 GPIO_ACTIVE_HIGH>; - input; - line-name = "sfp-los"; - }; - sfp_tx_fault { - /* SFP laser fault */ - gpio-hog; - gpios = <13 GPIO_ACTIVE_HIGH>; - input; - line-name = "sfp-tx-fault"; - }; - sfp_tx_disable { - /* SFP transmit disable */ - gpio-hog; - gpios = <14 GPIO_ACTIVE_HIGH>; - output-low; - line-name = "sfp-tx-disable"; - }; - sfp_mod_def0 { - /* SFP module present */ - gpio-hog; - gpios = <15 GPIO_ACTIVE_LOW>; - input; - line-name = "sfp-mod-def0"; - }; }; /* The MCP3021 is 100kHz clock only */ @@ -374,6 +342,16 @@ }; }; + sfp: sfp { + compatible = "sff,sfp"; + i2c-bus = <&i2c1>; + los-gpio = <&expander0 12 GPIO_ACTIVE_HIGH>; + moddef0-gpio = <&expander0 15 GPIO_ACTIVE_LOW>; + sfp,ethernet = <ð2>; + tx-disable-gpio = <&expander0 14 GPIO_ACTIVE_HIGH>; + tx-fault-gpio = <&expander0 13 GPIO_ACTIVE_HIGH>; + }; + dsa@0 { compatible = "marvell,dsa"; dsa,ethernet = <ð1>; @@ -390,12 +368,12 @@ port@0 { reg = <0>; - label = "lan1"; + label = "lan5"; }; port@1 { reg = <1>; - label = "lan2"; + label = "lan4"; }; port@2 { @@ -405,12 +383,12 @@ port@3 { reg = <3>; - label = "lan4"; + label = "lan2"; }; port@4 { reg = <4>; - label = "lan5"; + label = "lan1"; }; port@5 { @@ -444,3 +422,53 @@ }; }; }; +/* ++#define A38x_CUSTOMER_BOARD_1_MPP16_23 0x00400011 +MPP18: gpio ? (pca9655 int?) +MPP19: gpio ? (clkreq?) +MPP20: gpio ? (sd0 detect) +MPP21: sd0:cmd x sd0 +MPP22: gpio x mikro int +MPP23: gpio x switch irq ++#define A38x_CUSTOMER_BOARD_1_MPP24_31 0x22043333 +MPP24: ua1:rxd x mikro rx +MPP25: ua1:txd x mikro tx +MPP26: i2c1:sck x mikro sck +MPP27: i2c1:sda x mikro sda +MPP28: sd0:clk x sd0 +MPP29: gpio x mikro rst +MPP30: ge1:txd2 ? (config) +MPP31: ge1:txd3 ? (config) ++#define A38x_CUSTOMER_BOARD_1_MPP32_39 0x44400002 +MPP32: ge1:txctl ? (unused) +MPP33: gpio ? (pic_com0) +MPP34: gpio x rear button (pic_com1) +MPP35: gpio ? (pic_com2) +MPP36: gpio ? (unused) +MPP37: sd0:d3 x sd0 +MPP38: sd0:d0 x sd0 +MPP39: sd0:d1 x sd0 ++#define A38x_CUSTOMER_BOARD_1_MPP40_47 0x41144004 +MPP40: sd0:d2 x sd0 +MPP41: gpio x switch reset +MPP42: gpio ? sw1-1 +MPP43: spi1:cs2 x mikro cs +MPP44: sata3:prsnt ? (unused) +MPP45: ref:clk_out0 ? +MPP46: ref:clk_out1 x switch clk +MPP47: 4 ? (unused) ++#define A38x_CUSTOMER_BOARD_1_MPP48_55 0x40333333 +MPP48: tdm:pclk +MPP49: tdm:fsync +MPP50: tdm:drx +MPP51: tdm:dtx +MPP52: tdm:int +MPP53: tdm:rst +MPP54: gpio ? (pwm) +MPP55: spi1:cs1 x slic ++#define A38x_CUSTOMER_BOARD_1_MPP56_63 0x00004444 +MPP56: spi1:mosi x mikro mosi +MPP57: spi1:sck x mikro sck +MPP58: spi1:miso x mikro miso +MPP59: spi1:cs0 x w25q32 +*/ diff --git a/drivers/cpuidle/cpuidle-mvebu-v7.c b/drivers/cpuidle/cpuidle-mvebu-v7.c index 01a856971f05..18ded9e7cb34 100644 --- a/drivers/cpuidle/cpuidle-mvebu-v7.c +++ b/drivers/cpuidle/cpuidle-mvebu-v7.c @@ -39,8 +39,12 @@ static int mvebu_v7_enter_idle(struct cpuidle_device *dev, ret = mvebu_v7_cpu_suspend(deepidle); cpu_pm_exit(); + /* + * If we failed to enter the desired state, indicate that we + * slept lightly. + */ if (ret) - return ret; + return 0; return index; } diff --git a/drivers/net/ethernet/marvell/Kconfig b/drivers/net/ethernet/marvell/Kconfig index 2664827ddecd..423124d4d56f 100644 --- a/drivers/net/ethernet/marvell/Kconfig +++ b/drivers/net/ethernet/marvell/Kconfig @@ -57,7 +57,7 @@ config MVNETA tristate "Marvell Armada 370/38x/XP network interface support" depends on PLAT_ORION select MVMDIO - select FIXED_PHY + select PHYLINK ---help--- This driver supports the network interface units in the Marvell ARMADA XP, ARMADA 370 and ARMADA 38x SoC family. diff --git a/drivers/net/ethernet/marvell/mvneta.c b/drivers/net/ethernet/marvell/mvneta.c index d41c28d00b57..ea972f921651 100644 --- a/drivers/net/ethernet/marvell/mvneta.c +++ b/drivers/net/ethernet/marvell/mvneta.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -188,6 +189,7 @@ #define MVNETA_GMAC_CTRL_0 0x2c00 #define MVNETA_GMAC_MAX_RX_SIZE_SHIFT 2 #define MVNETA_GMAC_MAX_RX_SIZE_MASK 0x7ffc +#define MVNETA_GMAC0_PORT_1000BASE_X BIT(1) #define MVNETA_GMAC0_PORT_ENABLE BIT(0) #define MVNETA_GMAC_CTRL_2 0x2c08 #define MVNETA_GMAC2_INBAND_AN_ENABLE BIT(0) @@ -203,13 +205,19 @@ #define MVNETA_GMAC_TX_FLOW_CTRL_ENABLE BIT(5) #define MVNETA_GMAC_RX_FLOW_CTRL_ACTIVE BIT(6) #define MVNETA_GMAC_TX_FLOW_CTRL_ACTIVE BIT(7) +#define MVNETA_GMAC_AN_COMPLETE BIT(11) +#define MVNETA_GMAC_SYNC_OK BIT(14) #define MVNETA_GMAC_AUTONEG_CONFIG 0x2c0c #define MVNETA_GMAC_FORCE_LINK_DOWN BIT(0) #define MVNETA_GMAC_FORCE_LINK_PASS BIT(1) #define MVNETA_GMAC_INBAND_AN_ENABLE BIT(2) +#define MVNETA_GMAC_AN_BYPASS_ENABLE BIT(3) +#define MVNETA_GMAC_INBAND_RESTART_AN BIT(4) #define MVNETA_GMAC_CONFIG_MII_SPEED BIT(5) #define MVNETA_GMAC_CONFIG_GMII_SPEED BIT(6) #define MVNETA_GMAC_AN_SPEED_EN BIT(7) +#define MVNETA_GMAC_CONFIG_FLOW_CTRL BIT(8) +#define MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL BIT(9) #define MVNETA_GMAC_AN_FLOW_CTRL_EN BIT(11) #define MVNETA_GMAC_CONFIG_FULL_DUPLEX BIT(12) #define MVNETA_GMAC_AN_DUPLEX_EN BIT(13) @@ -235,6 +243,12 @@ #define MVNETA_TXQ_TOKEN_SIZE_REG(q) (0x3e40 + ((q) << 2)) #define MVNETA_TXQ_TOKEN_SIZE_MAX 0x7fffffff +#define MVNETA_LPI_CTRL_0 0x2cc0 +#define MVNETA_LPI_CTRL_1 0x2cc4 +#define MVNETA_LPI_REQUEST_ENABLE BIT(0) +#define MVNETA_LPI_CTRL_2 0x2cc8 +#define MVNETA_LPI_STATUS 0x2ccc + #define MVNETA_CAUSE_TXQ_SENT_DESC_ALL_MASK 0xff /* Descriptor ring Macros */ @@ -308,6 +322,11 @@ #define MVNETA_RX_GET_BM_POOL_ID(rxd) \ (((rxd)->status & MVNETA_RXD_BM_POOL_MASK) >> MVNETA_RXD_BM_POOL_SHIFT) +enum { + ETHTOOL_STAT_EEE_WAKEUP, + ETHTOOL_MAX_STATS, +}; + struct mvneta_statistic { unsigned short offset; unsigned short type; @@ -316,6 +335,7 @@ struct mvneta_statistic { #define T_REG_32 32 #define T_REG_64 64 +#define T_SW 1 static const struct mvneta_statistic mvneta_statistics[] = { { 0x3000, T_REG_64, "good_octets_received", }, @@ -350,6 +370,7 @@ static const struct mvneta_statistic mvneta_statistics[] = { { 0x304c, T_REG_32, "broadcast_frames_sent", }, { 0x3054, T_REG_32, "fc_sent", }, { 0x300c, T_REG_32, "internal_mac_transmit_err", }, + { ETHTOOL_STAT_EEE_WAKEUP, T_SW, "eee_wakeup_errors", }, }; struct mvneta_pcpu_stats { @@ -398,21 +419,19 @@ struct mvneta_port { u16 tx_ring_size; u16 rx_ring_size; - struct mii_bus *mii_bus; - struct phy_device *phy_dev; - phy_interface_t phy_interface; - struct device_node *phy_node; - unsigned int link; - unsigned int duplex; - unsigned int speed; + struct device_node *dn; unsigned int tx_csum_limit; - unsigned int use_inband_status:1; + struct phylink *phylink; struct mvneta_bm *bm_priv; struct mvneta_bm_pool *pool_long; struct mvneta_bm_pool *pool_short; int bm_win_id; + bool eee_enabled; + bool eee_active; + bool tx_lpi_enabled; + u64 ethtool_stats[ARRAY_SIZE(mvneta_statistics)]; u32 indir[MVNETA_RSS_LU_TABLE_SIZE]; @@ -1238,44 +1257,6 @@ static void mvneta_set_other_mcast_table(struct mvneta_port *pp, int queue) mvreg_write(pp, MVNETA_DA_FILT_OTH_MCAST + offset, val); } -static void mvneta_set_autoneg(struct mvneta_port *pp, int enable) -{ - u32 val; - - if (enable) { - val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~(MVNETA_GMAC_FORCE_LINK_PASS | - MVNETA_GMAC_FORCE_LINK_DOWN | - MVNETA_GMAC_AN_FLOW_CTRL_EN); - val |= MVNETA_GMAC_INBAND_AN_ENABLE | - MVNETA_GMAC_AN_SPEED_EN | - MVNETA_GMAC_AN_DUPLEX_EN; - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); - - val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); - val |= MVNETA_GMAC_1MS_CLOCK_ENABLE; - mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); - - val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); - val |= MVNETA_GMAC2_INBAND_AN_ENABLE; - mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); - } else { - val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~(MVNETA_GMAC_INBAND_AN_ENABLE | - MVNETA_GMAC_AN_SPEED_EN | - MVNETA_GMAC_AN_DUPLEX_EN); - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); - - val = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); - val &= ~MVNETA_GMAC_1MS_CLOCK_ENABLE; - mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, val); - - val = mvreg_read(pp, MVNETA_GMAC_CTRL_2); - val &= ~MVNETA_GMAC2_INBAND_AN_ENABLE; - mvreg_write(pp, MVNETA_GMAC_CTRL_2, val); - } -} - static void mvneta_percpu_unmask_interrupt(void *arg) { struct mvneta_port *pp = arg; @@ -1423,7 +1404,6 @@ static void mvneta_defaults_set(struct mvneta_port *pp) val &= ~MVNETA_PHY_POLLING_ENABLE; mvreg_write(pp, MVNETA_UNIT_CONTROL, val); - mvneta_set_autoneg(pp, pp->use_inband_status); mvneta_set_ucast_table(pp, -1); mvneta_set_special_mcast_table(pp, -1); mvneta_set_other_mcast_table(pp, -1); @@ -2616,26 +2596,11 @@ static irqreturn_t mvneta_isr(int irq, void *dev_id) return IRQ_HANDLED; } -static int mvneta_fixed_link_update(struct mvneta_port *pp, - struct phy_device *phy) +static void mvneta_link_change(struct mvneta_port *pp) { - struct fixed_phy_status status; - struct fixed_phy_status changed = {}; u32 gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS); - status.link = !!(gmac_stat & MVNETA_GMAC_LINK_UP); - if (gmac_stat & MVNETA_GMAC_SPEED_1000) - status.speed = SPEED_1000; - else if (gmac_stat & MVNETA_GMAC_SPEED_100) - status.speed = SPEED_100; - else - status.speed = SPEED_10; - status.duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX); - changed.link = 1; - changed.speed = 1; - changed.duplex = 1; - fixed_phy_update_state(phy, &status, &changed); - return 0; + phylink_mac_change(pp->phylink, !!(gmac_stat & MVNETA_GMAC_LINK_UP)); } /* NAPI handler @@ -2664,12 +2629,11 @@ static int mvneta_poll(struct napi_struct *napi, int budget) u32 cause_misc = mvreg_read(pp, MVNETA_INTR_MISC_CAUSE); mvreg_write(pp, MVNETA_INTR_MISC_CAUSE, 0); - if (pp->use_inband_status && (cause_misc & - (MVNETA_CAUSE_PHY_STATUS_CHANGE | - MVNETA_CAUSE_LINK_CHANGE | - MVNETA_CAUSE_PSC_SYNC_CHANGE))) { - mvneta_fixed_link_update(pp, pp->phy_dev); - } + + if (cause_misc & (MVNETA_CAUSE_PHY_STATUS_CHANGE | + MVNETA_CAUSE_LINK_CHANGE | + MVNETA_CAUSE_PSC_SYNC_CHANGE)) + mvneta_link_change(pp); } /* Release Tx descriptors */ @@ -2985,7 +2949,7 @@ static void mvneta_start_dev(struct mvneta_port *pp) MVNETA_CAUSE_LINK_CHANGE | MVNETA_CAUSE_PSC_SYNC_CHANGE); - phy_start(pp->phy_dev); + phylink_start(pp->phylink); netif_tx_start_all_queues(pp->dev); } @@ -2993,7 +2957,7 @@ static void mvneta_stop_dev(struct mvneta_port *pp) { unsigned int cpu; - phy_stop(pp->phy_dev); + phylink_stop(pp->phylink); for_each_online_cpu(cpu) { struct mvneta_pcpu_port *port = per_cpu_ptr(pp->ports, cpu); @@ -3163,99 +3127,246 @@ static int mvneta_set_mac_addr(struct net_device *dev, void *addr) return 0; } -static void mvneta_adjust_link(struct net_device *ndev) +static int mvneta_mac_support(struct net_device *ndev, unsigned int mode, + struct phylink_link_state *state) +{ + switch (mode) { + case MLO_AN_8023Z: + state->supported = SUPPORTED_1000baseT_Full | + SUPPORTED_Autoneg; + state->advertising = ADVERTISED_1000baseT_Full | + ADVERTISED_Autoneg; + state->an_enabled = 1; + break; + + case MLO_AN_FIXED: + break; + + default: + state->supported = PHY_10BT_FEATURES | + PHY_100BT_FEATURES | + SUPPORTED_1000baseT_Full | + SUPPORTED_Autoneg; + state->advertising = ADVERTISED_10baseT_Half | + ADVERTISED_10baseT_Full | + ADVERTISED_100baseT_Half | + ADVERTISED_100baseT_Full | + ADVERTISED_1000baseT_Full | + ADVERTISED_Autoneg; + state->an_enabled = 1; + break; + } + + /* All modes support flow control */ + state->supported |= SUPPORTED_Pause; + state->advertising |= ADVERTISED_Pause; + + return 0; +} + +static int mvneta_mac_link_state(struct net_device *ndev, + struct phylink_link_state *state) { struct mvneta_port *pp = netdev_priv(ndev); - struct phy_device *phydev = pp->phy_dev; - int status_change = 0; + u32 gmac_stat; - if (phydev->link) { - if ((pp->speed != phydev->speed) || - (pp->duplex != phydev->duplex)) { - u32 val; + gmac_stat = mvreg_read(pp, MVNETA_GMAC_STATUS); - val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED | - MVNETA_GMAC_CONFIG_GMII_SPEED | - MVNETA_GMAC_CONFIG_FULL_DUPLEX); + if (gmac_stat & MVNETA_GMAC_SPEED_1000) + state->speed = SPEED_1000; + else if (gmac_stat & MVNETA_GMAC_SPEED_100) + state->speed = SPEED_100; + else + state->speed = SPEED_10; - if (phydev->duplex) - val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX; + state->an_complete = !!(gmac_stat & MVNETA_GMAC_AN_COMPLETE); + state->sync = !!(gmac_stat & MVNETA_GMAC_SYNC_OK); + state->link = !!(gmac_stat & MVNETA_GMAC_LINK_UP); + state->duplex = !!(gmac_stat & MVNETA_GMAC_FULL_DUPLEX); - if (phydev->speed == SPEED_1000) - val |= MVNETA_GMAC_CONFIG_GMII_SPEED; - else if (phydev->speed == SPEED_100) - val |= MVNETA_GMAC_CONFIG_MII_SPEED; + state->pause = 0; + if (gmac_stat & MVNETA_GMAC_RX_FLOW_CTRL_ENABLE) + state->pause |= MLO_PAUSE_RX; + if (gmac_stat & MVNETA_GMAC_TX_FLOW_CTRL_ENABLE) + state->pause |= MLO_PAUSE_TX; - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); + return 1; +} - pp->duplex = phydev->duplex; - pp->speed = phydev->speed; - } +static void mvneta_mac_an_restart(struct net_device *ndev, unsigned int mode) +{ + struct mvneta_port *pp = netdev_priv(ndev); + + if (mode == MLO_AN_8023Z) { + u32 gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); + + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, + gmac_an | MVNETA_GMAC_INBAND_RESTART_AN); + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, + gmac_an & ~MVNETA_GMAC_INBAND_RESTART_AN); } +} - if (phydev->link != pp->link) { - if (!phydev->link) { - pp->duplex = -1; - pp->speed = 0; - } +static void mvneta_mac_config(struct net_device *ndev, unsigned int mode, + const struct phylink_link_state *state) +{ + struct mvneta_port *pp = netdev_priv(ndev); + u32 new_ctrl0, gmac_ctrl0 = mvreg_read(pp, MVNETA_GMAC_CTRL_0); + u32 new_ctrl2, gmac_ctrl2 = mvreg_read(pp, MVNETA_GMAC_CTRL_2); + u32 new_clk, gmac_clk = mvreg_read(pp, MVNETA_GMAC_CLOCK_DIVIDER); + u32 new_an, gmac_an = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); + + new_ctrl0 = gmac_ctrl0 & ~MVNETA_GMAC0_PORT_1000BASE_X; + new_ctrl2 = gmac_ctrl2 & ~MVNETA_GMAC2_INBAND_AN_ENABLE; + new_clk = gmac_clk & ~MVNETA_GMAC_1MS_CLOCK_ENABLE; + new_an = gmac_an & ~(MVNETA_GMAC_INBAND_AN_ENABLE | + MVNETA_GMAC_INBAND_RESTART_AN | + MVNETA_GMAC_CONFIG_MII_SPEED | + MVNETA_GMAC_CONFIG_GMII_SPEED | + MVNETA_GMAC_AN_SPEED_EN | + MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL | + MVNETA_GMAC_CONFIG_FLOW_CTRL | + MVNETA_GMAC_AN_FLOW_CTRL_EN | + MVNETA_GMAC_CONFIG_FULL_DUPLEX | + MVNETA_GMAC_AN_DUPLEX_EN); + + if (state->advertising & ADVERTISED_Pause) + new_an |= MVNETA_GMAC_ADVERT_SYM_FLOW_CTRL; + if (state->pause & MLO_PAUSE_TXRX_MASK) + new_an |= MVNETA_GMAC_CONFIG_FLOW_CTRL; + + switch (mode) { + case MLO_AN_SGMII: + /* SGMII mode receives the state from the PHY */ + new_ctrl2 |= MVNETA_GMAC2_INBAND_AN_ENABLE; + new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE; + new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN | + MVNETA_GMAC_FORCE_LINK_PASS)) | + MVNETA_GMAC_INBAND_AN_ENABLE | + MVNETA_GMAC_AN_SPEED_EN | + MVNETA_GMAC_AN_DUPLEX_EN; + break; + + case MLO_AN_8023Z: + /* 802.3z negotiation - only 1000base-X */ + new_ctrl0 |= MVNETA_GMAC0_PORT_1000BASE_X; + new_clk |= MVNETA_GMAC_1MS_CLOCK_ENABLE; + new_an = (new_an & ~(MVNETA_GMAC_FORCE_LINK_DOWN | + MVNETA_GMAC_FORCE_LINK_PASS)) | + MVNETA_GMAC_INBAND_AN_ENABLE | + MVNETA_GMAC_CONFIG_GMII_SPEED | + /* The MAC only supports FD mode */ + MVNETA_GMAC_CONFIG_FULL_DUPLEX; + + if (state->pause & MLO_PAUSE_AN && state->an_enabled) + new_an |= MVNETA_GMAC_AN_FLOW_CTRL_EN; + break; - pp->link = phydev->link; - status_change = 1; + default: + /* Phy or fixed speed */ + if (state->duplex) + new_an |= MVNETA_GMAC_CONFIG_FULL_DUPLEX; + + if (state->speed == SPEED_1000) + new_an |= MVNETA_GMAC_CONFIG_GMII_SPEED; + else if (state->speed == SPEED_100) + new_an |= MVNETA_GMAC_CONFIG_MII_SPEED; + break; } - if (status_change) { - if (phydev->link) { - if (!pp->use_inband_status) { - u32 val = mvreg_read(pp, - MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~MVNETA_GMAC_FORCE_LINK_DOWN; - val |= MVNETA_GMAC_FORCE_LINK_PASS; - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, - val); - } - mvneta_port_up(pp); - } else { - if (!pp->use_inband_status) { - u32 val = mvreg_read(pp, - MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~MVNETA_GMAC_FORCE_LINK_PASS; - val |= MVNETA_GMAC_FORCE_LINK_DOWN; - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, - val); - } - mvneta_port_down(pp); - } - phy_print_status(phydev); + /* Armada 370 documentation says we can only change the port mode + * and in-band enable when the link is down, so force it down + * while making these changes. We also do this for GMAC_CTRL2 */ + if ((new_ctrl0 ^ gmac_ctrl0) & MVNETA_GMAC0_PORT_1000BASE_X || + (new_ctrl2 ^ gmac_ctrl2) & MVNETA_GMAC2_INBAND_AN_ENABLE || + (new_an ^ gmac_an) & MVNETA_GMAC_INBAND_AN_ENABLE) { + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, + (gmac_an & ~MVNETA_GMAC_FORCE_LINK_PASS) | + MVNETA_GMAC_FORCE_LINK_DOWN); } + + if (new_ctrl0 != gmac_ctrl0) + mvreg_write(pp, MVNETA_GMAC_CTRL_0, new_ctrl0); + if (new_ctrl2 != gmac_ctrl2) + mvreg_write(pp, MVNETA_GMAC_CTRL_2, new_ctrl2); + if (new_clk != gmac_clk) + mvreg_write(pp, MVNETA_GMAC_CLOCK_DIVIDER, new_clk); + if (new_an != gmac_an) + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, new_an); } -static int mvneta_mdio_probe(struct mvneta_port *pp) +static void mvneta_set_eee(struct mvneta_port *pp, bool enable) +{ + u32 lpi_ctl1; + + lpi_ctl1 = mvreg_read(pp, MVNETA_LPI_CTRL_1); + if (enable) + lpi_ctl1 |= MVNETA_LPI_REQUEST_ENABLE; + else + lpi_ctl1 &= ~MVNETA_LPI_REQUEST_ENABLE; + mvreg_write(pp, MVNETA_LPI_CTRL_1, lpi_ctl1); +} + +static void mvneta_mac_link_down(struct net_device *ndev, unsigned int mode) { - struct phy_device *phy_dev; + struct mvneta_port *pp = netdev_priv(ndev); + u32 val; - phy_dev = of_phy_connect(pp->dev, pp->phy_node, mvneta_adjust_link, 0, - pp->phy_interface); - if (!phy_dev) { - netdev_err(pp->dev, "could not find the PHY\n"); - return -ENODEV; + mvneta_port_down(pp); + + if (mode == MLO_AN_PHY || mode == MLO_AN_FIXED) { + val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); + val &= ~MVNETA_GMAC_FORCE_LINK_PASS; + val |= MVNETA_GMAC_FORCE_LINK_DOWN; + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); } - phy_dev->supported &= PHY_GBIT_FEATURES; - phy_dev->advertising = phy_dev->supported; + pp->eee_active = false; + mvneta_set_eee(pp, false); +} - pp->phy_dev = phy_dev; - pp->link = 0; - pp->duplex = 0; - pp->speed = 0; +static void mvneta_mac_link_up(struct net_device *ndev, unsigned int mode, + struct phy_device *phy) +{ + struct mvneta_port *pp = netdev_priv(ndev); + u32 val; - return 0; + if (mode == MLO_AN_PHY || mode == MLO_AN_FIXED) { + val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); + val &= ~MVNETA_GMAC_FORCE_LINK_DOWN; + val |= MVNETA_GMAC_FORCE_LINK_PASS; + mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); + } + + mvneta_port_up(pp); + + if (phy && pp->eee_enabled) { + pp->eee_active = phy_init_eee(phy, 0) >= 0; + mvneta_set_eee(pp, pp->eee_active && pp->tx_lpi_enabled); + } +} + +static const struct phylink_mac_ops mvneta_phylink_ops = { + .mac_get_support = mvneta_mac_support, + .mac_link_state = mvneta_mac_link_state, + .mac_an_restart = mvneta_mac_an_restart, + .mac_config = mvneta_mac_config, + .mac_link_down = mvneta_mac_link_down, + .mac_link_up = mvneta_mac_link_up, +}; + +static int mvneta_mdio_probe(struct mvneta_port *pp) +{ + int err = phylink_of_phy_connect(pp->phylink, pp->dn); + if (err) + netdev_err(pp->dev, "could not attach PHY\n"); + + return err; } static void mvneta_mdio_remove(struct mvneta_port *pp) { - phy_disconnect(pp->phy_dev); - pp->phy_dev = NULL; + phylink_disconnect_phy(pp->phylink); } /* Electing a CPU must be done in an atomic way: it should be done @@ -3497,10 +3608,7 @@ static int mvneta_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { struct mvneta_port *pp = netdev_priv(dev); - if (!pp->phy_dev) - return -ENOTSUPP; - - return phy_mii_ioctl(pp->phy_dev, ifr, cmd); + return phylink_mii_ioctl(pp->phylink, ifr, cmd); } /* Ethtool methods */ @@ -3510,54 +3618,22 @@ int mvneta_ethtool_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) { struct mvneta_port *pp = netdev_priv(dev); - if (!pp->phy_dev) - return -ENODEV; - - return phy_ethtool_gset(pp->phy_dev, cmd); + return phylink_ethtool_get_settings(pp->phylink, cmd); } /* Set settings (phy address, speed) for ethtools */ int mvneta_ethtool_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) { struct mvneta_port *pp = netdev_priv(dev); - struct phy_device *phydev = pp->phy_dev; - - if (!phydev) - return -ENODEV; - if ((cmd->autoneg == AUTONEG_ENABLE) != pp->use_inband_status) { - u32 val; - - mvneta_set_autoneg(pp, cmd->autoneg == AUTONEG_ENABLE); - - if (cmd->autoneg == AUTONEG_DISABLE) { - val = mvreg_read(pp, MVNETA_GMAC_AUTONEG_CONFIG); - val &= ~(MVNETA_GMAC_CONFIG_MII_SPEED | - MVNETA_GMAC_CONFIG_GMII_SPEED | - MVNETA_GMAC_CONFIG_FULL_DUPLEX); - - if (phydev->duplex) - val |= MVNETA_GMAC_CONFIG_FULL_DUPLEX; - - if (phydev->speed == SPEED_1000) - val |= MVNETA_GMAC_CONFIG_GMII_SPEED; - else if (phydev->speed == SPEED_100) - val |= MVNETA_GMAC_CONFIG_MII_SPEED; - - mvreg_write(pp, MVNETA_GMAC_AUTONEG_CONFIG, val); - } - - pp->use_inband_status = (cmd->autoneg == AUTONEG_ENABLE); - netdev_info(pp->dev, "autoneg status set to %i\n", - pp->use_inband_status); + return phylink_ethtool_set_settings(pp->phylink, cmd); +} - if (netif_running(dev)) { - mvneta_port_down(pp); - mvneta_port_up(pp); - } - } +static int mvneta_ethtool_nway_reset(struct net_device *dev) +{ + struct mvneta_port *pp = netdev_priv(dev); - return phy_ethtool_sset(pp->phy_dev, cmd); + return phylink_ethtool_nway_reset(pp->phylink); } /* Set interrupt coalescing for ethtools */ @@ -3649,6 +3725,22 @@ static int mvneta_ethtool_set_ringparam(struct net_device *dev, return 0; } +static void mvneta_ethtool_get_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *pause) +{ + struct mvneta_port *pp = netdev_priv(dev); + + phylink_ethtool_get_pauseparam(pp->phylink, pause); +} + +static int mvneta_ethtool_set_pauseparam(struct net_device *dev, + struct ethtool_pauseparam *pause) +{ + struct mvneta_port *pp = netdev_priv(dev); + + return phylink_ethtool_set_pauseparam(pp->phylink, pause); +} + static void mvneta_ethtool_get_strings(struct net_device *netdev, u32 sset, u8 *data) { @@ -3665,26 +3757,35 @@ static void mvneta_ethtool_update_stats(struct mvneta_port *pp) { const struct mvneta_statistic *s; void __iomem *base = pp->base; - u32 high, low, val; - u64 val64; + u32 high, low; + u64 val; int i; for (i = 0, s = mvneta_statistics; s < mvneta_statistics + ARRAY_SIZE(mvneta_statistics); s++, i++) { + val = 0; + switch (s->type) { case T_REG_32: val = readl_relaxed(base + s->offset); - pp->ethtool_stats[i] += val; break; case T_REG_64: /* Docs say to read low 32-bit then high */ low = readl_relaxed(base + s->offset); high = readl_relaxed(base + s->offset + 4); - val64 = (u64)high << 32 | low; - pp->ethtool_stats[i] += val64; + val = (u64)high << 32 | low; + break; + case T_SW: + switch (s->offset) { + case ETHTOOL_STAT_EEE_WAKEUP: + val = phylink_get_eee_err(pp->phylink); + break; + } break; } + + pp->ethtool_stats[i] += val; } } @@ -3807,6 +3908,63 @@ static int mvneta_ethtool_get_rxfh(struct net_device *dev, u32 *indir, u8 *key, return 0; } +static int mvneta_ethtool_get_module_info(struct net_device *dev, + struct ethtool_modinfo *modinfo) +{ + struct mvneta_port *pp = netdev_priv(dev); + + return phylink_ethtool_get_module_info(pp->phylink, modinfo); +} + +static int mvneta_ethtool_get_module_eeprom(struct net_device *dev, + struct ethtool_eeprom *ee, u8 *buf) +{ + struct mvneta_port *pp = netdev_priv(dev); + + return phylink_ethtool_get_module_eeprom(pp->phylink, ee, buf); +} + +static int mvneta_ethtool_get_eee(struct net_device *dev, + struct ethtool_eee *eee) +{ + struct mvneta_port *pp = netdev_priv(dev); + u32 lpi_ctl0; + + lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0); + + eee->eee_enabled = pp->eee_enabled; + eee->eee_active = pp->eee_active; + eee->tx_lpi_enabled = pp->tx_lpi_enabled; + eee->tx_lpi_timer = (lpi_ctl0) >> 8; // * scale; + + return phylink_ethtool_get_eee(pp->phylink, eee); +} + +static int mvneta_ethtool_set_eee(struct net_device *dev, + struct ethtool_eee *eee) +{ + struct mvneta_port *pp = netdev_priv(dev); + u32 lpi_ctl0; + + /* The Armada 37x documents do not give limits for this other than + * it being an 8-bit register. */ + if (eee->tx_lpi_enabled && + (eee->tx_lpi_timer < 0 || eee->tx_lpi_timer > 255)) + return -EINVAL; + + lpi_ctl0 = mvreg_read(pp, MVNETA_LPI_CTRL_0); + lpi_ctl0 &= ~(0xff << 8); + lpi_ctl0 |= eee->tx_lpi_timer << 8; + mvreg_write(pp, MVNETA_LPI_CTRL_0, lpi_ctl0); + + pp->eee_enabled = eee->eee_enabled; + pp->tx_lpi_enabled = eee->tx_lpi_enabled; + + mvneta_set_eee(pp, eee->tx_lpi_enabled && eee->eee_enabled); + + return phylink_ethtool_set_eee(pp->phylink, eee); +} + static const struct net_device_ops mvneta_netdev_ops = { .ndo_open = mvneta_open, .ndo_stop = mvneta_stop, @@ -3823,11 +3981,14 @@ const struct ethtool_ops mvneta_eth_tool_ops = { .get_link = ethtool_op_get_link, .get_settings = mvneta_ethtool_get_settings, .set_settings = mvneta_ethtool_set_settings, + .nway_reset = mvneta_ethtool_nway_reset, .set_coalesce = mvneta_ethtool_set_coalesce, .get_coalesce = mvneta_ethtool_get_coalesce, .get_drvinfo = mvneta_ethtool_get_drvinfo, .get_ringparam = mvneta_ethtool_get_ringparam, .set_ringparam = mvneta_ethtool_set_ringparam, + .get_pauseparam = mvneta_ethtool_get_pauseparam, + .set_pauseparam = mvneta_ethtool_set_pauseparam, .get_strings = mvneta_ethtool_get_strings, .get_ethtool_stats = mvneta_ethtool_get_stats, .get_sset_count = mvneta_ethtool_get_sset_count, @@ -3835,6 +3996,10 @@ const struct ethtool_ops mvneta_eth_tool_ops = { .get_rxnfc = mvneta_ethtool_get_rxnfc, .get_rxfh = mvneta_ethtool_get_rxfh, .set_rxfh = mvneta_ethtool_set_rxfh, + .get_module_info = mvneta_ethtool_get_module_info, + .get_module_eeprom = mvneta_ethtool_get_module_eeprom, + .get_eee = mvneta_ethtool_get_eee, + .set_eee = mvneta_ethtool_set_eee, }; /* Initialize hw */ @@ -3960,14 +4125,13 @@ static int mvneta_probe(struct platform_device *pdev) const struct mbus_dram_target_info *dram_target_info; struct resource *res; struct device_node *dn = pdev->dev.of_node; - struct device_node *phy_node; struct device_node *bm_node; struct mvneta_port *pp; struct net_device *dev; + struct phylink *phylink; const char *dt_mac_addr; char hw_mac_addr[ETH_ALEN]; const char *mac_from; - const char *managed; int tx_csum_limit; int phy_mode; int err; @@ -3983,31 +4147,11 @@ static int mvneta_probe(struct platform_device *pdev) goto err_free_netdev; } - phy_node = of_parse_phandle(dn, "phy", 0); - if (!phy_node) { - if (!of_phy_is_fixed_link(dn)) { - dev_err(&pdev->dev, "no PHY specified\n"); - err = -ENODEV; - goto err_free_irq; - } - - err = of_phy_register_fixed_link(dn); - if (err < 0) { - dev_err(&pdev->dev, "cannot register fixed PHY\n"); - goto err_free_irq; - } - - /* In the case of a fixed PHY, the DT node associated - * to the PHY is the Ethernet MAC DT node. - */ - phy_node = of_node_get(dn); - } - phy_mode = of_get_phy_mode(dn); if (phy_mode < 0) { dev_err(&pdev->dev, "incorrect phy-mode\n"); err = -EINVAL; - goto err_put_phy_node; + goto err_free_irq; } dev->tx_queue_len = MVNETA_MAX_TXD; @@ -4018,12 +4162,7 @@ static int mvneta_probe(struct platform_device *pdev) pp = netdev_priv(dev); spin_lock_init(&pp->lock); - pp->phy_node = phy_node; - pp->phy_interface = phy_mode; - - err = of_property_read_string(dn, "managed", &managed); - pp->use_inband_status = (err == 0 && - strcmp(managed, "in-band-status") == 0); + pp->dn = dn; pp->cpu_notifier.notifier_call = mvneta_percpu_notifier; pp->rxq_def = rxq_def; @@ -4035,7 +4174,7 @@ static int mvneta_probe(struct platform_device *pdev) pp->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(pp->clk)) { err = PTR_ERR(pp->clk); - goto err_put_phy_node; + goto err_free_irq; } clk_prepare_enable(pp->clk); @@ -4143,6 +4282,14 @@ static int mvneta_probe(struct platform_device *pdev) dev->priv_flags |= IFF_UNICAST_FLT | IFF_LIVE_ADDR_CHANGE; dev->gso_max_segs = MVNETA_MAX_TSO_SEGS; + phylink = phylink_create(dev, dn, phy_mode, &mvneta_phylink_ops); + if (IS_ERR(phylink)) { + err = PTR_ERR(phylink); + goto err_free_stats; + } + + pp->phylink = phylink; + err = register_netdev(dev); if (err < 0) { dev_err(&pdev->dev, "failed to register\n"); @@ -4154,14 +4301,6 @@ static int mvneta_probe(struct platform_device *pdev) platform_set_drvdata(pdev, pp->dev); - if (pp->use_inband_status) { - struct phy_device *phy = of_phy_find_device(dn); - - mvneta_fixed_link_update(pp, phy); - - put_device(&phy->mdio.dev); - } - return 0; err_netdev: @@ -4172,14 +4311,14 @@ static int mvneta_probe(struct platform_device *pdev) 1 << pp->id); } err_free_stats: + if (pp->phylink) + phylink_destroy(pp->phylink); free_percpu(pp->stats); err_free_ports: free_percpu(pp->ports); err_clk: clk_disable_unprepare(pp->clk_bus); clk_disable_unprepare(pp->clk); -err_put_phy_node: - of_node_put(phy_node); err_free_irq: irq_dispose_mapping(dev->irq); err_free_netdev: @@ -4199,7 +4338,7 @@ static int mvneta_remove(struct platform_device *pdev) free_percpu(pp->ports); free_percpu(pp->stats); irq_dispose_mapping(dev->irq); - of_node_put(pp->phy_node); + phylink_destroy(pp->phylink); free_netdev(dev); if (pp->bm_priv) { diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 47a64342cc16..dabf1d4ca4ba 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -10,6 +10,16 @@ menuconfig PHYLIB devices. This option provides infrastructure for managing PHY devices. +config PHYLINK + tristate + depends on NETDEVICES + select PHYLIB + select SWPHY + help + PHYlink models the link between the PHY and MAC, allowing fixed + configuration links, PHYs, and Serdes links with MAC level + autonegotiation modes. + if PHYLIB config SWPHY @@ -169,6 +179,11 @@ config FIXED_PHY Currently tested with mpc866ads and mpc8349e-mitx. +config SFP + tristate "SFP cage support" + depends on I2C && PHYLINK + select MDIO_I2C + config MDIO_BITBANG tristate "Support for bitbanged MDIO buses" help @@ -190,6 +205,16 @@ config MDIO_GPIO config MDIO_CAVIUM tristate +config MDIO_I2C + tristate + depends on I2C + help + Support I2C based PHYs. This provides a MDIO bus bridged + to I2C to allow PHYs connected in I2C mode to be accessed + using the existing infrastructure. + + This is library mode. + config MDIO_OCTEON tristate "Support for MDIO buses on Octeon and some ThunderX SOCs" depends on 64BIT diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 534dfa74d5a2..7dd7b96df819 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -3,6 +3,7 @@ libphy-y := phy.o phy_device.o mdio_bus.o mdio_device.o libphy-$(CONFIG_SWPHY) += swphy.o +obj-$(CONFIG_PHYLINK) += phylink.o obj-$(CONFIG_PHYLIB) += libphy.o obj-$(CONFIG_AQUANTIA_PHY) += aquantia.o obj-$(CONFIG_MARVELL_PHY) += marvell.o @@ -25,6 +26,7 @@ obj-$(CONFIG_LSI_ET1011C_PHY) += et1011c.o obj-$(CONFIG_FIXED_PHY) += fixed_phy.o obj-$(CONFIG_MDIO_BITBANG) += mdio-bitbang.o obj-$(CONFIG_MDIO_GPIO) += mdio-gpio.o +obj-$(CONFIG_MDIO_I2C) += mdio-i2c.o obj-$(CONFIG_NATIONAL_PHY) += national.o obj-$(CONFIG_DP83640_PHY) += dp83640.o obj-$(CONFIG_DP83848_PHY) += dp83848.o @@ -49,3 +51,4 @@ obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o obj-$(CONFIG_INTEL_XWAY_PHY) += intel-xway.o obj-$(CONFIG_MDIO_HISI_FEMAC) += mdio-hisi-femac.o obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o +obj-$(CONFIG_SFP) += sfp.o diff --git a/drivers/net/phy/fixed_phy.c b/drivers/net/phy/fixed_phy.c index c649c101bbab..e9cd2fdc2d68 100644 --- a/drivers/net/phy/fixed_phy.c +++ b/drivers/net/phy/fixed_phy.c @@ -115,37 +115,6 @@ int fixed_phy_set_link_update(struct phy_device *phydev, } EXPORT_SYMBOL_GPL(fixed_phy_set_link_update); -int fixed_phy_update_state(struct phy_device *phydev, - const struct fixed_phy_status *status, - const struct fixed_phy_status *changed) -{ - struct fixed_mdio_bus *fmb = &platform_fmb; - struct fixed_phy *fp; - - if (!phydev || phydev->mdio.bus != fmb->mii_bus) - return -EINVAL; - - list_for_each_entry(fp, &fmb->phys, node) { - if (fp->addr == phydev->mdio.addr) { - write_seqcount_begin(&fp->seqcount); -#define _UPD(x) if (changed->x) \ - fp->status.x = status->x - _UPD(link); - _UPD(speed); - _UPD(duplex); - _UPD(pause); - _UPD(asym_pause); -#undef _UPD - fixed_phy_update(fp); - write_seqcount_end(&fp->seqcount); - return 0; - } - } - - return -ENOENT; -} -EXPORT_SYMBOL(fixed_phy_update_state); - int fixed_phy_add(unsigned int irq, int phy_addr, struct fixed_phy_status *status, int link_gpio) diff --git a/drivers/net/phy/marvell.c b/drivers/net/phy/marvell.c index c2dcf02df202..d0ad2c38ebfb 100644 --- a/drivers/net/phy/marvell.c +++ b/drivers/net/phy/marvell.c @@ -1523,7 +1523,7 @@ static struct phy_driver marvell_drivers[] = { .phy_id = MARVELL_PHY_ID_88E1111, .phy_id_mask = MARVELL_PHY_ID_MASK, .name = "Marvell 88E1111", - .features = PHY_GBIT_FEATURES, + .features = PHY_GBIT_FEATURES | SUPPORTED_Pause, .flags = PHY_HAS_INTERRUPT, .probe = marvell_probe, .config_init = &m88e1111_config_init, @@ -1671,7 +1671,8 @@ static struct phy_driver marvell_drivers[] = { .phy_id = MARVELL_PHY_ID_88E1510, .phy_id_mask = MARVELL_PHY_ID_MASK, .name = "Marvell 88E1510", - .features = PHY_GBIT_FEATURES | SUPPORTED_FIBRE, + .features = PHY_GBIT_FEATURES | SUPPORTED_FIBRE | + SUPPORTED_Pause, .flags = PHY_HAS_INTERRUPT, .probe = marvell_probe, .config_init = &m88e1510_config_init, @@ -1690,7 +1691,7 @@ static struct phy_driver marvell_drivers[] = { .phy_id = MARVELL_PHY_ID_88E1540, .phy_id_mask = MARVELL_PHY_ID_MASK, .name = "Marvell 88E1540", - .features = PHY_GBIT_FEATURES, + .features = PHY_GBIT_FEATURES | SUPPORTED_Pause, .flags = PHY_HAS_INTERRUPT, .probe = marvell_probe, .config_init = &marvell_config_init, diff --git a/drivers/net/phy/mdio-i2c.c b/drivers/net/phy/mdio-i2c.c new file mode 100644 index 000000000000..6d24fd13ca86 --- /dev/null +++ b/drivers/net/phy/mdio-i2c.c @@ -0,0 +1,109 @@ +/* + * MDIO I2C bridge + * + * Copyright (C) 2015-2016 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Network PHYs can appear on I2C buses when they are part of SFP module. + * This driver exposes these PHYs to the networking PHY code, allowing + * our PHY drivers access to these PHYs, and so allowing configuration + * of their settings. + */ +#include +#include + +#include "mdio-i2c.h" + +/* + * I2C bus addresses 0x50 and 0x51 are normally an EEPROM, which is + * specified to be present in SFP modules. These correspond with PHY + * addresses 16 and 17. Disallow access to these "phy" addresses. + */ +static bool i2c_mii_valid_phy_id(int phy_id) +{ + return phy_id != 0x10 && phy_id != 0x11; +} + +static unsigned int i2c_mii_phy_addr(int phy_id) +{ + return phy_id + 0x40; +} + +static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg) +{ + struct i2c_adapter *i2c = bus->priv; + struct i2c_msg msgs[2]; + u8 data[2], dev_addr = reg; + int bus_addr, ret; + + if (!i2c_mii_valid_phy_id(phy_id)) + return 0xffff; + + bus_addr = i2c_mii_phy_addr(phy_id); + msgs[0].addr = bus_addr; + msgs[0].flags = 0; + msgs[0].len = 1; + msgs[0].buf = &dev_addr; + msgs[1].addr = bus_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = sizeof(data); + msgs[1].buf = data; + + ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs)); + if (ret != ARRAY_SIZE(msgs)) + return 0xffff; + + return data[0] << 8 | data[1]; +} + +static int i2c_mii_write(struct mii_bus *bus, int phy_id, int reg, u16 val) +{ + struct i2c_adapter *i2c = bus->priv; + struct i2c_msg msg; + int ret; + u8 data[3]; + + if (!i2c_mii_valid_phy_id(phy_id)) + return 0; + + data[0] = reg; + data[1] = val >> 8; + data[2] = val; + + msg.addr = i2c_mii_phy_addr(phy_id); + msg.flags = 0; + msg.len = 3; + msg.buf = data; + + ret = i2c_transfer(i2c, &msg, 1); + + return ret < 0 ? ret : 0; +} + +struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c) +{ + struct mii_bus *mii; + + if (!i2c_check_functionality(i2c, I2C_FUNC_I2C)) + return ERR_PTR(-EINVAL); + + mii = mdiobus_alloc(); + if (!mii) + return ERR_PTR(-ENOMEM); + + snprintf(mii->id, MII_BUS_ID_SIZE, "i2c:%s", dev_name(parent)); + mii->parent = parent; + mii->read = i2c_mii_read; + mii->write = i2c_mii_write; + mii->priv = i2c; + + return mii; +} +EXPORT_SYMBOL_GPL(mdio_i2c_alloc); + +MODULE_AUTHOR("Russell King"); +MODULE_DESCRIPTION("MDIO I2C bridge library"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/net/phy/mdio-i2c.h b/drivers/net/phy/mdio-i2c.h new file mode 100644 index 000000000000..889ab57d7f3e --- /dev/null +++ b/drivers/net/phy/mdio-i2c.h @@ -0,0 +1,19 @@ +/* + * MDIO I2C bridge + * + * Copyright (C) 2015 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef MDIO_I2C_H +#define MDIO_I2C_H + +struct device; +struct i2c_adapter; +struct mii_bus; + +struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c); + +#endif diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c index c5dc2c363f96..45d812ba7270 100644 --- a/drivers/net/phy/phy.c +++ b/drivers/net/phy/phy.c @@ -38,7 +38,7 @@ #include -static const char *phy_speed_to_str(int speed) +const char *phy_speed_to_str(int speed) { switch (speed) { case SPEED_10: @@ -57,6 +57,7 @@ static const char *phy_speed_to_str(int speed) return "Unsupported (update phy.c)"; } } +EXPORT_SYMBOL_GPL(phy_speed_to_str); #define PHY_STATE_STR(_state) \ case PHY_##_state: \ @@ -606,6 +607,7 @@ void phy_start_machine(struct phy_device *phydev) { queue_delayed_work(system_power_efficient_wq, &phydev->state_queue, HZ); } +EXPORT_SYMBOL_GPL(phy_start_machine); /** * phy_stop_machine - stop the PHY state machine tracking @@ -891,6 +893,16 @@ void phy_start(struct phy_device *phydev) } EXPORT_SYMBOL(phy_start); +static void phy_link_up(struct phy_device *phydev) +{ + phydev->phy_link_change(phydev, true, true); +} + +static void phy_link_down(struct phy_device *phydev, bool do_carrier) +{ + phydev->phy_link_change(phydev, false, do_carrier); +} + /** * phy_state_machine - Handle the state machine * @work: work_struct that describes the work to be done @@ -932,8 +944,7 @@ void phy_state_machine(struct work_struct *work) /* If the link is down, give up on negotiation for now */ if (!phydev->link) { phydev->state = PHY_NOLINK; - netif_carrier_off(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); + phy_link_down(phydev, true); break; } @@ -945,9 +956,7 @@ void phy_state_machine(struct work_struct *work) /* If AN is done, we're running */ if (err > 0) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); - + phy_link_up(phydev); } else if (0 == phydev->link_timeout--) needs_aneg = true; break; @@ -972,8 +981,7 @@ void phy_state_machine(struct work_struct *work) } } phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); + phy_link_up(phydev); } break; case PHY_FORCING: @@ -983,13 +991,12 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); + phy_link_up(phydev); } else { if (0 == phydev->link_timeout--) needs_aneg = true; + phy_link_down(phydev, false); } - - phydev->adjust_link(phydev->attached_dev); break; case PHY_RUNNING: /* Only register a CHANGE if we are polling and link changed @@ -1012,14 +1019,12 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); + phy_link_up(phydev); } else { phydev->state = PHY_NOLINK; - netif_carrier_off(phydev->attached_dev); + phy_link_down(phydev, true); } - phydev->adjust_link(phydev->attached_dev); - if (phy_interrupt_is_valid(phydev)) err = phy_config_interrupt(phydev, PHY_INTERRUPT_ENABLED); @@ -1027,8 +1032,7 @@ void phy_state_machine(struct work_struct *work) case PHY_HALTED: if (phydev->link) { phydev->link = 0; - netif_carrier_off(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); + phy_link_down(phydev, true); do_suspend = true; } break; @@ -1048,11 +1052,11 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); + phy_link_up(phydev); } else { phydev->state = PHY_NOLINK; + phy_link_down(phydev, false); } - phydev->adjust_link(phydev->attached_dev); } else { phydev->state = PHY_AN; phydev->link_timeout = PHY_AN_TIMEOUT; @@ -1064,11 +1068,11 @@ void phy_state_machine(struct work_struct *work) if (phydev->link) { phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); + phy_link_up(phydev); } else { phydev->state = PHY_NOLINK; + phy_link_down(phydev, false); } - phydev->adjust_link(phydev->attached_dev); } break; } diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index e977ba931878..1877889e0439 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -645,6 +645,19 @@ struct phy_device *phy_find_first(struct mii_bus *bus) } EXPORT_SYMBOL(phy_find_first); +static void phy_link_change(struct phy_device *phydev, bool up, bool do_carrier) +{ + struct net_device *netdev = phydev->attached_dev; + + if (do_carrier) { + if (up) + netif_carrier_on(netdev); + else + netif_carrier_off(netdev); + } + phydev->adjust_link(netdev); +} + /** * phy_prepare_link - prepares the PHY layer to monitor link status * @phydev: target phy_device struct @@ -892,6 +905,7 @@ int phy_attach_direct(struct net_device *dev, struct phy_device *phydev, goto error; } + phydev->phy_link_change = phy_link_change; phydev->attached_dev = dev; dev->phydev = phydev; @@ -975,6 +989,7 @@ void phy_detach(struct phy_device *phydev) phydev->attached_dev->phydev = NULL; phydev->attached_dev = NULL; phy_suspend(phydev); + phydev->phylink = NULL; /* If the device had no specific driver before (i.e. - it * was using the generic driver), we unbind the device diff --git a/drivers/net/phy/phylink.c b/drivers/net/phy/phylink.c new file mode 100644 index 000000000000..6a18947e151c --- /dev/null +++ b/drivers/net/phy/phylink.c @@ -0,0 +1,1151 @@ +/* + * phylink models the MAC to optional PHY connection, supporting + * technologies such as SFP cages where the PHY is hot-pluggable. + * + * Copyright (C) 2015 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "swphy.h" + +#define SUPPORTED_INTERFACES \ + (SUPPORTED_TP | SUPPORTED_MII | SUPPORTED_FIBRE | \ + SUPPORTED_BNC | SUPPORTED_AUI | SUPPORTED_Backplane) +#define ADVERTISED_INTERFACES \ + (ADVERTISED_TP | ADVERTISED_MII | ADVERTISED_FIBRE | \ + ADVERTISED_BNC | ADVERTISED_AUI | ADVERTISED_Backplane) + +static LIST_HEAD(phylinks); +static DEFINE_MUTEX(phylink_mutex); + +enum { + PHYLINK_DISABLE_STOPPED, + PHYLINK_DISABLE_LINK, +}; + +struct phylink { + struct list_head node; + struct net_device *netdev; + const struct phylink_mac_ops *ops; + struct mutex config_mutex; + + unsigned long phylink_disable_state; /* bitmask of disables */ + struct phy_device *phydev; + phy_interface_t link_interface; /* PHY_INTERFACE_xxx */ + u8 link_an_mode; /* MLO_AN_xxx */ + u8 link_port; /* The current non-phy ethtool port */ + u32 link_port_support; /* SUPPORTED_xxx ethtool for ports */ + + /* The link configuration settings */ + struct phylink_link_state link_config; + struct gpio_desc *link_gpio; + + struct mutex state_mutex; /* may be taken within config_mutex */ + struct phylink_link_state phy_state; + struct work_struct resolve; + + bool mac_link_up; + + const struct phylink_module_ops *module_ops; + void *module_data; +}; + +static const char *phylink_an_mode_str(unsigned int mode) +{ + static const char *modestr[] = { + [MLO_AN_PHY] = "phy", + [MLO_AN_FIXED] = "fixed", + [MLO_AN_SGMII] = "SGMII", + [MLO_AN_8023Z] = "802.3z", + }; + + return mode < ARRAY_SIZE(modestr) ? modestr[mode] : "unknown"; +} + +static int phylink_parse_fixedlink(struct phylink *pl, struct device_node *np) +{ + struct device_node *fixed_node; + int ret, len; + + fixed_node = of_get_child_by_name(np, "fixed-link"); + if (fixed_node) { + struct gpio_desc *desc; + u32 speed; + + ret = of_property_read_u32(fixed_node, "speed", &speed); + + pl->link_an_mode = MLO_AN_FIXED; + pl->link_config.link = 1; + pl->link_config.an_complete = 1; + pl->link_config.speed = speed; + pl->link_config.duplex = DUPLEX_HALF; + + if (of_property_read_bool(fixed_node, "full-duplex")) + pl->link_config.duplex = DUPLEX_FULL; + + /* We treat the "pause" and "asym-pause" terminology as + * defining the link partner's ability. */ + if (of_property_read_bool(fixed_node, "pause")) + pl->link_config.pause |= MLO_PAUSE_SYM; + if (of_property_read_bool(fixed_node, "asym-pause")) + pl->link_config.pause |= MLO_PAUSE_ASYM; + + if (ret == 0) { + desc = fwnode_get_named_gpiod(&fixed_node->fwnode, + "link-gpios"); + + if (!IS_ERR(desc)) + pl->link_gpio = desc; + else if (desc == ERR_PTR(-EPROBE_DEFER)) + ret = -EPROBE_DEFER; + } + of_node_put(fixed_node); + } else { + const __be32 *fixed_prop; + + fixed_prop = of_get_property(np, "fixed-link", &len); + if (fixed_prop && len == 5 * sizeof(*fixed_prop)) { + pl->link_config.duplex = be32_to_cpu(fixed_prop[1]) ? + DUPLEX_FULL : DUPLEX_HALF; + pl->link_config.speed = be32_to_cpu(fixed_prop[2]); + if (be32_to_cpu(fixed_prop[3])) + pl->link_config.pause |= MLO_PAUSE_SYM; + if (be32_to_cpu(fixed_prop[4])) + pl->link_config.pause |= MLO_PAUSE_ASYM; + + pl->link_an_mode = MLO_AN_FIXED; + } + ret = 0; + } + + if (pl->link_an_mode == MLO_AN_FIXED) { + if (pl->link_config.speed > SPEED_1000 && + pl->link_config.duplex != DUPLEX_FULL) + netdev_warn(pl->netdev, "fixed link specifies half duplex for %dMbps link?\n", + pl->link_config.speed); + +#define S(spd) \ + pl->link_config.supported |= pl->link_config.duplex ? \ + SUPPORTED_##spd##_Full : SUPPORTED_##spd##_Half +#define A(spd) \ + pl->link_config.advertising |= pl->link_config.duplex ? \ + ADVERTISED_##spd##_Full : ADVERTISED_##spd##_Half +#define C(spd, tech) \ + case spd: \ + S(spd##tech); \ + A(spd##tech); \ + break + switch (pl->link_config.speed) { + C(10, baseT); + C(100, baseT); + C(1000, baseT); +#undef S +#undef A +#define S(spd) pl->link_config.supported |= SUPPORTED_##spd##_Full +#define A(spd) pl->link_config.advertising |= ADVERTISED_##spd##_Full + C(2500, baseX); + C(10000, baseT); + } +#undef S +#undef A +#undef C + } + return ret; +} + +static int phylink_parse_managed(struct phylink *pl, struct device_node *np) +{ + const char *managed; + + if (of_property_read_string(np, "managed", &managed) == 0 && + strcmp(managed, "in-band-status") == 0) { + if (pl->link_an_mode == MLO_AN_FIXED) { + netdev_err(pl->netdev, + "can't use both fixed-link and in-band-status\n"); + return -EINVAL; + } + pl->link_an_mode = MLO_AN_SGMII; + pl->link_config.an_enabled = true; + } + + return 0; +} + + +static int phylink_get_support(struct phylink *pl, unsigned int mode) +{ + struct phylink_link_state state = pl->link_config; + int ret; + + ret = pl->ops->mac_get_support(pl->netdev, mode, &state); + if (ret == 0) { + pl->link_an_mode = mode; + pl->link_config = state; + } + + return ret; +} + +static void phylink_mac_config(struct phylink *pl, + const struct phylink_link_state *state) +{ + pl->ops->mac_config(pl->netdev, pl->link_an_mode, state); +} + +static void phylink_mac_an_restart(struct phylink *pl) +{ + if (pl->link_config.an_enabled) + pl->ops->mac_an_restart(pl->netdev, pl->link_an_mode); +} + +static int phylink_get_mac_state(struct phylink *pl, struct phylink_link_state *state) +{ + struct net_device *ndev = pl->netdev; + + state->supported = pl->link_config.supported; + state->advertising = pl->link_config.advertising; + state->an_enabled = pl->link_config.an_enabled; + state->link = 1; + state->sync = 1; + + return pl->ops->mac_link_state(ndev, state); +} + +/* The fixed state is... fixed except for the link state, + * which may be determined by a GPIO. + */ +static void phylink_get_fixed_state(struct phylink *pl, struct phylink_link_state *state) +{ + *state = pl->link_config; + if (pl->link_gpio) + state->link = !!gpiod_get_value(pl->link_gpio); +} + +/* Flow control is resolved according to our and the link partners + * advertisments using the following drawn from the 802.3 specs: + * Local device Link partner + * Pause AsymDir Pause AsymDir Result + * 1 X 1 X TX+RX + * 0 1 1 1 RX + * 1 1 0 1 TX + */ +static void phylink_resolve_flow(struct phylink *pl, + struct phylink_link_state *state) +{ + int new_pause = 0; + + if (pl->link_config.pause & MLO_PAUSE_AN) { + int pause = 0; + + if (pl->link_config.advertising & ADVERTISED_Pause) + pause |= MLO_PAUSE_SYM; + if (pl->link_config.advertising & ADVERTISED_Asym_Pause) + pause |= MLO_PAUSE_ASYM; + + pause &= state->pause; + + if (pause & MLO_PAUSE_SYM) + new_pause = MLO_PAUSE_TX | MLO_PAUSE_RX; + else if (pause & MLO_PAUSE_ASYM) + new_pause = state->pause & MLO_PAUSE_SYM ? + MLO_PAUSE_RX : MLO_PAUSE_TX; + } else { + new_pause = pl->link_config.pause & MLO_PAUSE_TXRX_MASK; + } + + state->pause &= ~MLO_PAUSE_TXRX_MASK; + state->pause |= new_pause; +} + +static const char *phylink_pause_to_str(int pause) +{ + switch (pause & MLO_PAUSE_TXRX_MASK) { + case MLO_PAUSE_TX | MLO_PAUSE_RX: + return "rx/tx"; + case MLO_PAUSE_TX: + return "tx"; + case MLO_PAUSE_RX: + return "rx"; + default: + return "off"; + } +} + +extern const char *phy_speed_to_str(int speed); + +static void phylink_resolve(struct work_struct *w) +{ + struct phylink *pl = container_of(w, struct phylink, resolve); + struct phylink_link_state link_state; + struct net_device *ndev = pl->netdev; + + mutex_lock(&pl->state_mutex); + if (pl->phylink_disable_state) { + link_state.link = false; + } else { + switch (pl->link_an_mode) { + case MLO_AN_PHY: + link_state = pl->phy_state; + phylink_resolve_flow(pl, &link_state); + break; + + case MLO_AN_FIXED: + phylink_get_fixed_state(pl, &link_state); + break; + + case MLO_AN_SGMII: + phylink_get_mac_state(pl, &link_state); + if (pl->phydev) { + link_state.link = link_state.link && + pl->phy_state.link; + link_state.pause |= pl->phy_state.pause; + phylink_resolve_flow(pl, &link_state); + } + break; + + case MLO_AN_8023Z: + phylink_get_mac_state(pl, &link_state); + break; + } + } + + if (link_state.link != netif_carrier_ok(ndev)) { + if (!link_state.link) { + netif_carrier_off(ndev); + pl->ops->mac_link_down(ndev, pl->link_an_mode); + netdev_info(ndev, "Link is Down\n"); + } else { + /* If we have a PHY, we need the MAC updated with + * the current link parameters (eg, in SGMII mode, + * with flow control status.) + */ + if (pl->phydev) + phylink_mac_config(pl, &link_state); + + pl->ops->mac_link_up(ndev, pl->link_an_mode, + pl->phydev); + + netif_carrier_on(ndev); + + netdev_info(ndev, + "Link is Up - %s/%s - flow control %s\n", + phy_speed_to_str(link_state.speed), + link_state.duplex ? "Full" : "Half", + phylink_pause_to_str(link_state.pause)); + } + } + mutex_unlock(&pl->state_mutex); +} + +static void phylink_run_resolve(struct phylink *pl) +{ + if (!pl->phylink_disable_state) + queue_work(system_power_efficient_wq, &pl->resolve); +} + +struct phylink *phylink_create(struct net_device *ndev, struct device_node *np, + phy_interface_t iface, const struct phylink_mac_ops *ops) +{ + struct phylink *pl; + int ret; + + pl = kzalloc(sizeof(*pl), GFP_KERNEL); + if (!pl) + return ERR_PTR(-ENOMEM); + + mutex_init(&pl->state_mutex); + mutex_init(&pl->config_mutex); + INIT_WORK(&pl->resolve, phylink_resolve); + pl->netdev = ndev; + pl->link_interface = iface; + pl->link_port_support = SUPPORTED_MII; + pl->link_port = PORT_MII; + pl->link_config.pause = MLO_PAUSE_AN; + pl->ops = ops; + __set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); + + ret = phylink_parse_fixedlink(pl, np); + if (ret < 0) { + kfree(pl); + return ERR_PTR(ret); + } + + ret = phylink_parse_managed(pl, np); + if (ret < 0) { + kfree(pl); + return ERR_PTR(ret); + } + + ret = phylink_get_support(pl, pl->link_an_mode); + if (ret) { + kfree(pl); + return ERR_PTR(ret); + } + + mutex_lock(&phylink_mutex); + list_add_tail(&pl->node, &phylinks); + mutex_unlock(&phylink_mutex); + + return pl; +} +EXPORT_SYMBOL_GPL(phylink_create); + +void phylink_destroy(struct phylink *pl) +{ + mutex_lock(&phylink_mutex); + list_del(&pl->node); + mutex_unlock(&phylink_mutex); + + cancel_work_sync(&pl->resolve); + kfree(pl); +} +EXPORT_SYMBOL_GPL(phylink_destroy); + +void phylink_phy_change(struct phy_device *phy, bool up, bool do_carrier) +{ + struct phylink *pl = phy->phylink; + + mutex_lock(&pl->state_mutex); + pl->phy_state.speed = phy->speed; + pl->phy_state.duplex = phy->duplex; + pl->phy_state.pause = MLO_PAUSE_NONE; + if (phy->pause) + pl->phy_state.pause |= MLO_PAUSE_SYM; + if (phy->asym_pause) + pl->phy_state.pause |= MLO_PAUSE_ASYM; + pl->phy_state.link = up; + mutex_unlock(&pl->state_mutex); + + phylink_run_resolve(pl); + + netdev_dbg(pl->netdev, "phy link %s\n", up ? "up" : "down"); +} + +static int phylink_bringup_phy(struct phylink *pl, struct phy_device *phy) +{ + mutex_lock(&pl->config_mutex); + phy->phylink = pl; + phy->phy_link_change = phylink_phy_change; + + netdev_info(pl->netdev, + "PHY [%s] driver [%s]\n", dev_name(&phy->mdio.dev), + phy->drv->name); + + mutex_lock(&pl->state_mutex); + pl->phydev = phy; + + /* Restrict the phy advertisment to the union of the PHY and + * MAC-level advert. + */ + phy->advertising &= ADVERTISED_INTERFACES | + pl->link_config.advertising; + mutex_unlock(&pl->state_mutex); + + phy_start_machine(phy); + if (phy->irq > 0) + phy_start_interrupts(phy); + + mutex_unlock(&pl->config_mutex); + + return 0; +} + +int phylink_connect_phy(struct phylink *pl, struct phy_device *phy) +{ + int ret; + + ret = phy_attach_direct(pl->netdev, phy, 0, pl->link_interface); + if (ret) + return ret; + + ret = phylink_bringup_phy(pl, phy); + if (ret) + phy_detach(phy); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_connect_phy); + +int phylink_of_phy_connect(struct phylink *pl, struct device_node *dn) +{ + struct device_node *phy_node; + struct phy_device *phy_dev; + int ret; + + /* Fixed links are handled without needing a PHY */ + if (pl->link_an_mode == MLO_AN_FIXED) + return 0; + + phy_node = of_parse_phandle(dn, "phy-handle", 0); + if (!phy_node) + phy_node = of_parse_phandle(dn, "phy", 0); + if (!phy_node) + phy_node = of_parse_phandle(dn, "phy-device", 0); + + if (!phy_node) { + if (pl->link_an_mode == MLO_AN_PHY) { + netdev_err(pl->netdev, "unable to find PHY node\n"); + return -ENODEV; + } + return 0; + } + + phy_dev = of_phy_attach(pl->netdev, phy_node, 0, pl->link_interface); + /* We're done with the phy_node handle */ + of_node_put(phy_node); + + if (!phy_dev) + return -ENODEV; + + ret = phylink_bringup_phy(pl, phy_dev); + if (ret) + phy_detach(phy_dev); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_of_phy_connect); + +void phylink_disconnect_phy(struct phylink *pl) +{ + struct phy_device *phy; + + mutex_lock(&pl->config_mutex); + phy = pl->phydev; + + mutex_lock(&pl->state_mutex); + pl->phydev = NULL; + mutex_unlock(&pl->state_mutex); + flush_work(&pl->resolve); + + if (phy) + phy_disconnect(phy); + + mutex_unlock(&pl->config_mutex); +} +EXPORT_SYMBOL_GPL(phylink_disconnect_phy); + +void phylink_mac_change(struct phylink *pl, bool up) +{ + phylink_run_resolve(pl); + netdev_dbg(pl->netdev, "mac link %s\n", up ? "up" : "down"); +} +EXPORT_SYMBOL_GPL(phylink_mac_change); + +void phylink_start(struct phylink *pl) +{ + mutex_lock(&pl->config_mutex); + + netdev_info(pl->netdev, "configuring for %s link mode\n", + phylink_an_mode_str(pl->link_an_mode)); + + /* Apply the link configuration to the MAC when starting. This allows + * a fixed-link to start with the correct parameters, and also + * ensures that we set the appropriate advertisment for Serdes links. + */ + phylink_resolve_flow(pl, &pl->link_config); + phylink_mac_config(pl, &pl->link_config); + + clear_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); + phylink_run_resolve(pl); + + if (pl->phydev) + phy_start(pl->phydev); + + mutex_unlock(&pl->config_mutex); +} +EXPORT_SYMBOL_GPL(phylink_start); + +void phylink_stop(struct phylink *pl) +{ + mutex_lock(&pl->config_mutex); + + if (pl->phydev) + phy_stop(pl->phydev); + + set_bit(PHYLINK_DISABLE_STOPPED, &pl->phylink_disable_state); + flush_work(&pl->resolve); + + pl->mac_link_up = false; + + mutex_unlock(&pl->config_mutex); +} +EXPORT_SYMBOL_GPL(phylink_stop); + +static void phylink_get_ethtool(const struct phylink_link_state *state, + struct ethtool_cmd *cmd) +{ + cmd->supported &= SUPPORTED_INTERFACES; + cmd->supported |= state->supported; + cmd->advertising &= ADVERTISED_INTERFACES; + cmd->advertising |= state->advertising; + ethtool_cmd_speed_set(cmd, state->speed); + cmd->duplex = state->duplex; + + cmd->autoneg = state->an_enabled ? AUTONEG_ENABLE : AUTONEG_DISABLE; +} + +static int phylink_ethtool_gset(struct phylink *pl, struct ethtool_cmd *cmd) +{ + struct phylink_link_state link_state; + int ret; + + if (pl->phydev) { + ret = phy_ethtool_gset(pl->phydev, cmd); + if (ret) + return ret; + + cmd->supported &= SUPPORTED_INTERFACES | + pl->link_config.supported; + } else { + cmd->supported = pl->link_port_support; + cmd->transceiver = XCVR_EXTERNAL; + cmd->port = pl->link_port; + } + + switch (pl->link_an_mode) { + case MLO_AN_FIXED: + /* We are using fixed settings. Report these as the + * current link settings - and note that these also + * represent the supported speeds/duplex/pause modes. + */ + phylink_get_fixed_state(pl, &link_state); + phylink_get_ethtool(&link_state, cmd); + break; + + case MLO_AN_SGMII: + /* If there is a phy attached, then use the reported + * settings from the phy with no modification. + */ + if (pl->phydev) + break; + + case MLO_AN_8023Z: + phylink_get_mac_state(pl, &link_state); + + /* The MAC is reporting the link results from its own PCS + * layer via in-band status. Report these as the current + * link settings. + */ + phylink_get_ethtool(&link_state, cmd); + break; + } + + return 0; +} + +int phylink_ethtool_get_settings(struct phylink *pl, struct ethtool_cmd *cmd) +{ + int ret; + + mutex_lock(&pl->config_mutex); + ret = phylink_ethtool_gset(pl, cmd); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_settings); + +static int phylink_ethtool_sset(struct phylink *pl, struct ethtool_cmd *cmd) +{ + u32 supported; + int ret; + + /* Calculate the union of the MAC support and attached phy support */ + supported = pl->link_config.supported; + if (pl->phydev) + supported &= pl->phydev->supported; + + /* Mask out unsupported advertisments */ + cmd->advertising &= supported; + + /* FIXME: should we reject autoneg if phy/mac does not support it? */ + + if (cmd->autoneg == AUTONEG_DISABLE) { + /* Autonegotiation disabled, validate speed and duplex */ + if (cmd->duplex != DUPLEX_HALF && cmd->duplex != DUPLEX_FULL) + return -EINVAL; + + /* FIXME: validate speed/duplex against supported */ + + cmd->advertising &= ~ADVERTISED_Autoneg; + } else { + /* Autonegotiation enabled, validate advertisment */ + /* FIXME: shouldn't we ensure there's some duplex/speeds set */ + if (cmd->advertising == 0) + return -EINVAL; + + cmd->advertising |= ADVERTISED_Autoneg; + } + + /* If we have a fixed link (as specified by firmware), refuse + * to enable autonegotiation, or change link parameters. + */ + if (pl->link_an_mode == MLO_AN_FIXED) { + if (cmd->autoneg != AUTONEG_DISABLE || + ethtool_cmd_speed(cmd) != pl->link_config.speed || + cmd->duplex != pl->link_config.duplex) + return -EINVAL; + } + + /* If we have a PHY, configure the phy */ + if (pl->phydev) { + ret = phy_ethtool_sset(pl->phydev, cmd); + if (ret) + return ret; + } + + mutex_lock(&pl->state_mutex); + /* Configure the MAC to match the new settings */ + pl->link_config.advertising = cmd->advertising; + pl->link_config.speed = cmd->speed; + pl->link_config.duplex = cmd->duplex; + pl->link_config.an_enabled = cmd->autoneg != AUTONEG_DISABLE; + + phylink_mac_config(pl, &pl->link_config); + phylink_mac_an_restart(pl); + mutex_unlock(&pl->state_mutex); + + return ret; +} + +int phylink_ethtool_set_settings(struct phylink *pl, struct ethtool_cmd *cmd) +{ + int ret; + + if (cmd->autoneg != AUTONEG_DISABLE && cmd->autoneg != AUTONEG_ENABLE) + return -EINVAL; + + mutex_lock(&pl->config_mutex); + ret = phylink_ethtool_sset(pl, cmd); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_set_settings); + +int phylink_ethtool_nway_reset(struct phylink *pl) +{ + int ret = 0; + + mutex_lock(&pl->config_mutex); + if (pl->phydev) + ret = genphy_restart_aneg(pl->phydev); + phylink_mac_an_restart(pl); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_nway_reset); + +void phylink_ethtool_get_pauseparam(struct phylink *pl, + struct ethtool_pauseparam *pause) +{ + mutex_lock(&pl->config_mutex); + + pause->autoneg = !!(pl->link_config.pause & MLO_PAUSE_AN); + pause->rx_pause = !!(pl->link_config.pause & MLO_PAUSE_RX); + pause->tx_pause = !!(pl->link_config.pause & MLO_PAUSE_TX); + + mutex_unlock(&pl->config_mutex); +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_pauseparam); + +static int __phylink_ethtool_set_pauseparam(struct phylink *pl, + struct ethtool_pauseparam *pause) +{ + struct phylink_link_state *config = &pl->link_config; + + if (!(config->supported & (SUPPORTED_Pause | SUPPORTED_Asym_Pause))) + return -EOPNOTSUPP; + + if (!(config->supported & SUPPORTED_Asym_Pause) && + !pause->autoneg && pause->rx_pause != pause->tx_pause) + return -EINVAL; + + config->pause &= ~(MLO_PAUSE_AN | MLO_PAUSE_TXRX_MASK); + + if (pause->autoneg) + config->pause |= MLO_PAUSE_AN; + if (pause->rx_pause) + config->pause |= MLO_PAUSE_RX; + if (pause->tx_pause) + config->pause |= MLO_PAUSE_TX; + + switch (pl->link_an_mode) { + case MLO_AN_PHY: + /* Silently mark the carrier down, and then trigger a resolve */ + netif_carrier_off(pl->netdev); + phylink_run_resolve(pl); + break; + + case MLO_AN_FIXED: + /* Should we allow fixed links to change against the config? */ + phylink_resolve_flow(pl, config); + phylink_mac_config(pl, config); + break; + + case MLO_AN_SGMII: + case MLO_AN_8023Z: + phylink_mac_config(pl, config); + phylink_mac_an_restart(pl); + break; + } + + return 0; +} + +int phylink_ethtool_set_pauseparam(struct phylink *pl, + struct ethtool_pauseparam *pause) +{ + int ret; + + mutex_lock(&pl->config_mutex); + ret = __phylink_ethtool_set_pauseparam(pl, pause); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_set_pauseparam); + +int phylink_ethtool_get_module_info(struct phylink *pl, + struct ethtool_modinfo *modinfo) +{ + int ret = -EOPNOTSUPP; + + mutex_lock(&pl->config_mutex); + if (pl->module_ops) + ret = pl->module_ops->get_module_info(pl->module_data, + modinfo); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_info); + +int phylink_ethtool_get_module_eeprom(struct phylink *pl, + struct ethtool_eeprom *ee, u8 *buf) +{ + int ret = -EOPNOTSUPP; + + mutex_lock(&pl->config_mutex); + if (pl->module_ops) + ret = pl->module_ops->get_module_eeprom(pl->module_data, ee, + buf); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_module_eeprom); + +int phylink_init_eee(struct phylink *pl, bool clk_stop_enable) +{ + int ret = -EPROTONOSUPPORT; + + mutex_lock(&pl->config_mutex); + if (pl->phydev) + ret = phy_init_eee(pl->phydev, clk_stop_enable); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_init_eee); + +int phylink_get_eee_err(struct phylink *pl) +{ + int ret = 0; + + mutex_lock(&pl->config_mutex); + if (pl->phydev) + ret = phy_get_eee_err(pl->phydev); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_get_eee_err); + +int phylink_ethtool_get_eee(struct phylink *pl, struct ethtool_eee *eee) +{ + int ret = -EOPNOTSUPP; + + mutex_lock(&pl->config_mutex); + if (pl->phydev) + ret = phy_ethtool_get_eee(pl->phydev, eee); + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_get_eee); + +int phylink_ethtool_set_eee(struct phylink *pl, struct ethtool_eee *eee) +{ + int ret = -EOPNOTSUPP; + + mutex_lock(&pl->config_mutex); + if (pl->phydev) { + ret = phy_ethtool_set_eee(pl->phydev, eee); + if (ret == 0 && eee->eee_enabled) + phy_start_aneg(pl->phydev); + } + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_ethtool_set_eee); + +/* This emulates MII registers for a fixed-mode phy operating as per the + * passed in state. "aneg" defines if we report negotiation is possible. + * + * FIXME: should deal with negotiation state too. + */ +static int phylink_mii_emul_read(struct net_device *ndev, unsigned int reg, + struct phylink_link_state *state, bool aneg) +{ + struct fixed_phy_status fs; + int val; + + fs.link = state->link; + fs.speed = state->speed; + fs.duplex = state->duplex; + fs.pause = state->pause & MLO_PAUSE_SYM; + fs.asym_pause = state->pause & MLO_PAUSE_ASYM; + + val = swphy_read_reg(reg, &fs); + if (reg == MII_BMSR) { + if (!state->an_complete) + val &= ~BMSR_ANEGCOMPLETE; + if (!aneg) + val &= ~BMSR_ANEGCAPABLE; + } + return val; +} + +static int phylink_mii_read(struct phylink *pl, unsigned int phy_id, + unsigned int reg) +{ + struct phylink_link_state state; + int val = 0xffff; + + if (pl->phydev && pl->phydev->mdio.addr != phy_id) + return mdiobus_read(pl->phydev->mdio.bus, phy_id, reg); + + if (!pl->phydev && phy_id != 0) + return val; + + switch (pl->link_an_mode) { + case MLO_AN_FIXED: + phylink_get_fixed_state(pl, &state); + val = phylink_mii_emul_read(pl->netdev, reg, &state, true); + break; + + case MLO_AN_PHY: + val = mdiobus_read(pl->phydev->mdio.bus, phy_id, reg); + break; + + case MLO_AN_SGMII: + if (pl->phydev) { + val = mdiobus_read(pl->phydev->mdio.bus, + pl->phydev->mdio.addr, reg); + break; + } + /* No phy, fall through to reading the MAC end */ + case MLO_AN_8023Z: + val = phylink_get_mac_state(pl, &state); + if (val < 0) + return val; + + val = phylink_mii_emul_read(pl->netdev, reg, &state, true); + break; + } + + return val & 0xffff; +} + +static void phylink_mii_write(struct phylink *pl, unsigned int phy_id, + unsigned int reg, unsigned int val) +{ + if (pl->phydev && pl->phydev->mdio.addr != phy_id) { + mdiobus_write(pl->phydev->mdio.bus, phy_id, reg, val); + return; + } + + if (!pl->phydev && phy_id != 0) + return; + + switch (pl->link_an_mode) { + case MLO_AN_FIXED: + break; + + case MLO_AN_PHY: + mdiobus_write(pl->phydev->mdio.bus, pl->phydev->mdio.addr, + reg, val); + break; + + case MLO_AN_SGMII: + if (pl->phydev) { + mdiobus_write(pl->phydev->mdio.bus, phy_id, reg, val); + break; + } + /* No phy, fall through to reading the MAC end */ + case MLO_AN_8023Z: + break; + } +} + +int phylink_mii_ioctl(struct phylink *pl, struct ifreq *ifr, int cmd) +{ + struct mii_ioctl_data *mii_data = if_mii(ifr); + int val, ret; + + mutex_lock(&pl->config_mutex); + + switch (cmd) { + case SIOCGMIIPHY: + mii_data->phy_id = pl->phydev ? pl->phydev->mdio.addr : 0; + /* fallthrough */ + + case SIOCGMIIREG: + val = phylink_mii_read(pl, mii_data->phy_id, mii_data->reg_num); + if (val < 0) { + ret = val; + } else { + mii_data->val_out = val; + ret = 0; + } + break; + + case SIOCSMIIREG: + phylink_mii_write(pl, mii_data->phy_id, mii_data->reg_num, + mii_data->val_in); + ret = 0; + break; + + default: + ret = -EOPNOTSUPP; + if (pl->phydev) + ret = phy_mii_ioctl(pl->phydev, ifr, cmd); + break; + } + + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_mii_ioctl); + + + +int phylink_register_module(struct phylink *pl, void *data, + const struct phylink_module_ops *ops) +{ + int ret = -EBUSY; + + mutex_lock(&pl->config_mutex); + if (!pl->module_ops) { + pl->module_ops = ops; + pl->module_data = data; + ret = 0; + } + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_register_module); + +int phylink_unregister_module(struct phylink *pl, void *data) +{ + int ret = -EINVAL; + + mutex_lock(&pl->config_mutex); + if (pl->module_data == data) { + pl->module_ops = NULL; + pl->module_data = NULL; + ret = 0; + } + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_unregister_module); + +void phylink_disable(struct phylink *pl) +{ + set_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state); + flush_work(&pl->resolve); + + netif_carrier_off(pl->netdev); +} +EXPORT_SYMBOL_GPL(phylink_disable); + +void phylink_enable(struct phylink *pl) +{ + clear_bit(PHYLINK_DISABLE_LINK, &pl->phylink_disable_state); + phylink_run_resolve(pl); +} +EXPORT_SYMBOL_GPL(phylink_enable); + +void phylink_set_link_port(struct phylink *pl, u32 support, u8 port) +{ + WARN_ON(support & ~SUPPORTED_INTERFACES); + + mutex_lock(&pl->config_mutex); + pl->link_port_support = support; + pl->link_port = port; + mutex_unlock(&pl->config_mutex); +} +EXPORT_SYMBOL_GPL(phylink_set_link_port); + +int phylink_set_link_an_mode(struct phylink *pl, unsigned int mode) +{ + int ret = 0; + + mutex_lock(&pl->config_mutex); + if (pl->link_an_mode != mode) { + ret = phylink_get_support(pl, mode); + if (ret == 0) { + if (!test_bit(PHYLINK_DISABLE_STOPPED, + &pl->phylink_disable_state)) + phylink_mac_config(pl, &pl->link_config); + + netdev_info(pl->netdev, "switched to %s link mode\n", + phylink_an_mode_str(mode)); + } + } + mutex_unlock(&pl->config_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(phylink_set_link_an_mode); + +struct phylink *phylink_lookup_by_netdev(struct net_device *ndev) +{ + struct phylink *pl, *found = NULL; + + mutex_lock(&phylink_mutex); + list_for_each_entry(pl, &phylinks, node) + if (pl->netdev == ndev) { + found = pl; + break; + } + + mutex_unlock(&phylink_mutex); + + return found; +} +EXPORT_SYMBOL_GPL(phylink_lookup_by_netdev); + +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c new file mode 100644 index 000000000000..de6251701340 --- /dev/null +++ b/drivers/net/phy/sfp.c @@ -0,0 +1,1234 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdio-i2c.h" +#include "swphy.h" + +enum { + GPIO_MODDEF0, + GPIO_LOS, + GPIO_TX_FAULT, + GPIO_TX_DISABLE, + GPIO_RATE_SELECT, + GPIO_MAX, + + SFP_F_PRESENT = BIT(GPIO_MODDEF0), + SFP_F_LOS = BIT(GPIO_LOS), + SFP_F_TX_FAULT = BIT(GPIO_TX_FAULT), + SFP_F_TX_DISABLE = BIT(GPIO_TX_DISABLE), + SFP_F_RATE_SELECT = BIT(GPIO_RATE_SELECT), + + SFP_E_INSERT = 0, + SFP_E_REMOVE, + SFP_E_DEV_DOWN, + SFP_E_DEV_UP, + SFP_E_TX_FAULT, + SFP_E_TX_CLEAR, + SFP_E_LOS_HIGH, + SFP_E_LOS_LOW, + SFP_E_TIMEOUT, + + SFP_MOD_EMPTY = 0, + SFP_MOD_PROBE, + SFP_MOD_PRESENT, + SFP_MOD_ERROR, + + SFP_DEV_DOWN = 0, + SFP_DEV_UP, + + SFP_S_DOWN = 0, + SFP_S_INIT, + SFP_S_WAIT_LOS, + SFP_S_LINK_UP, + SFP_S_TX_FAULT, + SFP_S_REINIT, + SFP_S_TX_DISABLE, +}; + +static const char *gpio_of_names[] = { + "moddef0", + "los", + "tx-fault", + "tx-disable", + "rate-select", +}; + +static const enum gpiod_flags gpio_flags[] = { + GPIOD_IN, + GPIOD_IN, + GPIOD_IN, + GPIOD_ASIS, + GPIOD_ASIS, +}; + +#define T_INIT_JIFFIES msecs_to_jiffies(300) +#define T_RESET_US 10 +#define T_FAULT_RECOVER msecs_to_jiffies(1000) + +/* SFP module presence detection is poor: the three MOD DEF signals are + * the same length on the PCB, which means it's possible for MOD DEF 0 to + * connect before the I2C bus on MOD DEF 1/2. Try to work around this + * design bug by waiting 50ms before probing, and then retry every 250ms. + */ +#define T_PROBE_INIT msecs_to_jiffies(50) +#define T_PROBE_RETRY msecs_to_jiffies(250) + +/* + * SFP modules appear to always have their PHY configured for bus address + * 0x56 (which with mdio-i2c, translates to a PHY address of 22). + */ +#define SFP_PHY_ADDR 22 + +/* + * Give this long for the PHY to reset. + */ +#define T_PHY_RESET_MS 50 + +static DEFINE_MUTEX(sfp_mutex); + +struct sfp { + struct device *dev; + struct i2c_adapter *i2c; + struct mii_bus *i2c_mii; + struct net_device *ndev; + struct phylink *phylink; + struct phy_device *mod_phy; + + unsigned int (*get_state)(struct sfp *); + void (*set_state)(struct sfp *, unsigned int); + int (*read)(struct sfp *, bool, u8, void *, size_t); + + struct gpio_desc *gpio[GPIO_MAX]; + + unsigned int state; + struct delayed_work poll; + struct delayed_work timeout; + struct mutex sm_mutex; + unsigned char sm_mod_state; + unsigned char sm_dev_state; + unsigned short sm_state; + unsigned int sm_retries; + + struct sfp_eeprom_id id; + + struct notifier_block netdev_nb; +}; + +static unsigned long poll_jiffies; + +static unsigned int sfp_gpio_get_state(struct sfp *sfp) +{ + unsigned int i, state, v; + + for (i = state = 0; i < GPIO_MAX; i++) { + if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i]) + continue; + + v = gpiod_get_value_cansleep(sfp->gpio[i]); + if (v) + state |= BIT(i); + } + + return state; +} + +static void sfp_gpio_set_state(struct sfp *sfp, unsigned int state) +{ + if (state & SFP_F_PRESENT) { + /* If the module is present, drive the signals */ + if (sfp->gpio[GPIO_TX_DISABLE]) + gpiod_direction_output(sfp->gpio[GPIO_TX_DISABLE], + state & SFP_F_TX_DISABLE); + if (state & SFP_F_RATE_SELECT) + gpiod_direction_output(sfp->gpio[GPIO_RATE_SELECT], + state & SFP_F_RATE_SELECT); + } else { + /* Otherwise, let them float to the pull-ups */ + if (sfp->gpio[GPIO_TX_DISABLE]) + gpiod_direction_input(sfp->gpio[GPIO_TX_DISABLE]); + if (state & SFP_F_RATE_SELECT) + gpiod_direction_input(sfp->gpio[GPIO_RATE_SELECT]); + } +} + +static int sfp__i2c_read(struct i2c_adapter *i2c, u8 bus_addr, u8 dev_addr, + void *buf, size_t len) +{ + struct i2c_msg msgs[2]; + int ret; + + msgs[0].addr = bus_addr; + msgs[0].flags = 0; + msgs[0].len = 1; + msgs[0].buf = &dev_addr; + msgs[1].addr = bus_addr; + msgs[1].flags = I2C_M_RD; + msgs[1].len = len; + msgs[1].buf = buf; + + ret = i2c_transfer(i2c, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + + return ret == ARRAY_SIZE(msgs) ? len : 0; +} + +static int sfp_i2c_read(struct sfp *sfp, bool a2, u8 addr, void *buf, + size_t len) +{ + return sfp__i2c_read(sfp->i2c, a2 ? 0x51 : 0x50, addr, buf, len); +} + +static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c) +{ + struct mii_bus *i2c_mii; + int ret; + + if (!i2c_check_functionality(i2c, I2C_FUNC_I2C)) + return -EINVAL; + + sfp->i2c = i2c; + sfp->read = sfp_i2c_read; + + i2c_mii = mdio_i2c_alloc(sfp->dev, i2c); + if (IS_ERR(i2c_mii)) + return PTR_ERR(i2c_mii); + + i2c_mii->name = "SFP I2C Bus"; + i2c_mii->phy_mask = ~0; + + ret = mdiobus_register(i2c_mii); + if (ret < 0) { + mdiobus_free(i2c_mii); + return ret; + } + + sfp->i2c_mii = i2c_mii; + + return 0; +} + + +/* Interface */ +static unsigned int sfp_get_state(struct sfp *sfp) +{ + return sfp->get_state(sfp); +} + +static void sfp_set_state(struct sfp *sfp, unsigned int state) +{ + sfp->set_state(sfp, state); +} + +static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len) +{ + return sfp->read(sfp, a2, addr, buf, len); +} + +static unsigned int sfp_check(void *buf, size_t len) +{ + u8 *p, check; + + for (p = buf, check = 0; len; p++, len--) + check += *p; + + return check; +} + +static const char *sfp_link_len(char *buf, size_t size, unsigned int length, + unsigned int multiplier) +{ + if (length == 0) + return "unsupported/unspecified"; + + if (length == 255) { + *buf++ = '>'; + size -= 1; + length -= 1; + } + + length *= multiplier; + + if (length >= 1000) + snprintf(buf, size, "%u.%0*ukm", + length / 1000, + multiplier > 100 ? 1 : + multiplier > 10 ? 2 : 3, + length % 1000); + else + snprintf(buf, size, "%um", length); + + return buf; +} + +struct bitfield { + unsigned int mask; + unsigned int val; + const char *str; +}; + +static const struct bitfield sfp_options[] = { + { + .mask = SFP_OPTIONS_HIGH_POWER_LEVEL, + .val = SFP_OPTIONS_HIGH_POWER_LEVEL, + .str = "hpl", + }, { + .mask = SFP_OPTIONS_PAGING_A2, + .val = SFP_OPTIONS_PAGING_A2, + .str = "paginga2", + }, { + .mask = SFP_OPTIONS_RETIMER, + .val = SFP_OPTIONS_RETIMER, + .str = "retimer", + }, { + .mask = SFP_OPTIONS_COOLED_XCVR, + .val = SFP_OPTIONS_COOLED_XCVR, + .str = "cooled", + }, { + .mask = SFP_OPTIONS_POWER_DECL, + .val = SFP_OPTIONS_POWER_DECL, + .str = "powerdecl", + }, { + .mask = SFP_OPTIONS_RX_LINEAR_OUT, + .val = SFP_OPTIONS_RX_LINEAR_OUT, + .str = "rxlinear", + }, { + .mask = SFP_OPTIONS_RX_DECISION_THRESH, + .val = SFP_OPTIONS_RX_DECISION_THRESH, + .str = "rxthresh", + }, { + .mask = SFP_OPTIONS_TUNABLE_TX, + .val = SFP_OPTIONS_TUNABLE_TX, + .str = "tunabletx", + }, { + .mask = SFP_OPTIONS_RATE_SELECT, + .val = SFP_OPTIONS_RATE_SELECT, + .str = "ratesel", + }, { + .mask = SFP_OPTIONS_TX_DISABLE, + .val = SFP_OPTIONS_TX_DISABLE, + .str = "txdisable", + }, { + .mask = SFP_OPTIONS_TX_FAULT, + .val = SFP_OPTIONS_TX_FAULT, + .str = "txfault", + }, { + .mask = SFP_OPTIONS_LOS_INVERTED, + .val = SFP_OPTIONS_LOS_INVERTED, + .str = "los-", + }, { + .mask = SFP_OPTIONS_LOS_NORMAL, + .val = SFP_OPTIONS_LOS_NORMAL, + .str = "los+", + }, { } +}; + +static const struct bitfield diagmon[] = { + { + .mask = SFP_DIAGMON_DDM, + .val = SFP_DIAGMON_DDM, + .str = "ddm", + }, { + .mask = SFP_DIAGMON_INT_CAL, + .val = SFP_DIAGMON_INT_CAL, + .str = "intcal", + }, { + .mask = SFP_DIAGMON_EXT_CAL, + .val = SFP_DIAGMON_EXT_CAL, + .str = "extcal", + }, { + .mask = SFP_DIAGMON_RXPWR_AVG, + .val = SFP_DIAGMON_RXPWR_AVG, + .str = "rxpwravg", + }, { } +}; + +static const char *sfp_bitfield(char *out, size_t outsz, const struct bitfield *bits, unsigned int val) +{ + char *p = out; + int n; + + *p = '\0'; + while (bits->mask) { + if ((val & bits->mask) == bits->val) { + n = snprintf(p, outsz, "%s%s", + out != p ? ", " : "", + bits->str); + if (n == outsz) + break; + p += n; + outsz -= n; + } + bits++; + } + + return out; +} + +static const char *sfp_connector(unsigned int connector) +{ + switch (connector) { + case SFP_CONNECTOR_UNSPEC: + return "unknown/unspecified"; + case SFP_CONNECTOR_SC: + return "SC"; + case SFP_CONNECTOR_FIBERJACK: + return "Fiberjack"; + case SFP_CONNECTOR_LC: + return "LC"; + case SFP_CONNECTOR_MT_RJ: + return "MT-RJ"; + case SFP_CONNECTOR_MU: + return "MU"; + case SFP_CONNECTOR_SG: + return "SG"; + case SFP_CONNECTOR_OPTICAL_PIGTAIL: + return "Optical pigtail"; + case SFP_CONNECTOR_HSSDC_II: + return "HSSDC II"; + case SFP_CONNECTOR_COPPER_PIGTAIL: + return "Copper pigtail"; + default: + return "unknown"; + } +} + +static const char *sfp_encoding(unsigned int encoding) +{ + switch (encoding) { + case SFP_ENCODING_UNSPEC: + return "unspecified"; + case SFP_ENCODING_8B10B: + return "8b10b"; + case SFP_ENCODING_4B5B: + return "4b5b"; + case SFP_ENCODING_NRZ: + return "NRZ"; + case SFP_ENCODING_MANCHESTER: + return "MANCHESTER"; + default: + return "unknown"; + } +} + +/* Helpers */ +static void sfp_module_tx_disable(struct sfp *sfp) +{ + dev_dbg(sfp->dev, "tx disable %u -> %u\n", + sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 1); + sfp->state |= SFP_F_TX_DISABLE; + sfp_set_state(sfp, sfp->state); +} + +static void sfp_module_tx_enable(struct sfp *sfp) +{ + dev_dbg(sfp->dev, "tx disable %u -> %u\n", + sfp->state & SFP_F_TX_DISABLE ? 1 : 0, 0); + sfp->state &= ~SFP_F_TX_DISABLE; + sfp_set_state(sfp, sfp->state); +} + +static void sfp_module_tx_fault_reset(struct sfp *sfp) +{ + unsigned int state = sfp->state; + + if (state & SFP_F_TX_DISABLE) + return; + + sfp_set_state(sfp, state | SFP_F_TX_DISABLE); + + udelay(T_RESET_US); + + sfp_set_state(sfp, state); +} + +/* SFP state machine */ +static void sfp_sm_set_timer(struct sfp *sfp, unsigned int timeout) +{ + if (timeout) + mod_delayed_work(system_power_efficient_wq, &sfp->timeout, + timeout); + else + cancel_delayed_work(&sfp->timeout); +} + +static void sfp_sm_next(struct sfp *sfp, unsigned int state, + unsigned int timeout) +{ + sfp->sm_state = state; + sfp_sm_set_timer(sfp, timeout); +} + +static void sfp_sm_ins_next(struct sfp *sfp, unsigned int state, unsigned int timeout) +{ + sfp->sm_mod_state = state; + sfp_sm_set_timer(sfp, timeout); +} + +static void sfp_sm_phy_detach(struct sfp *sfp) +{ + phy_stop(sfp->mod_phy); + if (sfp->phylink) + phylink_disconnect_phy(sfp->phylink); + phy_device_remove(sfp->mod_phy); + phy_device_free(sfp->mod_phy); + sfp->mod_phy = NULL; +} + +static void sfp_sm_probe_phy(struct sfp *sfp) +{ + struct phy_device *phy; + int err; + + msleep(T_PHY_RESET_MS); + + phy = mdiobus_scan(sfp->i2c_mii, SFP_PHY_ADDR); + if (IS_ERR(phy)) { + dev_err(sfp->dev, "mdiobus scan returned %ld\n", PTR_ERR(phy)); + return; + } + if (!phy) { + dev_info(sfp->dev, "no PHY detected\n"); + return; + } + + err = phylink_connect_phy(sfp->phylink, phy); + if (err) { + phy_device_remove(phy); + phy_device_free(phy); + dev_err(sfp->dev, "phylink_connect_phy failed: %d\n", err); + return; + } + + sfp->mod_phy = phy; + phy_start(phy); +} + +static void sfp_sm_link_up(struct sfp *sfp) +{ + if (sfp->phylink) + phylink_enable(sfp->phylink); + + sfp_sm_next(sfp, SFP_S_LINK_UP, 0); +} + +static void sfp_sm_link_down(struct sfp *sfp) +{ + if (sfp->phylink) + phylink_disable(sfp->phylink); +} + +static void sfp_sm_link_check_los(struct sfp *sfp) +{ + unsigned int los = sfp->state & SFP_F_LOS; + + /* FIXME: what if neither SFP_OPTIONS_LOS_INVERTED nor + * SFP_OPTIONS_LOS_NORMAL are set? For now, we assume + * the same as SFP_OPTIONS_LOS_NORMAL set. + */ + if (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED) + los ^= SFP_F_LOS; + + if (los) + sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0); + else + sfp_sm_link_up(sfp); +} + +static void sfp_sm_fault(struct sfp *sfp, bool warn) +{ + if (sfp->sm_retries && !--sfp->sm_retries) { + dev_err(sfp->dev, "module persistently indicates fault, disabling\n"); + sfp_sm_next(sfp, SFP_S_TX_DISABLE, 0); + } else { + if (warn) + dev_err(sfp->dev, "module transmit fault indicated\n"); + + sfp_sm_next(sfp, SFP_S_TX_FAULT, T_FAULT_RECOVER); + } +} + +static void sfp_sm_mod_init(struct sfp *sfp) +{ + sfp_module_tx_enable(sfp); + + /* Wait t_init before indicating that the link is up, provided the + * current state indicates no TX_FAULT. If TX_FAULT clears before + * this time, that's fine too. + */ + sfp_sm_next(sfp, SFP_S_INIT, T_INIT_JIFFIES); + sfp->sm_retries = 5; + + if (sfp->phylink) { + /* Setting the serdes link mode is guesswork: there's no + * field in the EEPROM which indicates what mode should + * be used. + * + * If it's a gigabit-only fiber module, it probably does + * not have a PHY, so switch to 802.3z negotiation mode. + * Otherwise, switch to SGMII mode (which is required to + * support non-gigabit speeds) and probe for a PHY. + */ + if (!sfp->id.base.e1000_base_t && + !sfp->id.base.e100_base_lx && + !sfp->id.base.e100_base_fx) { + phylink_set_link_an_mode(sfp->phylink, MLO_AN_8023Z); + } else { + phylink_set_link_an_mode(sfp->phylink, MLO_AN_SGMII); + sfp_sm_probe_phy(sfp); + } + } +} + +static int sfp_sm_mod_probe(struct sfp *sfp) +{ + /* SFP module inserted - read I2C data */ + struct sfp_eeprom_id id; + char vendor[17]; + char part[17]; + char sn[17]; + char date[9]; + char rev[5]; + char options[80]; + u8 check; + int err; + + err = sfp_read(sfp, false, 0, &id, sizeof(id)); + if (err < 0) { + dev_err(sfp->dev, "failed to read EEPROM: %d\n", err); + return -EAGAIN; + } + + /* Validate the checksum over the base structure */ + check = sfp_check(&id.base, sizeof(id.base) - 1); + if (check != id.base.cc_base) { + dev_err(sfp->dev, + "EEPROM base structure checksum failure: 0x%02x\n", + check); + return -EINVAL; + } + + check = sfp_check(&id.ext, sizeof(id.ext) - 1); + if (check != id.ext.cc_ext) { + dev_err(sfp->dev, + "EEPROM extended structure checksum failure: 0x%02x\n", + check); + memset(&id.ext, 0, sizeof(id.ext)); + } + + sfp->id = id; + + memcpy(vendor, sfp->id.base.vendor_name, 16); + vendor[16] = '\0'; + memcpy(part, sfp->id.base.vendor_pn, 16); + part[16] = '\0'; + memcpy(rev, sfp->id.base.vendor_rev, 4); + rev[4] = '\0'; + memcpy(sn, sfp->id.ext.vendor_sn, 16); + sn[16] = '\0'; + date[0] = sfp->id.ext.datecode[4]; + date[1] = sfp->id.ext.datecode[5]; + date[2] = '-'; + date[3] = sfp->id.ext.datecode[2]; + date[4] = sfp->id.ext.datecode[3]; + date[5] = '-'; + date[6] = sfp->id.ext.datecode[0]; + date[7] = sfp->id.ext.datecode[1]; + date[8] = '\0'; + + dev_info(sfp->dev, "module %s %s rev %s sn %s dc %s\n", vendor, part, rev, sn, date); + dev_info(sfp->dev, " %s connector, encoding %s, nominal bitrate %u.%uGbps +%u%% -%u%%\n", + sfp_connector(sfp->id.base.connector), + sfp_encoding(sfp->id.base.encoding), + sfp->id.base.br_nominal / 10, + sfp->id.base.br_nominal % 10, + sfp->id.ext.br_max, sfp->id.ext.br_min); + dev_info(sfp->dev, " 1000BaseSX%c 1000BaseLX%c 1000BaseCX%c 1000BaseT%c 100BaseTLX%c 1000BaseFX%c BaseBX10%c BasePX%c\n", + sfp->id.base.e1000_base_sx ? '+' : '-', + sfp->id.base.e1000_base_lx ? '+' : '-', + sfp->id.base.e1000_base_cx ? '+' : '-', + sfp->id.base.e1000_base_t ? '+' : '-', + sfp->id.base.e100_base_lx ? '+' : '-', + sfp->id.base.e100_base_fx ? '+' : '-', + sfp->id.base.e_base_bx10 ? '+' : '-', + sfp->id.base.e_base_px ? '+' : '-'); + + if (!sfp->id.base.sfp_ct_passive && !sfp->id.base.sfp_ct_active && + !sfp->id.base.e1000_base_t) { + char len_9um[16], len_om[16]; + + dev_info(sfp->dev, " Wavelength %unm, fiber lengths:\n", + be16_to_cpup(&sfp->id.base.optical_wavelength)); + + if (sfp->id.base.link_len[0] == 255) + strcpy(len_9um, ">254km"); + else if (sfp->id.base.link_len[1] && sfp->id.base.link_len[1] != 255) + sprintf(len_9um, "%um", + sfp->id.base.link_len[1] * 100); + else if (sfp->id.base.link_len[0]) + sprintf(len_9um, "%ukm", sfp->id.base.link_len[0]); + else if (sfp->id.base.link_len[1] == 255) + strcpy(len_9um, ">25.4km"); + else + strcpy(len_9um, "unsupported"); + + dev_info(sfp->dev, " 9µm SM : %s\n", len_9um); + dev_info(sfp->dev, " 62.5µm MM OM1: %s\n", + sfp_link_len(len_om, sizeof(len_om), + sfp->id.base.link_len[3], 10)); + dev_info(sfp->dev, " 50µm MM OM2: %s\n", + sfp_link_len(len_om, sizeof(len_om), + sfp->id.base.link_len[2], 10)); + dev_info(sfp->dev, " 50µm MM OM3: %s\n", + sfp_link_len(len_om, sizeof(len_om), + sfp->id.base.link_len[5], 10)); + dev_info(sfp->dev, " 50µm MM OM4: %s\n", + sfp_link_len(len_om, sizeof(len_om), + sfp->id.base.link_len[4], 10)); + } else { + char len[16]; + dev_info(sfp->dev, " Copper length: %s\n", + sfp_link_len(len, sizeof(len), + sfp->id.base.link_len[4], 1)); + } + + dev_info(sfp->dev, " Options: %s\n", + sfp_bitfield(options, sizeof(options), sfp_options, + be16_to_cpu(sfp->id.ext.options))); + dev_info(sfp->dev, " Diagnostics: %s\n", + sfp_bitfield(options, sizeof(options), diagmon, + sfp->id.ext.diagmon)); + + /* We only support SFP modules, not the legacy GBIC modules. */ + if (sfp->id.base.phys_id != SFP_PHYS_ID_SFP || + sfp->id.base.phys_ext_id != SFP_PHYS_EXT_ID_SFP) { + dev_err(sfp->dev, "module is not SFP - phys id 0x%02x 0x%02x\n", + sfp->id.base.phys_id, sfp->id.base.phys_ext_id); + return -EINVAL; + } + + /* + * What isn't clear from the SFP documentation is whether this + * specifies the encoding expected on the TD/RD lines, or whether + * the TD/RD lines are always 8b10b encoded, but the transceiver + * converts. Eg, think of a copper SFP supporting 1G/100M/10M + * ethernet: this requires 8b10b encoding for 1G, 4b5b for 100M, + * and manchester for 10M. + */ + /* 1Gbit ethernet requires 8b10b encoding */ + if (sfp->id.base.encoding != SFP_ENCODING_8B10B) { + dev_err(sfp->dev, "module does not support 8B10B encoding\n"); + return -EINVAL; + } + + if (sfp->phylink) { + u32 support; + u8 port; + + if (sfp->id.base.e1000_base_t) { + support = SUPPORTED_TP; + port = PORT_TP; + } else { + support = SUPPORTED_FIBRE; + port = PORT_FIBRE; + } + phylink_set_link_port(sfp->phylink, support, port); + } + + return 0; +} + +static void sfp_sm_mod_remove(struct sfp *sfp) +{ + if (sfp->mod_phy) + sfp_sm_phy_detach(sfp); + + sfp_module_tx_disable(sfp); + + memset(&sfp->id, 0, sizeof(sfp->id)); + + dev_info(sfp->dev, "module removed\n"); +} + +static void sfp_sm_event(struct sfp *sfp, unsigned int event) +{ + mutex_lock(&sfp->sm_mutex); + + dev_dbg(sfp->dev, "SM: enter %u:%u:%u event %u\n", + sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state, event); + + /* This state machine tracks the insert/remove state of + * the module, and handles probing the on-board EEPROM. + */ + switch (sfp->sm_mod_state) { + default: + if (event == SFP_E_INSERT) { + sfp_module_tx_disable(sfp); + sfp_sm_ins_next(sfp, SFP_MOD_PROBE, T_PROBE_INIT); + } + break; + + case SFP_MOD_PROBE: + if (event == SFP_E_REMOVE) { + sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0); + } else if (event == SFP_E_TIMEOUT) { + int err = sfp_sm_mod_probe(sfp); + + if (err == 0) + sfp_sm_ins_next(sfp, SFP_MOD_PRESENT, 0); + else if (err == -EAGAIN) + sfp_sm_set_timer(sfp, T_PROBE_RETRY); + else + sfp_sm_ins_next(sfp, SFP_MOD_ERROR, 0); + } + break; + + case SFP_MOD_PRESENT: + case SFP_MOD_ERROR: + if (event == SFP_E_REMOVE) { + sfp_sm_mod_remove(sfp); + sfp_sm_ins_next(sfp, SFP_MOD_EMPTY, 0); + } + break; + } + + /* This state machine tracks the netdev up/down state */ + switch (sfp->sm_dev_state) { + default: + if (event == SFP_E_DEV_UP) + sfp->sm_dev_state = SFP_DEV_UP; + break; + + case SFP_DEV_UP: + if (event == SFP_E_DEV_DOWN) { + /* If the module has a PHY, avoid raising TX disable + * as this resets the PHY. Otherwise, raise it to + * turn the laser off. + */ + if (!sfp->mod_phy) + sfp_module_tx_disable(sfp); + sfp->sm_dev_state = SFP_DEV_DOWN; + } + break; + } + + /* Some events are global */ + if (sfp->sm_state != SFP_S_DOWN && + (sfp->sm_mod_state != SFP_MOD_PRESENT || + sfp->sm_dev_state != SFP_DEV_UP)) { + if (sfp->sm_state == SFP_S_LINK_UP && + sfp->sm_dev_state == SFP_DEV_UP) + sfp_sm_link_down(sfp); + if (sfp->mod_phy) + sfp_sm_phy_detach(sfp); + sfp_sm_next(sfp, SFP_S_DOWN, 0); + mutex_unlock(&sfp->sm_mutex); + return; + } + + /* The main state machine */ + switch (sfp->sm_state) { + case SFP_S_DOWN: + if (sfp->sm_mod_state == SFP_MOD_PRESENT && + sfp->sm_dev_state == SFP_DEV_UP) + sfp_sm_mod_init(sfp); + break; + + case SFP_S_INIT: + if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) + sfp_sm_fault(sfp, true); + else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) + sfp_sm_link_check_los(sfp); + break; + + case SFP_S_WAIT_LOS: + if (event == SFP_E_TX_FAULT) + sfp_sm_fault(sfp, true); + else if (event == + (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ? + SFP_E_LOS_HIGH : SFP_E_LOS_LOW)) + sfp_sm_link_up(sfp); + break; + + case SFP_S_LINK_UP: + if (event == SFP_E_TX_FAULT) { + sfp_sm_link_down(sfp); + sfp_sm_fault(sfp, true); + } else if (event == + (sfp->id.ext.options & SFP_OPTIONS_LOS_INVERTED ? + SFP_E_LOS_LOW : SFP_E_LOS_HIGH)) { + sfp_sm_link_down(sfp); + sfp_sm_next(sfp, SFP_S_WAIT_LOS, 0); + } + break; + + case SFP_S_TX_FAULT: + if (event == SFP_E_TIMEOUT) { + sfp_module_tx_fault_reset(sfp); + sfp_sm_next(sfp, SFP_S_REINIT, T_INIT_JIFFIES); + } + break; + + case SFP_S_REINIT: + if (event == SFP_E_TIMEOUT && sfp->state & SFP_F_TX_FAULT) { + sfp_sm_fault(sfp, false); + } else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) { + dev_info(sfp->dev, "module transmit fault recovered\n"); + sfp_sm_link_check_los(sfp); + } + break; + + case SFP_S_TX_DISABLE: + break; + } + + dev_dbg(sfp->dev, "SM: exit %u:%u:%u\n", + sfp->sm_mod_state, sfp->sm_dev_state, sfp->sm_state); + + mutex_unlock(&sfp->sm_mutex); +} + +static int sfp_module_info(void *priv, struct ethtool_modinfo *modinfo) +{ + struct sfp *sfp = priv; + + /* locking... and check module is present */ + + if (sfp->id.ext.sff8472_compliance) { + modinfo->type = ETH_MODULE_SFF_8472; + modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN; + } else { + modinfo->type = ETH_MODULE_SFF_8079; + modinfo->eeprom_len = ETH_MODULE_SFF_8079_LEN; + } + return 0; +} + +static int sfp_module_eeprom(void *priv, struct ethtool_eeprom *ee, u8 *data) +{ + struct sfp *sfp = priv; + unsigned int first, last, len; + int ret; + + if (ee->len == 0) + return -EINVAL; + + first = ee->offset; + last = ee->offset + ee->len; + if (first < ETH_MODULE_SFF_8079_LEN) { + len = last; + if (len > ETH_MODULE_SFF_8079_LEN) + len = ETH_MODULE_SFF_8079_LEN; + len -= first; + + ret = sfp->read(sfp, false, first, data, len); + if (ret < 0) + return ret; + + first += len; + data += len; + } + if (first >= ETH_MODULE_SFF_8079_LEN && last > first) { + len = last - first; + + ret = sfp->read(sfp, true, first, data, len); + if (ret < 0) + return ret; + } + return 0; +} + +static const struct phylink_module_ops sfp_module_ops = { + .get_module_info = sfp_module_info, + .get_module_eeprom = sfp_module_eeprom, +}; + +static void sfp_timeout(struct work_struct *work) +{ + struct sfp *sfp = container_of(work, struct sfp, timeout.work); + + sfp_sm_event(sfp, SFP_E_TIMEOUT); +} + +static void sfp_check_state(struct sfp *sfp) +{ + unsigned int state, i, changed; + + state = sfp_get_state(sfp); + changed = state ^ sfp->state; + changed &= SFP_F_PRESENT | SFP_F_LOS | SFP_F_TX_FAULT; + + for (i = 0; i < GPIO_MAX; i++) + if (changed & BIT(i)) + dev_dbg(sfp->dev, "%s %u -> %u\n", gpio_of_names[i], + !!(sfp->state & BIT(i)), !!(state & BIT(i))); + + state |= sfp->state & (SFP_F_TX_DISABLE | SFP_F_RATE_SELECT); + sfp->state = state; + + if (changed & SFP_F_PRESENT) + sfp_sm_event(sfp, state & SFP_F_PRESENT ? + SFP_E_INSERT : SFP_E_REMOVE); + + if (changed & SFP_F_TX_FAULT) + sfp_sm_event(sfp, state & SFP_F_TX_FAULT ? + SFP_E_TX_FAULT : SFP_E_TX_CLEAR); + + if (changed & SFP_F_LOS) + sfp_sm_event(sfp, state & SFP_F_LOS ? + SFP_E_LOS_HIGH : SFP_E_LOS_LOW); +} + +static irqreturn_t sfp_irq(int irq, void *data) +{ + struct sfp *sfp = data; + + sfp_check_state(sfp); + + return IRQ_HANDLED; +} + +static void sfp_poll(struct work_struct *work) +{ + struct sfp *sfp = container_of(work, struct sfp, poll.work); + + sfp_check_state(sfp); + mod_delayed_work(system_wq, &sfp->poll, poll_jiffies); +} + +static int sfp_netdev_notify(struct notifier_block *nb, unsigned long act, void *data) +{ + struct sfp *sfp = container_of(nb, struct sfp, netdev_nb); + struct netdev_notifier_info *info = data; + struct net_device *ndev = info->dev; + + if (!sfp->ndev || ndev != sfp->ndev) + return NOTIFY_DONE; + + switch (act) { + case NETDEV_UP: + sfp_sm_event(sfp, SFP_E_DEV_UP); + break; + + case NETDEV_GOING_DOWN: + sfp_sm_event(sfp, SFP_E_DEV_DOWN); + break; + + case NETDEV_UNREGISTER: + if (sfp->mod_phy && sfp->phylink) + phylink_disconnect_phy(sfp->phylink); + phylink_unregister_module(sfp->phylink, sfp); + sfp->phylink = NULL; + dev_put(sfp->ndev); + sfp->ndev = NULL; + break; + } + return NOTIFY_OK; +} + +static struct sfp *sfp_alloc(struct device *dev) +{ + struct sfp *sfp; + + sfp = kzalloc(sizeof(*sfp), GFP_KERNEL); + if (!sfp) + return ERR_PTR(-ENOMEM); + + sfp->dev = dev; + + mutex_init(&sfp->sm_mutex); + INIT_DELAYED_WORK(&sfp->poll, sfp_poll); + INIT_DELAYED_WORK(&sfp->timeout, sfp_timeout); + + sfp->netdev_nb.notifier_call = sfp_netdev_notify; + + return sfp; +} + +static void sfp_destroy(struct sfp *sfp) +{ + cancel_delayed_work_sync(&sfp->poll); + cancel_delayed_work_sync(&sfp->timeout); + if (sfp->i2c_mii) { + mdiobus_unregister(sfp->i2c_mii); + mdiobus_free(sfp->i2c_mii); + } + if (sfp->i2c) + i2c_put_adapter(sfp->i2c); + of_node_put(sfp->dev->of_node); + kfree(sfp); +} + +static void sfp_cleanup(void *data) +{ + struct sfp *sfp = data; + + sfp_destroy(sfp); +} + +static int sfp_probe(struct platform_device *pdev) +{ + struct sfp *sfp; + bool poll = false; + int irq, err, i; + + sfp = sfp_alloc(&pdev->dev); + if (IS_ERR(sfp)) + return PTR_ERR(sfp); + + platform_set_drvdata(pdev, sfp); + + err = devm_add_action(sfp->dev, sfp_cleanup, sfp); + if (err < 0) + return err; + + if (pdev->dev.of_node) { + struct device_node *node = pdev->dev.of_node; + struct device_node *np; + + np = of_parse_phandle(node, "i2c-bus", 0); + if (np) { + struct i2c_adapter *i2c; + + i2c = of_find_i2c_adapter_by_node(np); + of_node_put(np); + if (!i2c) + return -EPROBE_DEFER; + + err = sfp_i2c_configure(sfp, i2c); + if (err < 0) { + i2c_put_adapter(i2c); + return err; + } + } + + for (i = 0; i < GPIO_MAX; i++) { + sfp->gpio[i] = devm_gpiod_get_optional(sfp->dev, + gpio_of_names[i], gpio_flags[i]); + if (IS_ERR(sfp->gpio[i])) + return PTR_ERR(sfp->gpio[i]); + } + + sfp->get_state = sfp_gpio_get_state; + sfp->set_state = sfp_gpio_set_state; + + np = of_parse_phandle(node, "sfp,ethernet", 0); + if (!np) { + dev_err(sfp->dev, "missing sfp,ethernet property\n"); + return -EINVAL; + } + + sfp->ndev = of_find_net_device_by_node(np); + if (!sfp->ndev) { + dev_err(sfp->dev, "ethernet device not found\n"); + return -EPROBE_DEFER; + } + + dev_hold(sfp->ndev); + put_device(&sfp->ndev->dev); + + sfp->phylink = phylink_lookup_by_netdev(sfp->ndev); + if (!sfp->phylink) { + dev_err(sfp->dev, "ethernet device not found\n"); + return -EPROBE_DEFER; + } + + phylink_disable(sfp->phylink); + phylink_register_module(sfp->phylink, sfp, &sfp_module_ops); + } + + sfp->state = sfp_get_state(sfp); + if (sfp->gpio[GPIO_TX_DISABLE] && + gpiod_get_value_cansleep(sfp->gpio[GPIO_TX_DISABLE])) + sfp->state |= SFP_F_TX_DISABLE; + if (sfp->gpio[GPIO_RATE_SELECT] && + gpiod_get_value_cansleep(sfp->gpio[GPIO_RATE_SELECT])) + sfp->state |= SFP_F_RATE_SELECT; + sfp_set_state(sfp, sfp->state); + sfp_module_tx_disable(sfp); + if (sfp->state & SFP_F_PRESENT) + sfp_sm_event(sfp, SFP_E_INSERT); + + for (i = 0; i < GPIO_MAX; i++) { + if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i]) + continue; + + irq = gpiod_to_irq(sfp->gpio[i]); + if (!irq) { + poll = true; + continue; + } + + err = devm_request_threaded_irq(sfp->dev, irq, NULL, sfp_irq, + IRQF_ONESHOT | + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + dev_name(sfp->dev), sfp); + if (err) + poll = true; + } + + if (poll) + mod_delayed_work(system_wq, &sfp->poll, poll_jiffies); + + register_netdevice_notifier(&sfp->netdev_nb); + + return 0; +} + +static int sfp_remove(struct platform_device *pdev) +{ + struct sfp *sfp = platform_get_drvdata(pdev); + + unregister_netdevice_notifier(&sfp->netdev_nb); + if (sfp->ndev) + dev_put(sfp->ndev); + + return 0; +} + +static const struct of_device_id sfp_of_match[] = { + { .compatible = "sff,sfp", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sfp_of_match); + +static struct platform_driver sfp_driver = { + .probe = sfp_probe, + .remove = sfp_remove, + .driver = { + .name = "sfp", + .of_match_table = sfp_of_match, + }, +}; + +static int sfp_init(void) +{ + poll_jiffies = msecs_to_jiffies(100); + + return platform_driver_register(&sfp_driver); +} +module_init(sfp_init); + +static void sfp_exit(void) +{ + platform_driver_unregister(&sfp_driver); +} +module_exit(sfp_exit); + +MODULE_ALIAS("platform:sfp"); +MODULE_AUTHOR("Russell King"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c index 307f81d6b479..f3f344d76b3a 100644 --- a/drivers/pci/host/pci-mvebu.c +++ b/drivers/pci/host/pci-mvebu.c @@ -53,13 +53,26 @@ PCIE_CONF_ADDR_EN) #define PCIE_CONF_DATA_OFF 0x18fc #define PCIE_MASK_OFF 0x1910 +#define PCIE_MASK_PM_PME BIT(28) #define PCIE_MASK_ENABLE_INTS 0x0f000000 +#define PCIE_MASK_ERR_COR BIT(18) +#define PCIE_MASK_ERR_NONFATAL BIT(17) +#define PCIE_MASK_ERR_FATAL BIT(16) +#define PCIE_MASK_FERR_DET BIT(10) +#define PCIE_MASK_NFERR_DET BIT(9) +#define PCIE_MASK_CORERR_DET BIT(8) #define PCIE_CTRL_OFF 0x1a00 #define PCIE_CTRL_X1_MODE 0x0001 #define PCIE_STAT_OFF 0x1a04 #define PCIE_STAT_BUS 0xff00 #define PCIE_STAT_DEV 0x1f0000 #define PCIE_STAT_LINK_DOWN BIT(0) +#define PCIE_SSPL 0x1a0c +#define PCIE_SSPL_MSGEN BIT(14) +#define PCIE_SSPL_SPLS(x) (((x) & 3) << 8) +#define PCIE_SSPL_SPLS_VAL(x) (((x) >> 8) & 3) +#define PCIE_SSPL_SPLV(x) ((x) & 0xff) +#define PCIE_SSPL_SPLV_VAL(x) ((x) & 0xff) #define PCIE_RC_RTSTA 0x1a14 #define PCIE_DEBUG_CTRL 0x1a60 #define PCIE_DEBUG_SOFT_RESET BIT(20) @@ -114,7 +127,6 @@ struct mvebu_sw_pci_bridge { u16 bridgectrl; /* PCI express capability */ - u32 pcie_sltcap; u16 pcie_devctl; u16 pcie_rtctl; }; @@ -457,6 +469,54 @@ static void mvebu_pcie_handle_membase_change(struct mvebu_pcie_port *port) MVEBU_MBUS_NO_REMAP); } +static void mvebu_pcie_handle_irq_change(struct mvebu_pcie_port *port) +{ + u32 reg, old; + u16 devctl, rtctl; + + /* + * Errors from downstream devices: + * bridge control register SERR: enables reception of errors + * Errors from this device, or received errors: + * command SERR: enables ERR_NONFATAL and ERR_FATAL messages + * => when enabled, these conditions also flag SERR in status register + * devctl CERE: enables ERR_CORR messages + * devctl NFERE: enables ERR_NONFATAL messages + * devctl FERE: enables ERR_FATAL messages + * Enabled messages then have three paths: + * 1. rtctl: enables system error indication + * 2. root error status register updated + * 3. root error command register: forwarding via MSI + */ + old = mvebu_readl(port, PCIE_MASK_OFF); + reg = old & ~(PCIE_MASK_PM_PME | PCIE_MASK_FERR_DET | + PCIE_MASK_NFERR_DET | PCIE_MASK_CORERR_DET | + PCIE_MASK_ERR_COR | PCIE_MASK_ERR_NONFATAL | + PCIE_MASK_ERR_FATAL); + + devctl = port->bridge.pcie_devctl; + if (devctl & PCI_EXP_DEVCTL_FERE) + reg |= PCIE_MASK_FERR_DET | PCIE_MASK_ERR_FATAL; + if (devctl & PCI_EXP_DEVCTL_NFERE) + reg |= PCIE_MASK_NFERR_DET | PCIE_MASK_ERR_NONFATAL; + if (devctl & PCI_EXP_DEVCTL_CERE) + reg |= PCIE_MASK_CORERR_DET | PCIE_MASK_ERR_COR; + if (port->bridge.command & PCI_COMMAND_SERR) + reg |= PCIE_MASK_FERR_DET | PCIE_MASK_NFERR_DET | + PCIE_MASK_ERR_FATAL | PCIE_MASK_ERR_NONFATAL; + + if (!(port->bridge.bridgectrl & PCI_BRIDGE_CTL_SERR)) + reg &= ~(PCIE_MASK_ERR_COR | PCIE_MASK_ERR_NONFATAL | + PCIE_MASK_ERR_FATAL); + + rtctl = port->bridge.pcie_rtctl; + if (rtctl & PCI_EXP_RTCTL_PMEIE) + reg |= PCIE_MASK_PM_PME; + + if (old != reg) + mvebu_writel(port, reg, PCIE_MASK_OFF); +} + /* * Initialize the configuration space of the PCI-to-PCI bridge * associated with the given PCIe interface. @@ -480,6 +540,7 @@ static void mvebu_sw_pci_bridge_init(struct mvebu_pcie_port *port) /* Add capabilities */ bridge->status = PCI_STATUS_CAP_LIST; + bridge->bridgectrl = PCI_BRIDGE_CTL_SERR; } /* @@ -552,7 +613,7 @@ static int mvebu_sw_pci_bridge_read(struct mvebu_pcie_port *port, case PCI_INTERRUPT_LINE: /* LINE PIN MIN_GNT MAX_LAT */ - *value = 0; + *value = bridge->bridgectrl << 16; break; case PCISWCAP_EXP_LIST_ID: @@ -586,8 +647,12 @@ static int mvebu_sw_pci_bridge_read(struct mvebu_pcie_port *port, break; case PCISWCAP_EXP_SLTCAP: - *value = bridge->pcie_sltcap; + { + u32 tmp = mvebu_readl(port, PCIE_SSPL); + *value = PCIE_SSPL_SPLS_VAL(tmp) << 15 | + PCIE_SSPL_SPLV_VAL(tmp) << 7; break; + } case PCISWCAP_EXP_SLTCTL: *value = PCI_EXP_SLTSTA_PDS << 16; @@ -601,6 +666,16 @@ static int mvebu_sw_pci_bridge_read(struct mvebu_pcie_port *port, *value = mvebu_readl(port, PCIE_RC_RTSTA); break; + case 0x100 ... 0x128: + *value = mvebu_readl(port, where & ~3); + break; + + case 0x100 + PCI_ERR_ROOT_COMMAND: + case 0x100 + PCI_ERR_ROOT_STATUS: + case 0x100 + PCI_ERR_ROOT_ERR_SRC: + *value = 0; + break; + /* PCIe requires the v2 fields to be hard-wired to zero */ case PCISWCAP_EXP_DEVCAP2: case PCISWCAP_EXP_DEVCTL2: @@ -631,7 +706,7 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port, unsigned int where, int size, u32 value) { struct mvebu_sw_pci_bridge *bridge = &port->bridge; - u32 mask, reg; + u32 mask, reg, old; int err; if (size == 4) @@ -651,8 +726,7 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port, switch (where & ~3) { case PCI_COMMAND: - { - u32 old = bridge->command; + old = bridge->command; if (!mvebu_has_ioport(port)) value &= ~PCI_COMMAND_IO; @@ -662,8 +736,9 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port, mvebu_pcie_handle_iobase_change(port); if ((old ^ bridge->command) & PCI_COMMAND_MEMORY) mvebu_pcie_handle_membase_change(port); + if ((old ^ bridge->command) & PCI_COMMAND_SERR) + mvebu_pcie_handle_irq_change(port); break; - } case PCI_BASE_ADDRESS_0 ... PCI_BASE_ADDRESS_1: bridge->bar[((where & ~3) - PCI_BASE_ADDRESS_0) / 4] = value; @@ -692,6 +767,17 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port, mvebu_pcie_handle_iobase_change(port); break; + case PCI_INTERRUPT_LINE: + value >>= 16; + old = bridge->bridgectrl; + /* PCIe only has three bits here */ + bridge->bridgectrl = value & (PCI_BRIDGE_CTL_BUS_RESET | + PCI_BRIDGE_CTL_SERR | + PCI_BRIDGE_CTL_PARITY); + if ((old ^ bridge->bridgectrl) & PCI_BRIDGE_CTL_SERR) + mvebu_pcie_handle_irq_change(port); + break; + case PCI_PRIMARY_BUS: bridge->primary_bus = value & 0xff; bridge->secondary_bus = (value >> 8) & 0xff; @@ -701,6 +787,14 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port, break; case PCISWCAP_EXP_DEVCTL: + old = bridge->pcie_devctl; + bridge->pcie_devctl = value & (PCI_EXP_DEVCTL_FERE | + PCI_EXP_DEVCTL_NFERE | + PCI_EXP_DEVCTL_CERE | + PCI_EXP_DEVCTL_URRE); + if (bridge->pcie_devctl ^ old) + mvebu_pcie_handle_irq_change(port); + /* * Armada370 data says these bits must always * be zero when in root complex mode. @@ -741,10 +835,33 @@ static int mvebu_sw_pci_bridge_write(struct mvebu_pcie_port *port, mvebu_writel(port, value, PCIE_CAP_PCIEXP + PCI_EXP_LNKCTL); break; + case PCISWCAP_EXP_SLTCAP: + { + u32 sspl = PCIE_SSPL_SPLV((value & PCI_EXP_SLTCAP_SPLV) >> 7) | + PCIE_SSPL_SPLS((value & PCI_EXP_SLTCAP_SPLS) >> 15) | + PCIE_SSPL_MSGEN; + mvebu_writel(port, sspl, PCIE_SSPL); + break; + } + + case PCISWCAP_EXP_RTCTL: + old = bridge->pcie_rtctl; + bridge->pcie_rtctl = value & (PCI_EXP_RTCTL_SECEE | + PCI_EXP_RTCTL_SENFEE | + PCI_EXP_RTCTL_SEFEE | + PCI_EXP_RTCTL_PMEIE); + if (bridge->pcie_rtctl ^ old) + mvebu_pcie_handle_irq_change(port); + break; + case PCISWCAP_EXP_RTSTA: mvebu_writel(port, value, PCIE_RC_RTSTA); break; + case 0x100 ... 0x128: + mvebu_writel(port, value, where & ~3); + break; + default: break; } @@ -1162,7 +1279,7 @@ static int mvebu_pcie_powerup(struct mvebu_pcie_port *port) return ret; if (port->reset_gpio) { - u32 reset_udelay = 20000; + u32 reset_udelay = 30000; of_property_read_u32(port->dn, "reset-delay-us", &reset_udelay); diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 0ec649d961d7..767c34179700 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -356,8 +356,10 @@ static void pcie_aspm_cap_init(struct pcie_link_state *link, int blacklist) /* Get upstream/downstream components' register state */ pcie_get_aspm_reg(parent, &upreg); +dev_info(&parent->dev, "up support %x enabled %x\n", upreg.support, upreg.enabled); child = list_entry(linkbus->devices.next, struct pci_dev, bus_list); pcie_get_aspm_reg(child, &dwreg); +dev_info(&parent->dev, "dn support %x enabled %x\n", dwreg.support, dwreg.enabled); /* * Setup L0s state diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index e9270b4026f3..3845512a661b 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -367,6 +367,7 @@ int pcie_port_device_register(struct pci_dev *dev) /* Get and check PCI Express port services */ capabilities = get_port_device_capability(dev); +dev_info(&dev->dev, "PCIe capabilities: 0x%x\n", capabilities); if (!capabilities) return 0; @@ -379,6 +380,7 @@ int pcie_port_device_register(struct pci_dev *dev) * if that is to be used. */ status = init_service_irqs(dev, irqs, capabilities); +dev_info(&dev->dev, "init_service_irqs: %d\n", status); if (status) { capabilities &= PCIE_PORT_SERVICE_VC | PCIE_PORT_SERVICE_HP; if (!capabilities) diff --git a/include/linux/phy.h b/include/linux/phy.h index 2d24b283aa2d..fd8dfd97e2f2 100644 --- a/include/linux/phy.h +++ b/include/linux/phy.h @@ -150,6 +150,7 @@ static inline const char *phy_modes(phy_interface_t interface) #define MII_ADDR_C45 (1<<30) struct device; +struct phylink; struct sk_buff; /* @@ -419,10 +420,12 @@ struct phy_device { struct mutex lock; + struct phylink *phylink; struct net_device *attached_dev; u8 mdix; + void (*phy_link_change)(struct phy_device *, bool up, bool do_carrier); void (*adjust_link)(struct net_device *dev); }; #define to_phy_device(d) container_of(to_mdio_device(d), \ @@ -811,6 +814,7 @@ int phy_ethtool_ksettings_set(struct phy_device *phydev, const struct ethtool_link_ksettings *cmd); int phy_mii_ioctl(struct phy_device *phydev, struct ifreq *ifr, int cmd); int phy_start_interrupts(struct phy_device *phydev); +const char *phy_speed_to_str(int speed); void phy_print_status(struct phy_device *phydev); void phy_device_free(struct phy_device *phydev); int phy_set_max_speed(struct phy_device *phydev, u32 max_speed); diff --git a/include/linux/phy_fixed.h b/include/linux/phy_fixed.h index 1d41ec44e39d..43a83fa75040 100644 --- a/include/linux/phy_fixed.h +++ b/include/linux/phy_fixed.h @@ -23,9 +23,6 @@ extern void fixed_phy_unregister(struct phy_device *phydev); extern int fixed_phy_set_link_update(struct phy_device *phydev, int (*link_update)(struct net_device *, struct fixed_phy_status *)); -extern int fixed_phy_update_state(struct phy_device *phydev, - const struct fixed_phy_status *status, - const struct fixed_phy_status *changed); #else static inline int fixed_phy_add(unsigned int irq, int phy_id, struct fixed_phy_status *status, @@ -49,12 +46,6 @@ static inline int fixed_phy_set_link_update(struct phy_device *phydev, { return -ENODEV; } -static inline int fixed_phy_update_state(struct phy_device *phydev, - const struct fixed_phy_status *status, - const struct fixed_phy_status *changed) -{ - return -ENODEV; -} #endif /* CONFIG_FIXED_PHY */ #endif /* __PHY_FIXED_H */ diff --git a/include/linux/phylink.h b/include/linux/phylink.h new file mode 100644 index 000000000000..01d442b08e62 --- /dev/null +++ b/include/linux/phylink.h @@ -0,0 +1,102 @@ +#ifndef NETDEV_PCS_H +#define NETDEV_PCS_H + +#include +#include +#include + +struct device_node; +struct ethtool_cmd; +struct net_device; + +enum { + MLO_PAUSE_NONE, + MLO_PAUSE_ASYM = BIT(0), + MLO_PAUSE_SYM = BIT(1), + MLO_PAUSE_RX = BIT(2), + MLO_PAUSE_TX = BIT(3), + MLO_PAUSE_TXRX_MASK = MLO_PAUSE_TX | MLO_PAUSE_RX, + MLO_PAUSE_AN = BIT(4), + + MLO_AN_PHY = 0, + MLO_AN_FIXED, + MLO_AN_SGMII, + MLO_AN_8023Z, +}; + +struct phylink_link_state { + u32 supported; + u32 advertising; + u32 lp_advertising; + int speed; + int duplex; + int pause; + unsigned int link:1; + unsigned int sync:1; + unsigned int an_enabled:1; + unsigned int an_complete:1; +}; + +struct phylink_mac_ops { + /* Get the ethtool supported mask for the indicated mode */ + int (*mac_get_support)(struct net_device *, unsigned int mode, + struct phylink_link_state *); + + /* Read the current link state from the hardware */ + int (*mac_link_state)(struct net_device *, struct phylink_link_state *); + + /* Configure the MAC */ + void (*mac_config)(struct net_device *, unsigned int mode, + const struct phylink_link_state *); + void (*mac_an_restart)(struct net_device *, unsigned int mode); + + void (*mac_link_down)(struct net_device *, unsigned int mode); + void (*mac_link_up)(struct net_device *, unsigned int mode, + struct phy_device *); +}; + +struct phylink_module_ops { + int (*get_module_info)(void *, struct ethtool_modinfo *); + int (*get_module_eeprom)(void *, struct ethtool_eeprom *, u8 *); +}; + +struct phylink *phylink_create(struct net_device *, struct device_node *, + phy_interface_t iface, const struct phylink_mac_ops *ops); +void phylink_destroy(struct phylink *); + +int phylink_connect_phy(struct phylink *, struct phy_device *); +int phylink_of_phy_connect(struct phylink *, struct device_node *); +void phylink_disconnect_phy(struct phylink *); + +void phylink_mac_change(struct phylink *, bool up); + +void phylink_start(struct phylink *); +void phylink_stop(struct phylink *); + +int phylink_ethtool_get_settings(struct phylink *, struct ethtool_cmd *); +int phylink_ethtool_set_settings(struct phylink *, struct ethtool_cmd *); +int phylink_ethtool_nway_reset(struct phylink *); +void phylink_ethtool_get_pauseparam(struct phylink *, + struct ethtool_pauseparam *); +int phylink_ethtool_set_pauseparam(struct phylink *, + struct ethtool_pauseparam *); +int phylink_ethtool_get_module_info(struct phylink *, struct ethtool_modinfo *); +int phylink_ethtool_get_module_eeprom(struct phylink *, + struct ethtool_eeprom *, u8 *); +int phylink_init_eee(struct phylink *, bool); +int phylink_get_eee_err(struct phylink *); +int phylink_ethtool_get_eee(struct phylink *, struct ethtool_eee *); +int phylink_ethtool_set_eee(struct phylink *, struct ethtool_eee *); +int phylink_mii_ioctl(struct phylink *, struct ifreq *, int); + +int phylink_register_module(struct phylink *, void *, + const struct phylink_module_ops *); +int phylink_unregister_module(struct phylink *, void *); + +void phylink_set_link_port(struct phylink *pl, u32 support, u8 port); +int phylink_set_link_an_mode(struct phylink *pl, unsigned int mode); +void phylink_disable(struct phylink *pl); +void phylink_enable(struct phylink *pl); +struct phylink *phylink_lookup_by_netdev(struct net_device *ndev); + +#endif diff --git a/include/linux/sfp.h b/include/linux/sfp.h new file mode 100644 index 000000000000..14f237fe42cd --- /dev/null +++ b/include/linux/sfp.h @@ -0,0 +1,307 @@ +#ifndef LINUX_SFP_H +#define LINUX_SFP_H + +struct __packed sfp_eeprom_base { + u8 phys_id; + u8 phys_ext_id; + u8 connector; +#if defined __BIG_ENDIAN_BITFIELD + u8 e10g_base_er:1; + u8 e10g_base_lrm:1; + u8 e10g_base_lr:1; + u8 e10g_base_sr:1; + u8 if_1x_sx:1; + u8 if_1x_lx:1; + u8 if_1x_copper_active:1; + u8 if_1x_copper_passive:1; + + u8 escon_mmf_1310_led:1; + u8 escon_smf_1310_laser:1; + u8 sonet_oc192_short_reach:1; + u8 sonet_reach_bit1:1; + u8 sonet_reach_bit2:1; + u8 sonet_oc48_long_reach:1; + u8 sonet_oc48_intermediate_reach:1; + u8 sonet_oc48_short_reach:1; + + u8 unallocated_5_7:1; + u8 sonet_oc12_smf_long_reach:1; + u8 sonet_oc12_smf_intermediate_reach:1; + u8 sonet_oc12_short_reach:1; + u8 unallocated_5_3:1; + u8 sonet_oc3_smf_long_reach:1; + u8 sonet_oc3_smf_intermediate_reach:1; + u8 sonet_oc3_short_reach:1; + + u8 e_base_px:1; + u8 e_base_bx10:1; + u8 e100_base_fx:1; + u8 e100_base_lx:1; + u8 e1000_base_t:1; + u8 e1000_base_cx:1; + u8 e1000_base_lx:1; + u8 e1000_base_sx:1; + + u8 fc_ll_v:1; + u8 fc_ll_s:1; + u8 fc_ll_i:1; + u8 fc_ll_l:1; + u8 fc_ll_m:1; + u8 fc_tech_sa:1; + u8 fc_tech_lc:1; + u8 fc_tech_electrical_inter_enclosure:1; + + u8 fc_tech_electrical_intra_enclosure:1; + u8 fc_tech_sn:1; + u8 fc_tech_sl:1; + u8 fc_tech_ll:1; + u8 sfp_ct_active:1; + u8 sfp_ct_passive:1; + u8 unallocated_8_1:1; + u8 unallocated_8_0:1; + + u8 fc_media_tw:1; + u8 fc_media_tp:1; + u8 fc_media_mi:1; + u8 fc_media_tv:1; + u8 fc_media_m6:1; + u8 fc_media_m5:1; + u8 unallocated_9_1:1; + u8 fc_media_sm:1; + + u8 fc_speed_1200:1; + u8 fc_speed_800:1; + u8 fc_speed_1600:1; + u8 fc_speed_400:1; + u8 fc_speed_3200:1; + u8 fc_speed_200:1; + u8 unallocated_10_1:1; + u8 fc_speed_100:1; +#elif defined __LITTLE_ENDIAN_BITFIELD + u8 if_1x_copper_passive:1; + u8 if_1x_copper_active:1; + u8 if_1x_lx:1; + u8 if_1x_sx:1; + u8 e10g_base_sr:1; + u8 e10g_base_lr:1; + u8 e10g_base_lrm:1; + u8 e10g_base_er:1; + + u8 sonet_oc3_short_reach:1; + u8 sonet_oc3_smf_intermediate_reach:1; + u8 sonet_oc3_smf_long_reach:1; + u8 unallocated_5_3:1; + u8 sonet_oc12_short_reach:1; + u8 sonet_oc12_smf_intermediate_reach:1; + u8 sonet_oc12_smf_long_reach:1; + u8 unallocated_5_7:1; + + u8 sonet_oc48_short_reach:1; + u8 sonet_oc48_intermediate_reach:1; + u8 sonet_oc48_long_reach:1; + u8 sonet_reach_bit2:1; + u8 sonet_reach_bit1:1; + u8 sonet_oc192_short_reach:1; + u8 escon_smf_1310_laser:1; + u8 escon_mmf_1310_led:1; + + u8 e1000_base_sx:1; + u8 e1000_base_lx:1; + u8 e1000_base_cx:1; + u8 e1000_base_t:1; + u8 e100_base_lx:1; + u8 e100_base_fx:1; + u8 e_base_bx10:1; + u8 e_base_px:1; + + u8 fc_tech_electrical_inter_enclosure:1; + u8 fc_tech_lc:1; + u8 fc_tech_sa:1; + u8 fc_ll_m:1; + u8 fc_ll_l:1; + u8 fc_ll_i:1; + u8 fc_ll_s:1; + u8 fc_ll_v:1; + + u8 unallocated_8_0:1; + u8 unallocated_8_1:1; + u8 sfp_ct_passive:1; + u8 sfp_ct_active:1; + u8 fc_tech_ll:1; + u8 fc_tech_sl:1; + u8 fc_tech_sn:1; + u8 fc_tech_electrical_intra_enclosure:1; + + u8 fc_media_sm:1; + u8 unallocated_9_1:1; + u8 fc_media_m5:1; + u8 fc_media_m6:1; + u8 fc_media_tv:1; + u8 fc_media_mi:1; + u8 fc_media_tp:1; + u8 fc_media_tw:1; + + u8 fc_speed_100:1; + u8 unallocated_10_1:1; + u8 fc_speed_200:1; + u8 fc_speed_3200:1; + u8 fc_speed_400:1; + u8 fc_speed_1600:1; + u8 fc_speed_800:1; + u8 fc_speed_1200:1; +#else +#error Unknown Endian +#endif + u8 encoding; + u8 br_nominal; + u8 rate_id; + u8 link_len[6]; + char vendor_name[16]; + u8 reserved36; + char vendor_oui[3]; + char vendor_pn[16]; + char vendor_rev[4]; + union { + __be16 optical_wavelength; + u8 cable_spec; + }; + u8 reserved62; + u8 cc_base; +}; + +struct __packed sfp_eeprom_ext { + __be16 options; + u8 br_max; + u8 br_min; + char vendor_sn[16]; + char datecode[8]; + u8 diagmon; + u8 enhopts; + u8 sff8472_compliance; + u8 cc_ext; +}; + +struct __packed sfp_eeprom_id { + struct sfp_eeprom_base base; + struct sfp_eeprom_ext ext; +}; + +/* SFP EEPROM registers */ +enum { + SFP_PHYS_ID_SFP = 0x03, + SFP_PHYS_EXT_ID_SFP = 0x04, + SFP_CONNECTOR_UNSPEC = 0x00, + /* codes 01-05 not supportable on SFP, but some modules have single SC */ + SFP_CONNECTOR_SC = 0x01, + SFP_CONNECTOR_FIBERJACK = 0x06, + SFP_CONNECTOR_LC = 0x07, + SFP_CONNECTOR_MT_RJ = 0x08, + SFP_CONNECTOR_MU = 0x09, + SFP_CONNECTOR_SG = 0x0a, + SFP_CONNECTOR_OPTICAL_PIGTAIL = 0x0b, + SFP_CONNECTOR_HSSDC_II = 0x20, + SFP_CONNECTOR_COPPER_PIGTAIL = 0x21, + SFP_ENCODING_UNSPEC = 0x00, + SFP_ENCODING_8B10B = 0x01, + SFP_ENCODING_4B5B = 0x02, + SFP_ENCODING_NRZ = 0x03, + SFP_ENCODING_MANCHESTER = 0x04, + SFP_OPTIONS_HIGH_POWER_LEVEL = BIT(13), + SFP_OPTIONS_PAGING_A2 = BIT(12), + SFP_OPTIONS_RETIMER = BIT(11), + SFP_OPTIONS_COOLED_XCVR = BIT(10), + SFP_OPTIONS_POWER_DECL = BIT(9), + SFP_OPTIONS_RX_LINEAR_OUT = BIT(8), + SFP_OPTIONS_RX_DECISION_THRESH = BIT(7), + SFP_OPTIONS_TUNABLE_TX = BIT(6), + SFP_OPTIONS_RATE_SELECT = BIT(5), + SFP_OPTIONS_TX_DISABLE = BIT(4), + SFP_OPTIONS_TX_FAULT = BIT(3), + SFP_OPTIONS_LOS_INVERTED = BIT(2), + SFP_OPTIONS_LOS_NORMAL = BIT(1), + SFP_DIAGMON_DDM = BIT(6), + SFP_DIAGMON_INT_CAL = BIT(5), + SFP_DIAGMON_EXT_CAL = BIT(4), + SFP_DIAGMON_RXPWR_AVG = BIT(3), + SFP_DIAGMON_ADDRMODE = BIT(2), + SFP_ENHOPTS_ALARMWARN = BIT(7), + SFP_ENHOPTS_SOFT_TX_DISABLE = BIT(6), + SFP_ENHOPTS_SOFT_TX_FAULT = BIT(5), + SFP_ENHOPTS_SOFT_RX_LOS = BIT(4), + SFP_ENHOPTS_SOFT_RATE_SELECT = BIT(3), + SFP_ENHOPTS_APP_SELECT_SFF8079 = BIT(2), + SFP_ENHOPTS_SOFT_RATE_SFF8431 = BIT(1), + SFP_SFF8472_COMPLIANCE_NONE = 0x00, + SFP_SFF8472_COMPLIANCE_REV9_3 = 0x01, + SFP_SFF8472_COMPLIANCE_REV9_5 = 0x02, + SFP_SFF8472_COMPLIANCE_REV10_2 = 0x03, + SFP_SFF8472_COMPLIANCE_REV10_4 = 0x04, + SFP_SFF8472_COMPLIANCE_REV11_0 = 0x05, + SFP_SFF8472_COMPLIANCE_REV11_3 = 0x06, + SFP_SFF8472_COMPLIANCE_REV11_4 = 0x07, + SFP_SFF8472_COMPLIANCE_REV12_0 = 0x08, +}; + +/* SFP Diagnostics */ +enum { + /* Alarm and warnings stored MSB at lower address then LSB */ + SFP_TEMP_HIGH_ALARM = 0x00, + SFP_TEMP_LOW_ALARM = 0x02, + SFP_TEMP_HIGH_WARN = 0x04, + SFP_TEMP_LOW_WARN = 0x06, + SFP_VOLT_HIGH_ALARM = 0x08, + SFP_VOLT_LOW_ALARM = 0x0a, + SFP_VOLT_HIGH_WARN = 0x0c, + SFP_VOLT_LOW_WARN = 0x0e, + SFP_BIAS_HIGH_ALARM = 0x10, + SFP_BIAS_LOW_ALARM = 0x12, + SFP_BIAS_HIGH_WARN = 0x14, + SFP_BIAS_LOW_WARN = 0x16, + SFP_TXPWR_HIGH_ALARM = 0x18, + SFP_TXPWR_LOW_ALARM = 0x1a, + SFP_TXPWR_HIGH_WARN = 0x1c, + SFP_TXPWR_LOW_WARN = 0x1e, + SFP_RXPWR_HIGH_ALARM = 0x20, + SFP_RXPWR_LOW_ALARM = 0x22, + SFP_RXPWR_HIGH_WARN = 0x24, + SFP_RXPWR_LOW_WARN = 0x26, + SFP_LASER_TEMP_HIGH_ALARM = 0x28, + SFP_LASER_TEMP_LOW_ALARM = 0x2a, + SFP_LASER_TEMP_HIGH_WARN = 0x2c, + SFP_LASER_TEMP_LOW_WARN = 0x2e, + SFP_TEC_CUR_HIGH_ALARM = 0x30, + SFP_TEC_CUR_LOW_ALARM = 0x32, + SFP_TEC_CUR_HIGH_WARN = 0x34, + SFP_TEC_CUR_LOW_WARN = 0x36, + SFP_CAL_RXPWR4 = 0x38, + SFP_CAL_RXPWR3 = 0x3c, + SFP_CAL_RXPWR2 = 0x40, + SFP_CAL_RXPWR1 = 0x44, + SFP_CAL_RXPWR0 = 0x48, + SFP_CAL_TXI_SLOPE = 0x4c, + SFP_CAL_TXI_OFFSET = 0x4e, + SFP_CAL_TXPWR_SLOPE = 0x50, + SFP_CAL_TXPWR_OFFSET = 0x52, + SFP_CAL_T_SLOPE = 0x54, + SFP_CAL_T_OFFSET = 0x56, + SFP_CAL_V_SLOPE = 0x58, + SFP_CAL_V_OFFSET = 0x5a, + SFP_CHKSUM = 0x5f, + + SFP_TEMP = 0x60, + SFP_VCC = 0x62, + SFP_TX_BIAS = 0x64, + SFP_TX_POWER = 0x66, + SFP_RX_POWER = 0x68, + SFP_LASER_TEMP = 0x6a, + SFP_TEC_CUR = 0x6c, + + SFP_STATUS = 0x6e, + SFP_ALARM = 0x70, + + SFP_EXT_STATUS = 0x76, + SFP_VSL = 0x78, + SFP_PAGE = 0x7f, +}; + +#endif