/* * Driver for the MDIO interface of Marvell network interfaces. * * Since the MDIO interface of Marvell network interfaces is shared * between all network interfaces, having a single driver allows to * handle concurrent accesses properly (you may have four Ethernet * ports, but they in fact share the same SMI interface to access * the MDIO bus). This driver is currently used by the mvneta and * mv643xx_eth drivers. * * Copyright (C) 2012 Marvell * * Thomas Petazzoni <thomas.petazzoni@free-electrons.com> * * This file is licensed under the terms of the GNU General Public * License version 2. This program is licensed "as is" without any * warranty of any kind, whether express or implied.
*/
struct orion_mdio_dev { void __iomem *regs; struct clk *clk[4]; /* * If we have access to the error interrupt pin (which is * somewhat misnamed as it not only reflects internal errors * but also reflects SMI completion), use that to wait for * SMI access completion instead of polling the SMI busy bit.
*/ int err_interrupt;
wait_queue_head_t smi_busy_wait;
};
struct orion_mdio_ops { int (*is_done)(struct orion_mdio_dev *);
};
/* Wait for the SMI unit to be ready for another operation
*/ staticint orion_mdio_wait_ready(conststruct orion_mdio_ops *ops, struct mii_bus *bus)
{ struct orion_mdio_dev *dev = bus->priv; unsignedlong timeout; int done;
if (dev->err_interrupt <= 0) { if (!read_poll_timeout_atomic(ops->is_done, done, done, 2,
MVMDIO_SMI_TIMEOUT, false, dev)) return 0;
} else { /* wait_event_timeout does not guarantee a delay of at * least one whole jiffy, so timeout must be no less * than two.
*/
timeout = max(usecs_to_jiffies(MVMDIO_SMI_TIMEOUT), 2);
if (wait_event_timeout(dev->smi_busy_wait,
ops->is_done(dev), timeout)) return 0;
}
dev_err(bus->parent, "Timeout: SMI busy for too long\n"); return -ETIMEDOUT;
}
staticint orion_mdio_xsmi_write_c45(struct mii_bus *bus, int mii_id, int dev_addr, int regnum, u16 value)
{ struct orion_mdio_dev *dev = bus->priv; int ret;
ret = orion_mdio_wait_ready(&orion_mdio_xsmi_ops, bus); if (ret < 0) return ret;
if (device_property_read_u32(bus->parent, "clock-frequency", &freq)) return;
mg_core = of_clk_get_by_name(bus->parent->of_node, "mg_core_clk"); if (IS_ERR(mg_core)) {
dev_err(bus->parent, "MG core clock unknown, not changing MDC frequency"); return;
}
div = clk_get_rate(mg_core) / (freq + 1) + 1;
clk_put(mg_core);
if (div <= 8)
div = MVMDIO_XSMI_CLKDIV_8; elseif (div <= 32)
div = MVMDIO_XSMI_CLKDIV_32; elseif (div <= 64)
div = MVMDIO_XSMI_CLKDIV_64; else
div = MVMDIO_XSMI_CLKDIV_256;
dev = bus->priv;
dev->regs = devm_ioremap(&pdev->dev, r->start, resource_size(r)); if (!dev->regs) {
dev_err(&pdev->dev, "Unable to remap SMI register\n"); return -ENODEV;
}
init_waitqueue_head(&dev->smi_busy_wait);
if (pdev->dev.of_node) { for (i = 0; i < ARRAY_SIZE(dev->clk); i++) {
dev->clk[i] = of_clk_get(pdev->dev.of_node, i); if (PTR_ERR(dev->clk[i]) == -EPROBE_DEFER) {
ret = -EPROBE_DEFER; goto out_clk;
} if (IS_ERR(dev->clk[i])) break;
clk_prepare_enable(dev->clk[i]);
}
if (!IS_ERR(of_clk_get(pdev->dev.of_node,
ARRAY_SIZE(dev->clk))))
dev_warn(&pdev->dev, "unsupported number of clocks, limiting to the first "
__stringify(ARRAY_SIZE(dev->clk)) "\n");
if (type == BUS_TYPE_XSMI)
orion_mdio_xsmi_set_mdc_freq(bus);
} else {
dev->clk[0] = clk_get_optional(&pdev->dev, NULL); if (IS_ERR(dev->clk[0])) {
ret = PTR_ERR(dev->clk[0]); goto out_clk;
}
clk_prepare_enable(dev->clk[0]);
}
dev->err_interrupt = platform_get_irq_optional(pdev, 0); if (dev->err_interrupt > 0 &&
resource_size(r) < MVMDIO_ERR_INT_MASK + 4) {
dev_err(&pdev->dev, "disabling interrupt, resource size is too small\n");
dev->err_interrupt = 0;
} if (dev->err_interrupt > 0) {
ret = devm_request_irq(&pdev->dev, dev->err_interrupt,
orion_mdio_err_irq,
IRQF_SHARED, pdev->name, dev); if (ret) goto out_mdio;
/* For the platforms not supporting DT/ACPI fall-back * to mdiobus_register via of_mdiobus_register.
*/ if (is_acpi_node(pdev->dev.fwnode))
ret = acpi_mdiobus_register(bus, pdev->dev.fwnode); else
ret = of_mdiobus_register(bus, pdev->dev.of_node); if (ret < 0) {
dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret); goto out_mdio;
}
platform_set_drvdata(pdev, bus);
return 0;
out_mdio: if (dev->err_interrupt > 0)
writel(0, dev->regs + MVMDIO_ERR_INT_MASK);
out_clk: for (i = 0; i < ARRAY_SIZE(dev->clk); i++) { if (IS_ERR(dev->clk[i])) break;
clk_disable_unprepare(dev->clk[i]);
clk_put(dev->clk[i]);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.