[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1430359064-23454-9-git-send-email-f.fainelli@gmail.com>
Date: Wed, 29 Apr 2015 18:57:44 -0700
From: Florian Fainelli <f.fainelli@...il.com>
To: netdev@...r.kernel.org
Cc: dave@...emloft.net, Florian Fainelli <f.fainelli@...il.com>,
vivien.didelot@...oirfairelinux.com,
jerome.oufella@...oirfairelinux.com, linux@...ck-us.net,
andrew@...n.ch, cphealy@...il.com, mathieu@...eaurora.org,
jonasj76@...il.com, andrey.volkov@...vision.fr,
Chris.Packham@...iedtelesis.co.nz
Subject: [RFC PATCH net-next 8/8] net: dsa: mv88e6xxx: Allow them to be proper PHY drivers
Register a PHY fixup for the different MV88E6xxx switches supported by
the 88e6xxxx driver, basically replacing the legacy probe function.
Since these are all now a PHY driver, the Ethernet MAC controller attached
to it needs to be signaled proper link parameters (speed, duplex, link)
Signed-off-by: Florian Fainelli <f.fainelli@...il.com>
---
drivers/net/dsa/mv88e6123_61_65.c | 116 ++++++++++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6131.c | 107 +++++++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6171.c | 95 +++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6352.c | 102 +++++++++++++++++++++++++++++++++
drivers/net/dsa/mv88e6xxx.c | 40 ++++++++++++-
drivers/net/dsa/mv88e6xxx.h | 12 ++++
6 files changed, 470 insertions(+), 2 deletions(-)
diff --git a/drivers/net/dsa/mv88e6123_61_65.c b/drivers/net/dsa/mv88e6123_61_65.c
index b4af6d5aff7c..acd0e08d6b21 100644
--- a/drivers/net/dsa/mv88e6123_61_65.c
+++ b/drivers/net/dsa/mv88e6123_61_65.c
@@ -296,6 +296,122 @@ struct dsa_switch_driver mv88e6123_61_65_switch_driver = {
.get_regs = mv88e6xxx_get_regs,
};
+/* PHY library read status callback, must reflect the CPU port link parameters
+ * towards the attached Ethernet MAC driver
+ */
+static int mv88e6123_61_65_read_status(struct phy_device *phydev)
+{
+ phydev->link = 1;
+ phydev->speed = SPEED_1000;
+ phydev->duplex = DUPLEX_FULL;
+ phydev->state = PHY_RUNNING;
+
+ netif_carrier_on(phydev->attached_dev);
+ phydev->adjust_link(phydev->attached_dev);
+
+ return 0;
+}
+
+static int mv88e6123_61_65_phy_probe(struct phy_device *phydev)
+{
+ struct dsa_switch *ds;
+
+ ds = dsa_alloc_switch(sizeof(struct mv88e6xxx_priv_state));
+ if (!ds)
+ return -ENOMEM;
+
+ phydev->priv = ds;
+ phydev->is_switch = true;
+ ds->drv = &mv88e6123_61_65_switch_driver;
+ ds->tag_protocol = DSA_TAG_PROTO_EDSA;
+ ds->master_dev = &phydev->dev;
+
+ return 0;
+}
+
+static void mv88e6123_61_65_remove(struct phy_device *phydev)
+{
+ struct dsa_switch *ds = phydev->priv;
+
+ dsa_switch_unregister(ds);
+ kfree(ds);
+ phydev->priv = NULL;
+}
+
+static int mv88e6123_61_65_phy_fixup(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->addr != 16)
+ return 0;
+
+ ret = __mv88e6xxx_reg_read(phydev->bus, phydev->addr, REG_PORT(0), 0x03);
+ if (ret < 0)
+ return 0;
+
+ switch (ret) {
+ case PORT_SWITCH_ID_6123_A1:
+ case PORT_SWITCH_ID_6123_A2:
+ case PORT_SWITCH_ID_6161_A1:
+ case PORT_SWITCH_ID_6161_A2:
+ case PORT_SWITCH_ID_6165_A1:
+ case PORT_SWITCH_ID_6165_A2:
+ phydev->phy_id = ret;
+ break;
+ default:
+ ret &= 0xfff0;
+ switch (ret) {
+ case PORT_SWITCH_ID_6123:
+ case PORT_SWITCH_ID_6161:
+ case PORT_SWITCH_ID_6165:
+ phydev->phy_id = ret;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+#define MV88E61XX_DRV(_magic, _name) \
+{ \
+ .phy_id = (_magic), \
+ .phy_id_mask = 0xffffffff, \
+ .name = (_name), \
+ .features = PHY_GBIT_FEATURES, \
+ .config_init = mv88e6xxx_config_init, \
+ .config_aneg = mv88e6xxx_config_aneg, \
+ .read_status = mv88e6123_61_65_read_status, \
+ .probe = mv88e6123_61_65_phy_probe, \
+ .remove = mv88e6123_61_65_remove, \
+ .driver = { .owner = THIS_MODULE }, \
+}
+
+static struct phy_driver mv88e6123_61_65_phy_drivers[] = {
+ MV88E61XX_DRV(PORT_SWITCH_ID_6123_A1, "Marvell 88E6123 (A1)"),
+ MV88E61XX_DRV(PORT_SWITCH_ID_6123_A2, "Marvell 88E6123 (A2)"),
+ MV88E61XX_DRV(PORT_SWITCH_ID_6123, "Marvell 88E6123"),
+ MV88E61XX_DRV(PORT_SWITCH_ID_6161_A1, "Marvell 88E6161 (A1)"),
+ MV88E61XX_DRV(PORT_SWITCH_ID_6161_A2, "Marvell 88E6161 (A2)"),
+ MV88E61XX_DRV(PORT_SWITCH_ID_6161, "Marvell 88E6161"),
+ MV88E61XX_DRV(PORT_SWITCH_ID_6165_A1, "Marvell 88E6165 (A1)"),
+ MV88E61XX_DRV(PORT_SWITCH_ID_6165_A2, "Marvell 88E6165 (A2)"),
+ MV88E61XX_DRV(PORT_SWITCH_ID_6165, "Marvell 88E6165"),
+};
+
+int __init mv88e6123_61_65_phy_drivers_register(void)
+{
+ phy_register_fixup_for_id(PHY_ANY_ID, mv88e6123_61_65_phy_fixup);
+
+ return phy_drivers_register(mv88e6123_61_65_phy_drivers,
+ ARRAY_SIZE(mv88e6123_61_65_phy_drivers));
+}
+
+void __exit mv88e6123_61_65_phy_drivers_unregister(void)
+{
+ phy_drivers_unregister(mv88e6123_61_65_phy_drivers,
+ ARRAY_SIZE(mv88e6123_61_65_phy_drivers));
+}
+
MODULE_ALIAS("platform:mv88e6123");
MODULE_ALIAS("platform:mv88e6161");
MODULE_ALIAS("platform:mv88e6165");
diff --git a/drivers/net/dsa/mv88e6131.c b/drivers/net/dsa/mv88e6131.c
index e54824fa0d95..e60e3e3a2e65 100644
--- a/drivers/net/dsa/mv88e6131.c
+++ b/drivers/net/dsa/mv88e6131.c
@@ -313,6 +313,113 @@ struct dsa_switch_driver mv88e6131_switch_driver = {
.get_sset_count = mv88e6xxx_get_sset_count,
};
+static int mv88e6131_read_status(struct phy_device *phydev)
+{
+ struct dsa_switch *ds = phydev->priv;
+ struct mv88e6xxx_priv_state *ps = ds_to_priv(ds);
+
+ phydev->link = 1;
+ phydev->duplex = DUPLEX_FULL;
+ if (ps->id == PORT_SWITCH_ID_6085)
+ phydev->speed = SPEED_100;
+ else
+ phydev->speed = SPEED_1000;
+ phydev->state = PHY_RUNNING;
+
+ netif_carrier_on(phydev->attached_dev);
+ phydev->adjust_link(phydev->attached_dev);
+
+ return 0;
+}
+
+static int mv88e6131_phy_probe(struct phy_device *phydev)
+{
+ struct dsa_switch *ds;
+
+ ds = dsa_alloc_switch(sizeof(struct mv88e6xxx_priv_state));
+ if (!ds)
+ return -ENOMEM;
+
+ phydev->priv = ds;
+ phydev->is_switch = true;
+ ds->master_dev = &phydev->dev;
+ ds->drv = &mv88e6131_switch_driver;
+ ds->tag_protocol = DSA_TAG_PROTO_DSA;
+
+ return 0;
+}
+
+static void mv88e6131_remove(struct phy_device *phydev)
+{
+ struct dsa_switch *ds = phydev->priv;
+
+ dsa_switch_unregister(ds);
+ kfree(ds);
+ phydev->priv = NULL;
+}
+
+static int mv88e6131_phy_fixup(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->addr != 16)
+ return 0;
+
+ ret = __mv88e6xxx_reg_read(phydev->bus, phydev->addr, REG_PORT(0), 0x03);
+ if (ret < 0)
+ return 0;
+
+ if (ret == PORT_SWITCH_ID_6131_B2) {
+ phydev->phy_id = ret;
+ return 0;
+ }
+
+ ret &= 0xfff0;
+ switch (ret) {
+ case PORT_SWITCH_ID_6085:
+ case PORT_SWITCH_ID_6095:
+ case PORT_SWITCH_ID_6131:
+ phydev->phy_id = ret;
+ break;
+ }
+
+ return 0;
+}
+
+#define MV88E61XX_DRV(_magic, _feat, _name) \
+{ \
+ .phy_id = (_magic), \
+ .phy_id_mask = 0xffffffff, \
+ .name = _name, \
+ .features = (_feat), \
+ .config_init = mv88e6xxx_config_init, \
+ .config_aneg = mv88e6xxx_config_aneg, \
+ .read_status = mv88e6131_read_status, \
+ .probe = mv88e6131_phy_probe, \
+ .remove = mv88e6131_remove, \
+ .driver = { .owner = THIS_MODULE }, \
+}
+static struct phy_driver mv88e6131_phy_drivers[] = {
+ MV88E61XX_DRV(PORT_SWITCH_ID_6085, PHY_BASIC_FEATURES, "Marvell 88E6085"),
+ MV88E61XX_DRV(PORT_SWITCH_ID_6095, PHY_GBIT_FEATURES, "Marvell 88E6095/88E6095F"),
+ MV88E61XX_DRV(PORT_SWITCH_ID_6131_B2, PHY_GBIT_FEATURES, "Marvell 88E6131 (B2)"),
+ MV88E61XX_DRV(PORT_SWITCH_ID_6131, PHY_GBIT_FEATURES, "Marvell 88E6131"),
+};
+
+int __init mv88e6131_phy_drivers_register(void)
+{
+ phy_register_fixup_for_id(PHY_ANY_ID, mv88e6131_phy_fixup);
+
+ return phy_drivers_register(mv88e6131_phy_drivers,
+ ARRAY_SIZE(mv88e6131_phy_drivers));
+}
+
+void __exit mv88e6131_phy_drivers_unregister(void)
+{
+ phy_drivers_unregister(mv88e6131_phy_drivers,
+ ARRAY_SIZE(mv88e6131_phy_drivers));
+}
+
MODULE_ALIAS("platform:mv88e6085");
MODULE_ALIAS("platform:mv88e6095");
MODULE_ALIAS("platform:mv88e6095f");
diff --git a/drivers/net/dsa/mv88e6171.c b/drivers/net/dsa/mv88e6171.c
index 9104efea0e3e..120a0f46e321 100644
--- a/drivers/net/dsa/mv88e6171.c
+++ b/drivers/net/dsa/mv88e6171.c
@@ -307,5 +307,100 @@ struct dsa_switch_driver mv88e6171_switch_driver = {
.fdb_getnext = mv88e6xxx_port_fdb_getnext,
};
+static int mv88e6171_read_status(struct phy_device *phydev)
+{
+ phydev->link = 1;
+ phydev->duplex = DUPLEX_FULL;
+ phydev->state = PHY_RUNNING;
+
+ netif_carrier_on(phydev->attached_dev);
+ phydev->adjust_link(phydev->attached_dev);
+
+ return 0;
+}
+
+static int mv88e6171_phy_probe(struct phy_device *phydev)
+{
+ struct dsa_switch *ds;
+
+ ds = dsa_alloc_switch(sizeof(struct mv88e6xxx_priv_state));
+ if (!ds)
+ return -ENOMEM;
+
+ /* We cannot register the switch yet, since we do not have an attached
+ * network device
+ */
+ phydev->is_switch = true;
+ phydev->priv = ds;
+ ds->master_dev = &phydev->dev;
+ ds->drv = &mv88e6171_switch_driver;
+ ds->tag_protocol = DSA_TAG_PROTO_EDSA;
+
+ return 0;
+}
+
+static void mv88e6171_remove(struct phy_device *phydev)
+{
+ struct dsa_switch *ds = phydev->priv;
+
+ dsa_switch_unregister(ds);
+ kfree(ds);
+ phydev->priv = NULL;
+}
+
+static int mv88e6171_phy_fixup(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->addr != 16)
+ return 0;
+
+ ret = __mv88e6xxx_reg_read(phydev->bus, phydev->addr, REG_PORT(0), 0x03);
+ if (ret < 0)
+ return 0;
+
+ ret &= 0xfff0;
+ switch (ret) {
+ case PORT_SWITCH_ID_6171:
+ case PORT_SWITCH_ID_6172:
+ phydev->phy_id = ret;
+ break;
+ }
+
+ return 0;
+}
+
+#define MV88E6171_DRV(_magic, _name) \
+{ \
+ .phy_id = (_magic), \
+ .phy_id_mask = 0xfffffff0, \
+ .name = _name, \
+ .features = PHY_GBIT_FEATURES, \
+ .config_init = mv88e6xxx_config_init, \
+ .config_aneg = mv88e6xxx_config_aneg, \
+ .read_status = mv88e6171_read_status, \
+ .probe = mv88e6171_phy_probe, \
+ .remove = mv88e6171_remove, \
+ .driver = { .owner = THIS_MODULE }, \
+}
+static struct phy_driver mv88e6171_phy_drivers[] = {
+ MV88E6171_DRV(PORT_SWITCH_ID_6171, "Marvell 88E6171"),
+ MV88E6171_DRV(PORT_SWITCH_ID_6172, "Marvell 88E6172"),
+};
+
+int __init mv88e6171_phy_drivers_register(void)
+{
+ phy_register_fixup_for_id(PHY_ANY_ID, mv88e6171_phy_fixup);
+
+ return phy_drivers_register(mv88e6171_phy_drivers,
+ ARRAY_SIZE(mv88e6171_phy_drivers));
+}
+
+void __exit mv88e6171_phy_drivers_unregister(void)
+{
+ phy_drivers_unregister(mv88e6171_phy_drivers,
+ ARRAY_SIZE(mv88e6171_phy_drivers));
+}
+
MODULE_ALIAS("platform:mv88e6171");
MODULE_ALIAS("platform:mv88e6172");
diff --git a/drivers/net/dsa/mv88e6352.c b/drivers/net/dsa/mv88e6352.c
index 126c11b81e75..e7198edddd13 100644
--- a/drivers/net/dsa/mv88e6352.c
+++ b/drivers/net/dsa/mv88e6352.c
@@ -551,4 +551,106 @@ struct dsa_switch_driver mv88e6352_switch_driver = {
.fdb_getnext = mv88e6xxx_port_fdb_getnext,
};
+static int mv88e6352_read_status(struct phy_device *phydev)
+{
+ phydev->link = 1;
+ phydev->duplex = DUPLEX_FULL;
+ phydev->speed = SPEED_1000;
+ phydev->state = PHY_RUNNING;
+
+ netif_carrier_on(phydev->attached_dev);
+ phydev->adjust_link(phydev->attached_dev);
+
+ return 0;
+}
+
+static int mv88e6352_phy_probe(struct phy_device *phydev)
+{
+ struct dsa_switch *ds;
+
+ ds = dsa_alloc_switch(sizeof(struct mv88e6xxx_priv_state));
+ if (!ds)
+ return -ENOMEM;
+
+ phydev->priv = ds;
+ phydev->is_switch = true;
+ ds->master_dev = &phydev->dev;
+ ds->drv = &mv88e6352_switch_driver;
+ ds->tag_protocol = DSA_TAG_PROTO_EDSA;
+
+ return 0;
+}
+
+static void mv88e6352_remove(struct phy_device *phydev)
+{
+ struct dsa_switch *ds = phydev->priv;
+
+ dsa_switch_unregister(ds);
+ kfree(ds);
+ phydev->priv = NULL;
+}
+
+static int mv88e6352_phy_fixup(struct phy_device *phydev)
+{
+ int ret;
+
+ if (phydev->addr != 16)
+ return 0;
+
+ ret = __mv88e6xxx_reg_read(phydev->bus, phydev->addr, REG_PORT(0), 0x03);
+ if (ret < 0)
+ return 0;
+
+ switch (ret) {
+ case PORT_SWITCH_ID_6352_A0:
+ case PORT_SWITCH_ID_6352_A1:
+ phydev->phy_id = ret;
+ return 0;
+ }
+
+ ret &= 0xfff0;
+ switch (ret) {
+ case PORT_SWITCH_ID_6176:
+ case PORT_SWITCH_ID_6352:
+ phydev->phy_id = ret;
+ break;
+ }
+
+ return 0;
+}
+#define MV88E6352_DRV(_magic, _name) \
+{ \
+ .phy_id = (_magic), \
+ .phy_id_mask = 0xffffffff, \
+ .name = _name, \
+ .features = PHY_GBIT_FEATURES, \
+ .config_init = mv88e6xxx_config_init, \
+ .config_aneg = mv88e6xxx_config_aneg, \
+ .read_status = mv88e6352_read_status, \
+ .probe = mv88e6352_phy_probe, \
+ .remove = mv88e6352_remove, \
+ .driver = { .owner = THIS_MODULE }, \
+}
+
+static struct phy_driver mv88e6352_phy_drivers[] = {
+ MV88E6352_DRV(PORT_SWITCH_ID_6176, "Marvell 88E6176"),
+ MV88E6352_DRV(PORT_SWITCH_ID_6352_A0, "Marvell 88E6352 (A0)"),
+ MV88E6352_DRV(PORT_SWITCH_ID_6352_A1, "Marvell 88E6352 (A1)"),
+ MV88E6352_DRV(PORT_SWITCH_ID_6352, "Marvell 88E6352"),
+};
+
+int __init mv88e6352_phy_drivers_register(void)
+{
+ phy_register_fixup_for_id(PHY_ANY_ID, mv88e6352_phy_fixup);
+
+ return phy_drivers_register(mv88e6352_phy_drivers,
+ ARRAY_SIZE(mv88e6352_phy_drivers));
+}
+
+void __exit mv88e6352_phy_drivers_unregister(void)
+{
+ phy_drivers_unregister(mv88e6352_phy_drivers,
+ ARRAY_SIZE(mv88e6352_phy_drivers));
+}
+
MODULE_ALIAS("platform:mv88e6352");
diff --git a/drivers/net/dsa/mv88e6xxx.c b/drivers/net/dsa/mv88e6xxx.c
index af639ab4c55b..c51c6f179682 100644
--- a/drivers/net/dsa/mv88e6xxx.c
+++ b/drivers/net/dsa/mv88e6xxx.c
@@ -75,10 +75,22 @@ int __mv88e6xxx_reg_read(struct mii_bus *bus, int sw_addr, int addr, int reg)
return ret & 0xffff;
}
+static inline struct mii_bus *mv88e6xxx_mii_bus(struct dsa_switch *ds)
+{
+ struct phy_device *phydev;
+ if (ds->from_pd)
+ return dsa_host_dev_to_mii_bus(ds->master_dev);
+
+ phydev = to_phy_device(ds->master_dev);
+
+ return phydev->bus;
+}
+
+
/* Must be called with SMI mutex held */
static int _mv88e6xxx_reg_read(struct dsa_switch *ds, int addr, int reg)
{
- struct mii_bus *bus = dsa_host_dev_to_mii_bus(ds->master_dev);
+ struct mii_bus *bus = mv88e6xxx_mii_bus(ds);
int ret;
if (bus == NULL)
@@ -142,7 +154,7 @@ int __mv88e6xxx_reg_write(struct mii_bus *bus, int sw_addr, int addr,
static int _mv88e6xxx_reg_write(struct dsa_switch *ds, int addr, int reg,
u16 val)
{
- struct mii_bus *bus = dsa_host_dev_to_mii_bus(ds->master_dev);
+ struct mii_bus *bus = mv88e6xxx_mii_bus(ds);
if (bus == NULL)
return -EINVAL;
@@ -1446,19 +1458,36 @@ mv88e6xxx_phy_write_indirect(struct dsa_switch *ds, int port, int regnum,
return ret;
}
+int mv88e6xxx_config_init(struct phy_device *phydev)
+{
+ struct dsa_switch *ds = phydev->priv;
+
+ /* We now have an attached network device, register this device */
+ return dsa_switch_register_phydev(ds, phydev);
+}
+
+int mv88e6xxx_config_aneg(struct phy_device *phydev)
+{
+ return 0;
+}
+
static int __init mv88e6xxx_init(void)
{
#if IS_ENABLED(CONFIG_NET_DSA_MV88E6131)
register_switch_driver(&mv88e6131_switch_driver);
+ mv88e6131_phy_drivers_register();
#endif
#if IS_ENABLED(CONFIG_NET_DSA_MV88E6123_61_65)
register_switch_driver(&mv88e6123_61_65_switch_driver);
+ mv88e6123_61_65_phy_drivers_register();
#endif
#if IS_ENABLED(CONFIG_NET_DSA_MV88E6352)
register_switch_driver(&mv88e6352_switch_driver);
+ mv88e6352_phy_drivers_register();
#endif
#if IS_ENABLED(CONFIG_NET_DSA_MV88E6171)
register_switch_driver(&mv88e6171_switch_driver);
+ mv88e6171_phy_drivers_register();
#endif
return 0;
}
@@ -1468,12 +1497,19 @@ static void __exit mv88e6xxx_cleanup(void)
{
#if IS_ENABLED(CONFIG_NET_DSA_MV88E6171)
unregister_switch_driver(&mv88e6171_switch_driver);
+ mv88e6171_phy_drivers_unregister();
+#endif
+#if IS_ENABLED(CONFIG_NET_DSA_MV88E6352)
+ unregister_switch_driver(&mv88e6352_switch_driver);
+ mv88e6352_phy_drivers_unregister();
#endif
#if IS_ENABLED(CONFIG_NET_DSA_MV88E6123_61_65)
unregister_switch_driver(&mv88e6123_61_65_switch_driver);
+ mv88e6123_61_65_phy_drivers_unregister();
#endif
#if IS_ENABLED(CONFIG_NET_DSA_MV88E6131)
unregister_switch_driver(&mv88e6131_switch_driver);
+ mv88e6131_phy_drivers_unregister();
#endif
}
module_exit(mv88e6xxx_cleanup);
diff --git a/drivers/net/dsa/mv88e6xxx.h b/drivers/net/dsa/mv88e6xxx.h
index e045154f3364..449f948ca27f 100644
--- a/drivers/net/dsa/mv88e6xxx.h
+++ b/drivers/net/dsa/mv88e6xxx.h
@@ -315,6 +315,18 @@ extern struct dsa_switch_driver mv88e6123_61_65_switch_driver;
extern struct dsa_switch_driver mv88e6352_switch_driver;
extern struct dsa_switch_driver mv88e6171_switch_driver;
+int mv88e6xxx_config_init(struct phy_device *phydev);
+int mv88e6xxx_config_aneg(struct phy_device *phydev);
+
+int mv88e6123_61_65_phy_drivers_register(void);
+void mv88e6123_61_65_phy_drivers_unregister(void);
+int mv88e6131_phy_drivers_register(void);
+void mv88e6131_phy_drivers_unregister(void);
+int mv88e6352_phy_drivers_register(void);
+void mv88e6352_phy_drivers_unregister(void);
+int mv88e6171_phy_drivers_register(void);
+void mv88e6171_phy_drivers_unregister(void);
+
#define REG_READ(addr, reg) \
({ \
int __ret; \
--
2.1.0
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Powered by blists - more mailing lists