+ * - Add support to probe DSA switches connected to a MDIO bus, this is needed
+ * to convert switch drivers that are now under drivers/net/phy.
+ */
+
+#define DSA_PORT_NAME_LENGTH 16
+
+/* Maximum number of ports each DSA device can have */
+#define DSA_MAX_PORTS 12
+/* Used to size internal buffers, no support for jumbo yet */
+#define DSA_MAX_FRAME_SIZE 2048
+
+/**
+ * struct dsa_ops - DSA operations
+ *
+ * @port_enable: Initialize a switch port for I/O
+ * @port_disable: Disable a port
+ * @xmit: Insert the DSA tag for transmission
+ * DSA drivers receive a copy of the packet with headroom and
+ * tailroom reserved and set to 0.
+ * Packet points to headroom and length is updated to include
+ * both headroom and tailroom
+ * @rcv: Process the DSA tag on reception
+ * Packet and length describe the frame as received from master
+ * including any additional headers
+ */
+struct dsa_ops {
+ int (*port_enable)(struct udevice *dev, int port,
+ struct phy_device *phy);
+ void (*port_disable)(struct udevice *dev, int port,
+ struct phy_device *phy);
+ int (*xmit)(struct udevice *dev, int port, void *packet, int length);
+ int (*rcv)(struct udevice *dev, int *port, void *packet, int length);
+};
+
+#define dsa_get_ops(dev) ((struct dsa_ops *)(dev)->driver->ops)
+
+/**
+ * struct dsa_port_platdata - DSA port platform data
+ *
+ * @dev : Port u-device
+ * Uclass code sets this field for all ports
+ * @phy: PHY device associated with this port
+ * Uclass code sets this field for all ports except CPU port, based on
+ * DT information. It may be NULL.
+ * @node: Port DT node, if any. Uclass code sets this field.
+ * @index: Port index in the DSA switch, set by class code.
+ * @name: Name of the port Eth device. If a label property is present in the
+ * port DT node, it is used as name. Drivers can use custom names by
+ * populating this field, otherwise class code generates a default.
+ */
+struct dsa_port_platdata {
+ struct udevice *dev;
+ struct phy_device *phy;
+ ofnode node;
+ int index;
+ char name[DSA_PORT_NAME_LENGTH];
+};
+
+/**
+ * struct dsa_perdev_platdata - Per-device platform data for DSA DM
+ *
+ * @num_ports: Number of ports the device has, must be <= DSA_MAX_PORTS
+ * All DSA drivers must set this at _bind
+ * @headroom: Size, in bytes, of headroom needed for the DSA tag
+ * All DSA drivers must set this at _bind or _probe
+ * @tailroom: Size, in bytes, of tailroom needed for the DSA tag
+ * DSA class code allocates headroom and tailroom on Tx before
+ * calling DSA driver xmit function
+ * All DSA drivers must set this at _bind or _probe
+ * @master_node: DT node of the master Ethernet. DT is optional so this may be
+ * null.
+ * @master_dev: Ethernet device to be used as master. Uclass code sets this
+ * based on DT information if present, otherwise drivers must set
+ * this field in _probe.
+ * @cpu_port: Index of switch port linked to master Ethernet.
+ * Uclass code sets this based on DT information if present,
+ * otherwise drivers must set this field in _bind.
+ * @port: per-port data
+ */
+struct dsa_perdev_platdata {
+ int num_ports;
+ int headroom;
+ int tailroom;
+
+ ofnode master_node;
+ struct udevice *master_dev;
+ int cpu_port;
+ struct dsa_port_platdata port[DSA_MAX_PORTS];
+};
+
+#endif /* __DSA_H__ */
diff --git a/net/Makefile b/net/Makefile
index 2a700c8401..fac8c8beb9 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_CMD_SNTP) += sntp.o
obj-$(CONFIG_CMD_TFTPBOOT) += tftp.o
obj-$(CONFIG_UDP_FUNCTION_FASTBOOT) += fastboot.o
obj-$(CONFIG_CMD_WOL) += wol.o
+obj-$(CONFIG_DM_DSA) += dsa-uclass.o
# Disable this warning as it is triggered by:
# sprintf(buf, index ? "foo%d" : "foo", index)
diff --git a/net/dsa-uclass.c b/net/dsa-uclass.c
new file mode 100644
index 0000000000..3790a72841
--- /dev/null
+++ b/net/dsa-uclass.c
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2019, NXP
+ */
+
+#include <net/dsa.h>
+#include <dm/lists.h>
+#include <dm/device-internal.h>
+#include <dm/uclass-internal.h>
+#include <miiphy.h>
+
+#define DSA_PORT_CHILD_DRV_NAME "dsa-port"
+
+/* helper that returns the DSA master Ethernet device. */
+static struct udevice *dsa_port_get_master(struct udevice *pdev, bool probe)
+{
+ struct udevice *dev = dev_get_parent(pdev);
+ struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
+
+ if (probe)
+ device_probe(platdata->master_dev);
+
+ return platdata->master_dev;
+}
+
+/*
+ * Start the desired port, the CPU port and the master Eth interface.
+ * TODO: if cascaded we may need to _start ports in other switches too
+ */
+static int dsa_port_start(struct udevice *pdev)
+{
+ struct udevice *dev = dev_get_parent(pdev);
+ struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
+ struct udevice *master = dsa_port_get_master(pdev, true);
+ struct dsa_port_platdata *ppriv = dev_get_priv(pdev);
+ struct dsa_ops *ops = dsa_get_ops(dev);
+ int err;
+
+ if (!ppriv || !platdata)
+ return -EINVAL;
+
+ if (!master) {
+ dev_err(pdev, "DSA master Ethernet device not found!\n");
+ return -EINVAL;
+ }
+
+ if (ops->port_enable) {
+ err = ops->port_enable(dev, ppriv->index, ppriv->phy);
+ if (err)
+ return err;
+ err = ops->port_enable(dev, platdata->cpu_port,
+ platdata->port[platdata->cpu_port].phy);
+ if (err)
+ return err;
+ }
+
+ return eth_get_ops(master)->start(master);
+}
+
+/* Stop the desired port, the CPU port and the master Eth interface */
+static void dsa_port_stop(struct udevice *pdev)
+{
+ struct udevice *dev = dev_get_parent(pdev);
+ struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
+ struct udevice *master = dsa_port_get_master(pdev, false);
+ struct dsa_port_platdata *ppriv = dev_get_priv(pdev);
+ struct dsa_ops *ops = dsa_get_ops(dev);
+
+ if (!ppriv || !platdata)
+ return;
+
+ if (ops->port_disable) {
+ ops->port_disable(dev, ppriv->index, ppriv->phy);
+ ops->port_disable(dev, platdata->cpu_port,
+ platdata->port[platdata->cpu_port].phy);
+ }
+
+ /*
+ * stop master only if it's active, don't probe it otherwise.
+ * Under normal usage it would be active because we're using it, but
+ * during tear-down it may have been removed ahead of us.
+ */
+ if (master && device_active(master))
+ eth_get_ops(master)->stop(master);
+}
+
+/*
+ * Insert a DSA tag and call master Ethernet send on the resulting packet
+ * We copy the frame to a stack buffer where we have reserved headroom and
+ * tailroom space. Headroom and tailroom are set to 0.
+ */
+static int dsa_port_send(struct udevice *pdev, void *packet, int length)
+{
+ struct udevice *dev = dev_get_parent(pdev);
+ struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
+ struct udevice *master = dsa_port_get_master(pdev, true);
+ struct dsa_port_platdata *ppriv = dev_get_priv(pdev);
+ struct dsa_ops *ops = dsa_get_ops(dev);
+ uchar dsa_packet[DSA_MAX_FRAME_SIZE];
+ int head = platdata->headroom, tail = platdata->tailroom;
+ int err;
+
+ if (!master)
+ return -EINVAL;
+
+ if (length + head + tail > DSA_MAX_FRAME_SIZE)
+ return -EINVAL;
+
+ memset(dsa_packet, 0, head);
+ memset(dsa_packet + head + length, 0, tail);
+ memcpy(dsa_packet + head, packet, length);
+ length += head + tail;
+
+ err = ops->xmit(dev, ppriv->index, dsa_packet, length);
+ if (err)
+ return err;
+
+ return eth_get_ops(master)->send(master, dsa_packet, length);
+}
+
+/* Receive a frame from master Ethernet, process it and pass it on */
+static int dsa_port_recv(struct udevice *pdev, int flags, uchar **packetp)
+{
+ struct udevice *dev = dev_get_parent(pdev);
+ struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
+ struct udevice *master = dsa_port_get_master(pdev, true);
+ struct dsa_port_platdata *ppriv = dev_get_priv(pdev);
+ struct dsa_ops *ops = dsa_get_ops(dev);
+ int head = platdata->headroom, tail = platdata->tailroom;
+ int length, port_index, err;
+
+ if (!master)
+ return -EINVAL;
+
+ length = eth_get_ops(master)->recv(master, flags, packetp);
+ if (length <= 0)
+ return length;
+
+ /*
+ * if we receive frames from a different port or frames that DSA driver
+ * doesn't like we discard them here.
+ * In case of discard we return with no frame and expect to be called
+ * again instead of looping here, so upper layer can deal with timeouts
+ * and ctrl-c
+ */
+ err = ops->rcv(dev, &port_index, *packetp, length);
+ if (err || port_index != ppriv->index || (length <= head + tail)) {
+ if (eth_get_ops(master)->free_pkt)
+ eth_get_ops(master)->free_pkt(master, *packetp, length);
+ return -EAGAIN;
+ }
+
+ /*
+ * We move the pointer over headroom here to avoid a copy. If free_pkt
+ * gets called we move the pointer back before calling master free_pkt.
+ */
+ *packetp += head;
+
+ return length - head - tail;
+}
+
+static int dsa_port_free_pkt(struct udevice *pdev, uchar *packet, int length)
+{
+ struct udevice *dev = dev_get_parent(pdev);
+ struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
+ struct udevice *master = dsa_port_get_master(pdev, true);
+
+ if (!master)
+ return -EINVAL;
+
+ if (eth_get_ops(master)->free_pkt) {
+ /* return the original pointer and length to master Eth */
+ packet -= platdata->headroom;
+ length += platdata->headroom - platdata->tailroom;
+
+ return eth_get_ops(master)->free_pkt(master, packet, length);
+ }
+
+ return 0;
+}
+
+static const struct eth_ops dsa_port_ops = {
+ .start = dsa_port_start,
+ .send = dsa_port_send,
+ .recv = dsa_port_recv,
+ .stop = dsa_port_stop,
+ .free_pkt = dsa_port_free_pkt,
+};
+
+U_BOOT_DRIVER(dsa_port) = {
+ .name = DSA_PORT_CHILD_DRV_NAME,
+ .id = UCLASS_ETH,
+ .ops = &dsa_port_ops,
+ .platdata_auto_alloc_size = sizeof(struct eth_pdata),
+};
+
+/*
+ * reads the DT properties of the given DSA port.
+ * If the return value is != 0 then the port is skipped
+ */
+static int dsa_port_parse_dt(struct udevice *dev, int port_index,
+ ofnode ports_node, bool *is_cpu)
+{
+ struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
+ struct dsa_port_platdata *port = &platdata->port[port_index];
+ ofnode temp_node;
+ u32 ethernet;
+
+ /*
+ * if we don't have a DT we don't do anything here but the port is
+ * registered normally
+ */
+ if (!ofnode_valid(ports_node))
+ return 0;
+
+ ofnode_for_each_subnode(temp_node, ports_node) {
+ const char *port_label;
+ u32 reg;
+
+ if (ofnode_read_u32(temp_node, "reg", ®) ||
+ reg != port_index)
+ continue;
+
+ /* if the port is explicitly disabled in DT skip it */
+ if (!ofnode_is_available(temp_node))
+ return -ENODEV;
+
+ port->node = temp_node;
+
+ dev_dbg(dev, "port %d node %s\n", port->index,
+ ofnode_get_name(port->node));
+
+ /* Use 'label' if present in DT */
+ port_label = ofnode_read_string(port->node, "label");
+ if (port_label)
+ strncpy(port->name, port_label, DSA_PORT_NAME_LENGTH);
+
+ *is_cpu = !ofnode_read_u32(port->node, "ethernet",
+ ðernet);
+
+ if (*is_cpu) {
+ platdata->master_node =
+ ofnode_get_by_phandle(ethernet);
+ platdata->cpu_port = port_index;
+
+ dev_dbg(dev, "master node %s on port %d\n",
+ ofnode_get_name(platdata->master_node),
+ port_index);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * This function mostly deals with pulling information out of the device tree
+ * into the platdata structure.
+ * It goes through the list of switch ports, registers an Eth device for each
+ * front panel port and identifies the cpu port connected to master Eth device.
+ * TODO: support cascaded switches
+ */
+static int dm_dsa_post_bind(struct udevice *dev)
+{
+ struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
+ ofnode ports_node = ofnode_null();
+ int first_err = 0, err = 0, i;
+
+ if (!platdata) {
+ dev_err(dev, "missing plaform data\n");
+ return -EINVAL;
+ }
+
+ if (platdata->num_ports <= 0 || platdata->num_ports > DSA_MAX_PORTS) {
+ dev_err(dev, "unexpected num_ports value (%d)\n",
+ platdata->num_ports);
+ return -EINVAL;
+ }
+
+ platdata->master_node = ofnode_null();
+
+ if (!ofnode_valid(dev->node)) {
+ dev_dbg(dev, "Device doesn't have a valid DT node!\n");
+ } else {
+ ports_node = ofnode_find_subnode(dev->node, "ports");
+ if (!ofnode_valid(ports_node))
+ dev_dbg(dev,
+ "ports node is missing under DSA device!\n");
+ }
+
+ for (i = 0; i < platdata->num_ports; i++) {
+ struct dsa_port_platdata *port = &platdata->port[i];
+ bool skip_port, is_cpu = false;
+
+ port->index = i;
+
+ /*
+ * If the driver set up port names in _bind use those, otherwise
+ * use default ones.
+ * If present, DT label is used as name and overrides anything
+ * we may have here.
+ */
+ if (!strlen(port->name))
+ snprintf(port->name, DSA_PORT_NAME_LENGTH, "%s@%d",
+ dev->name, i);
+
+ skip_port = !!dsa_port_parse_dt(dev, i, ports_node, &is_cpu);
+
+ /*
+ * if this is the CPU port don't register it as an ETH device,
+ * we skip it on purpose since I/O to/from it from the CPU
+ * isn't useful
+ * TODO: cpu port may have a PHY and we don't handle that yet.
+ */
+ if (is_cpu || skip_port)
+ continue;
+
+ err = device_bind_driver_to_node(dev, DSA_PORT_CHILD_DRV_NAME,
+ port->name, port->node,
+ &port->dev);
+
+ /* try to bind all ports but keep 1st error */
+ if (err && !first_err)
+ first_err = err;
+ }
+
+ if (!ofnode_valid(platdata->master_node))
+ dev_dbg(dev, "DSA master Eth device is missing!\n");
+
+ return first_err;
+}
+
+/**
+ * This function deals with additional devices around the switch as these
should
+ * have been bound to drivers by now.
+ * TODO: pick up references to other switch devices here, if we're cascaded.
+ */
+static int dm_dsa_pre_probe(struct udevice *dev)
+{
+ struct dsa_perdev_platdata *platdata = dev_get_platdata(dev);
+ int i;
+
+ if (!platdata)
+ return -EINVAL;
+
+ if (ofnode_valid(platdata->master_node))
+ uclass_find_device_by_ofnode(UCLASS_ETH, platdata->master_node,
+ &platdata->master_dev);
+
+ for (i = 0; i < platdata->num_ports; i++) {
+ struct dsa_port_platdata *port = &platdata->port[i];
+
+ if (port->dev) {
+ port->dev->priv = port;
+ port->phy = dm_eth_phy_connect(port->dev);
+ }
+ }
+
+ return 0;
+}
+
+UCLASS_DRIVER(dsa) = {
+ .id = UCLASS_DSA,
+ .name = "dsa",
+ .post_bind = dm_dsa_post_bind,
+ .pre_probe = dm_dsa_pre_probe,
+ .per_device_platdata_auto_alloc_size =
+ sizeof(struct dsa_perdev_platdata),
+};
--
2.17.1