aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/mvebu/patches-4.14/410-sfp-hack-allow-marvell-10G-phy-support-to-use-SFP.patch
blob: d6e5fbf33f89d4aca6faecd132558848a717cf75 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
From 4a4aca08b11501cb1b2c509113bbb65eb66a1f45 Mon Sep 17 00:00:00 2001
From: Russell King <rmk+kernel@armlinux.org.uk>
Date: Fri, 14 Apr 2017 14:21:25 +0100
Subject: sfp: hack: allow marvell 10G phy support to use SFP

Allow the Marvell 10G PHY to register with the SFP bus, so that SFP+
cages can work.  This bypasses phylink, meaning that socket status
is not taken into account for the link state.  Also, the tx-disable
signal must be commented out in DT for this to work...

Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
---
 drivers/net/phy/marvell10g.c | 54 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 53 insertions(+), 1 deletion(-)

--- a/drivers/net/phy/marvell10g.c
+++ b/drivers/net/phy/marvell10g.c
@@ -15,8 +15,10 @@
  * If both the fiber and copper ports are connected, the first to gain
  * link takes priority and the other port is completely locked out.
  */
+#include <linux/of.h>
 #include <linux/phy.h>
 #include <linux/marvell_phy.h>
+#include <linux/sfp.h>
 
 enum {
 	MV_PMA_BOOT		= 0xc050,
@@ -41,6 +43,11 @@ enum {
 	MV_AN_RESULT_SPD_10000	= BIT(15),
 };
 
+struct mv3310_priv {
+	struct device_node *sfp_node;
+	struct sfp_bus *sfp_bus;
+};
+
 static int mv3310_modify(struct phy_device *phydev, int devad, u16 reg,
 			 u16 mask, u16 bits)
 {
@@ -59,8 +66,25 @@ static int mv3310_modify(struct phy_devi
 	return ret < 0 ? ret : 1;
 }
 
+static int mv3310_sfp_insert(void *upstream, const struct sfp_eeprom_id *id)
+{
+	struct phy_device *phydev = upstream;
+	struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+
+	if (sfp_parse_interface(priv->sfp_bus, id) != PHY_INTERFACE_MODE_10GKR) {
+		dev_err(&phydev->mdio.dev, "incompatible SFP module inserted\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static const struct sfp_upstream_ops mv3310_sfp_ops = {
+	.module_insert = mv3310_sfp_insert,
+};
+
 static int mv3310_probe(struct phy_device *phydev)
 {
+	struct mv3310_priv *priv;
 	u32 mmd_mask = MDIO_DEVS_PMAPMD | MDIO_DEVS_AN;
 	int ret;
 
@@ -78,9 +102,27 @@ static int mv3310_probe(struct phy_devic
 		return -ENODEV;
 	}
 
+	priv = devm_kzalloc(&phydev->mdio.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	dev_set_drvdata(&phydev->mdio.dev, priv);
+
+	if (phydev->mdio.dev.of_node)
+		priv->sfp_node = of_parse_phandle(phydev->mdio.dev.of_node,
+						  "sfp", 0);
+
 	return 0;
 }
 
+static void mv3310_remove(struct phy_device *phydev)
+{
+	struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
+
+	if (priv->sfp_bus)
+		sfp_unregister_upstream(priv->sfp_bus);
+}
+
 /*
  * Resetting the MV88X3310 causes it to become non-responsive.  Avoid
  * setting the reset bit(s).
@@ -92,6 +134,7 @@ static int mv3310_soft_reset(struct phy_
 
 static int mv3310_config_init(struct phy_device *phydev)
 {
+	struct mv3310_priv *priv = dev_get_drvdata(&phydev->mdio.dev);
 	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported) = { 0, };
 	u32 mask;
 	int val;
@@ -180,6 +223,14 @@ static int mv3310_config_init(struct phy
 	phydev->supported &= mask;
 	phydev->advertising &= phydev->supported;
 
+	/* Would be nice to do this in the probe function, but unfortunately,
+	 * phylib doesn't have phydev->attached_dev set there.
+	 */
+	if (priv->sfp_node && !priv->sfp_bus)
+		priv->sfp_bus = sfp_register_upstream(priv->sfp_node,
+						      phydev->attached_dev,
+						      phydev, &mv3310_sfp_ops);
+
 	return 0;
 }
 
@@ -363,12 +414,13 @@ static struct phy_driver mv3310_drivers[
 				  SUPPORTED_FIBRE |
 				  SUPPORTED_10000baseT_Full |
 				  SUPPORTED_Backplane,
-		.probe		= mv3310_probe,
 		.soft_reset	= mv3310_soft_reset,
 		.config_init	= mv3310_config_init,
+		.probe		= mv3310_probe,
 		.config_aneg	= mv3310_config_aneg,
 		.aneg_done	= mv3310_aneg_done,
 		.read_status	= mv3310_read_status,
+		.remove		= mv3310_remove,
 	},
 };
 
span> ohci_hcd ohci; /* _must_ be at the beginning. */ u32 enable_flags; }; static inline struct ssb_ohci_device * hcd_to_ssb_ohci(struct usb_hcd *hcd) { return (struct ssb_ohci_device *)(hcd->hcd_priv); } static const struct ssb_device_id ssb_ohci_table[] = { SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_USB11_HOSTDEV, SSB_ANY_REV), SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_USB11_HOST, SSB_ANY_REV), SSB_DEVTABLE_END }; MODULE_DEVICE_TABLE(ssb, ssb_ohci_table); static int ssb_ohci_reset(struct usb_hcd *hcd) { struct ssb_ohci_device *ohcidev = hcd_to_ssb_ohci(hcd); struct ohci_hcd *ohci = &ohcidev->ohci; int err; ohci_hcd_init(ohci); err = ohci_init(ohci); return err; } static int ssb_ohci_start(struct usb_hcd *hcd) { struct ssb_ohci_device *ohcidev = hcd_to_ssb_ohci(hcd); struct ohci_hcd *ohci = &ohcidev->ohci; int err; err = ohci_run(ohci); if (err < 0) { ohci_err(ohci, "can't start\n"); ohci_stop(hcd); } return err; } #ifdef CONFIG_PM static int ssb_ohci_hcd_suspend(struct usb_hcd *hcd, pm_message_t message) { struct ssb_ohci_device *ohcidev = hcd_to_ssb_ohci(hcd); struct ohci_hcd *ohci = &ohcidev->ohci; unsigned long flags; spin_lock_irqsave(&ohci->lock, flags); ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); ohci_readl(ohci, &ohci->regs->intrdisable); /* commit write */ /* make sure snapshot being resumed re-enumerates everything */ if (message.event == PM_EVENT_PRETHAW) ohci_usb_reset(ohci); clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); spin_unlock_irqrestore(&ohci->lock, flags); return 0; } static int ssb_ohci_hcd_resume(struct usb_hcd *hcd) { set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); usb_hcd_resume_root_hub(hcd); return 0; } #endif /* CONFIG_PM */ static const struct hc_driver ssb_ohci_hc_driver = { .description = "ssb-usb-ohci", .product_desc = "SSB OHCI Controller", .hcd_priv_size = sizeof(struct ssb_ohci_device), .irq = ohci_irq, .flags = HCD_MEMORY | HCD_USB11, .reset = ssb_ohci_reset, .start = ssb_ohci_start, .stop = ohci_stop, .shutdown = ohci_shutdown, #ifdef CONFIG_PM .suspend = ssb_ohci_hcd_suspend, .resume = ssb_ohci_hcd_resume, #endif .urb_enqueue = ohci_urb_enqueue, .urb_dequeue = ohci_urb_dequeue, .endpoint_disable = ohci_endpoint_disable, .get_frame_number = ohci_get_frame, .hub_status_data = ohci_hub_status_data, .hub_control = ohci_hub_control, .hub_irq_enable = ohci_rhsc_enable, #ifdef CONFIG_PM .bus_suspend = ohci_bus_suspend, .bus_resume = ohci_bus_resume, #endif .start_port_reset = ohci_start_port_reset, }; static void ssb_ohci_detach(struct ssb_device *dev) { struct usb_hcd *hcd = ssb_get_drvdata(dev); usb_remove_hcd(hcd); iounmap(hcd->regs); usb_put_hcd(hcd); ssb_device_disable(dev, 0); } static int ssb_ohci_attach(struct ssb_device *dev) { struct ssb_ohci_device *ohcidev; struct usb_hcd *hcd; int err = -ENOMEM; u32 tmp, flags = 0; if (dev->id.coreid == SSB_DEV_USB11_HOSTDEV) flags |= SSB_OHCI_TMSLOW_HOSTMODE; ssb_device_enable(dev, flags); hcd = usb_create_hcd(&ssb_ohci_hc_driver, dev->dev, dev->dev->bus_id); if (!hcd) goto err_dev_disable; ohcidev = hcd_to_ssb_ohci(hcd); ohcidev->enable_flags = flags; tmp = ssb_read32(dev, SSB_ADMATCH0); hcd->rsrc_start = ssb_admatch_base(tmp); hcd->rsrc_len = ssb_admatch_size(tmp); hcd->regs = ioremap_nocache(hcd->rsrc_start, hcd->rsrc_len); if (!hcd->regs) goto err_put_hcd; err = usb_add_hcd(hcd, dev->irq, IRQF_SHARED); if (err) goto err_iounmap; ssb_set_drvdata(dev, hcd); return err; err_iounmap: iounmap(hcd->regs); err_put_hcd: usb_put_hcd(hcd); err_dev_disable: ssb_device_disable(dev, flags); return err; } static int ssb_ohci_probe(struct ssb_device *dev, const struct ssb_device_id *id) { int err; u16 chipid_top; chipid_top = (dev->bus->chip_id & 0xFF00); if (chipid_top != 0x4700 && chipid_top != 0x5300) { /* USBcores are only connected on embedded devices. */ return -ENODEV; } /* TODO: Probably need more checks here whether the core is connected. */ if (usb_disabled()) return -ENODEV; /* We currently always attach SSB_DEV_USB11_HOSTDEV * as HOST OHCI. If we want to attach it as Client device, * we must branch here and call into the (yet to * be written) Client mode driver. Same for remove(). */ err = ssb_ohci_attach(dev); return err; } static void ssb_ohci_remove(struct ssb_device *dev) { ssb_ohci_detach(dev); } #ifdef CONFIG_PM static int ssb_ohci_suspend(struct ssb_device *dev, pm_message_t state) { ssb_device_disable(dev, 0); return 0; } static int ssb_ohci_resume(struct ssb_device *dev) { struct usb_hcd *hcd = ssb_get_drvdata(dev); struct ssb_ohci_device *ohcidev = hcd_to_ssb_ohci(hcd); ssb_device_enable(dev, ohcidev->enable_flags); return 0; } #else /* CONFIG_PM */ # define ssb_ohci_suspend NULL # define ssb_ohci_resume NULL #endif /* CONFIG_PM */ static struct ssb_driver ssb_ohci_driver = { .name = KBUILD_MODNAME, .id_table = ssb_ohci_table, .probe = ssb_ohci_probe, .remove = ssb_ohci_remove, .suspend = ssb_ohci_suspend, .resume = ssb_ohci_resume, };