123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- // SPDX-License-Identifier: GPL-2.0+
- /*
- * Copyright 2019-2021 NXP
- */
- #include <net/dsa.h>
- #include <dm/lists.h>
- #include <dm/device_compat.h>
- #include <dm/device-internal.h>
- #include <dm/uclass-internal.h>
- #include <linux/bitmap.h>
- #include <miiphy.h>
- #define DSA_PORT_CHILD_DRV_NAME "dsa-port"
- /* per-device internal state structure */
- struct dsa_priv {
- struct phy_device *cpu_port_fixed_phy;
- struct udevice *master_dev;
- int num_ports;
- u32 cpu_port;
- int headroom;
- int tailroom;
- };
- /* external API */
- int dsa_set_tagging(struct udevice *dev, ushort headroom, ushort tailroom)
- {
- struct dsa_priv *priv;
- if (!dev)
- return -EINVAL;
- if (headroom + tailroom > DSA_MAX_OVR)
- return -EINVAL;
- priv = dev_get_uclass_priv(dev);
- if (headroom > 0)
- priv->headroom = headroom;
- if (tailroom > 0)
- priv->tailroom = tailroom;
- return 0;
- }
- /* returns the DSA master Ethernet device */
- struct udevice *dsa_get_master(struct udevice *dev)
- {
- struct dsa_priv *priv;
- if (!dev)
- return NULL;
- priv = dev_get_uclass_priv(dev);
- return priv->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_priv *priv = dev_get_uclass_priv(dev);
- struct udevice *master = dsa_get_master(dev);
- struct dsa_ops *ops = dsa_get_ops(dev);
- int err;
- if (ops->port_enable) {
- struct dsa_port_pdata *port_pdata;
- port_pdata = dev_get_parent_plat(pdev);
- err = ops->port_enable(dev, port_pdata->index,
- port_pdata->phy);
- if (err)
- return err;
- err = ops->port_enable(dev, priv->cpu_port,
- priv->cpu_port_fixed_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_priv *priv = dev_get_uclass_priv(dev);
- struct udevice *master = dsa_get_master(dev);
- struct dsa_ops *ops = dsa_get_ops(dev);
- if (ops->port_disable) {
- struct dsa_port_pdata *port_pdata;
- port_pdata = dev_get_parent_plat(pdev);
- ops->port_disable(dev, port_pdata->index, port_pdata->phy);
- ops->port_disable(dev, priv->cpu_port, NULL);
- }
- 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_priv *priv = dev_get_uclass_priv(dev);
- int head = priv->headroom, tail = priv->tailroom;
- struct udevice *master = dsa_get_master(dev);
- struct dsa_ops *ops = dsa_get_ops(dev);
- uchar dsa_packet_tmp[PKTSIZE_ALIGN];
- struct dsa_port_pdata *port_pdata;
- int err;
- if (length + head + tail > PKTSIZE_ALIGN)
- return -EINVAL;
- memset(dsa_packet_tmp, 0, head);
- memset(dsa_packet_tmp + head + length, 0, tail);
- memcpy(dsa_packet_tmp + head, packet, length);
- length += head + tail;
- /* copy back to preserve original buffer alignment */
- memcpy(packet, dsa_packet_tmp, length);
- port_pdata = dev_get_parent_plat(pdev);
- err = ops->xmit(dev, port_pdata->index, packet, length);
- if (err)
- return err;
- return eth_get_ops(master)->send(master, 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_priv *priv = dev_get_uclass_priv(dev);
- int head = priv->headroom, tail = priv->tailroom;
- struct udevice *master = dsa_get_master(dev);
- struct dsa_ops *ops = dsa_get_ops(dev);
- struct dsa_port_pdata *port_pdata;
- int length, port_index, err;
- 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.
- */
- port_pdata = dev_get_parent_plat(pdev);
- err = ops->rcv(dev, &port_index, *packetp, length);
- if (err || port_index != port_pdata->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 udevice *master = dsa_get_master(dev);
- struct dsa_priv *priv;
- priv = dev_get_uclass_priv(dev);
- if (eth_get_ops(master)->free_pkt) {
- /* return the original pointer and length to master Eth */
- packet -= priv->headroom;
- length += priv->headroom - priv->tailroom;
- return eth_get_ops(master)->free_pkt(master, packet, length);
- }
- return 0;
- }
- static int dsa_port_of_to_pdata(struct udevice *pdev)
- {
- struct dsa_port_pdata *port_pdata;
- struct dsa_pdata *dsa_pdata;
- struct eth_pdata *eth_pdata;
- struct udevice *dev;
- const char *label;
- u32 index;
- int err;
- if (!pdev)
- return -ENODEV;
- err = ofnode_read_u32(dev_ofnode(pdev), "reg", &index);
- if (err)
- return err;
- dev = dev_get_parent(pdev);
- dsa_pdata = dev_get_uclass_plat(dev);
- port_pdata = dev_get_parent_plat(pdev);
- port_pdata->index = index;
- label = ofnode_read_string(dev_ofnode(pdev), "label");
- if (label)
- strncpy(port_pdata->name, label, DSA_PORT_NAME_LENGTH);
- eth_pdata = dev_get_plat(pdev);
- eth_pdata->priv_pdata = port_pdata;
- dev_dbg(pdev, "port %d node %s\n", port_pdata->index,
- ofnode_get_name(dev_ofnode(pdev)));
- 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,
- };
- static int dsa_port_probe(struct udevice *pdev)
- {
- struct udevice *dev = dev_get_parent(pdev);
- struct eth_pdata *eth_pdata, *master_pdata;
- unsigned char env_enetaddr[ARP_HLEN];
- struct dsa_port_pdata *port_pdata;
- struct dsa_priv *dsa_priv;
- struct udevice *master;
- int ret;
- port_pdata = dev_get_parent_plat(pdev);
- dsa_priv = dev_get_uclass_priv(dev);
- port_pdata->phy = dm_eth_phy_connect(pdev);
- if (!port_pdata->phy)
- return -ENODEV;
- master = dsa_get_master(dev);
- if (!master)
- return -ENODEV;
- /*
- * Probe the master device. We depend on the master device for proper
- * operation and we also need it for MAC inheritance below.
- *
- * TODO: we assume the master device is always there and doesn't get
- * removed during runtime.
- */
- ret = device_probe(master);
- if (ret)
- return ret;
- /*
- * Inherit port's hwaddr from the DSA master, unless the port already
- * has a unique MAC address specified in the environment.
- */
- eth_env_get_enetaddr_by_index("eth", dev_seq(pdev), env_enetaddr);
- if (!is_zero_ethaddr(env_enetaddr)) {
- /* individual port mac addrs require master to be promisc */
- struct eth_ops *eth_ops = eth_get_ops(master);
- if (eth_ops->set_promisc)
- eth_ops->set_promisc(master, 1);
- return 0;
- }
- master_pdata = dev_get_plat(master);
- eth_pdata = dev_get_plat(pdev);
- memcpy(eth_pdata->enetaddr, master_pdata->enetaddr, ARP_HLEN);
- eth_env_set_enetaddr_by_index("eth", dev_seq(pdev),
- master_pdata->enetaddr);
- return 0;
- }
- static int dsa_port_remove(struct udevice *pdev)
- {
- struct udevice *dev = dev_get_parent(pdev);
- struct dsa_port_pdata *port_pdata;
- struct dsa_priv *dsa_priv;
- port_pdata = dev_get_parent_plat(pdev);
- dsa_priv = dev_get_uclass_priv(dev);
- port_pdata->phy = NULL;
- return 0;
- }
- U_BOOT_DRIVER(dsa_port) = {
- .name = DSA_PORT_CHILD_DRV_NAME,
- .id = UCLASS_ETH,
- .ops = &dsa_port_ops,
- .probe = dsa_port_probe,
- .remove = dsa_port_remove,
- .of_to_plat = dsa_port_of_to_pdata,
- .plat_auto = sizeof(struct eth_pdata),
- };
- /*
- * This function mostly deals with pulling information out of the device tree
- * into the pdata 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 dsa_post_bind(struct udevice *dev)
- {
- struct dsa_pdata *pdata = dev_get_uclass_plat(dev);
- ofnode node = dev_ofnode(dev), pnode;
- int i, err, first_err = 0;
- if (!ofnode_valid(node))
- return -ENODEV;
- pdata->master_node = ofnode_null();
- node = ofnode_find_subnode(node, "ports");
- if (!ofnode_valid(node))
- node = ofnode_find_subnode(node, "ethernet-ports");
- if (!ofnode_valid(node)) {
- dev_err(dev, "ports node is missing under DSA device!\n");
- return -EINVAL;
- }
- pdata->num_ports = ofnode_get_child_count(node);
- if (pdata->num_ports <= 0 || pdata->num_ports > DSA_MAX_PORTS) {
- dev_err(dev, "invalid number of ports (%d)\n",
- pdata->num_ports);
- return -EINVAL;
- }
- /* look for the CPU port */
- ofnode_for_each_subnode(pnode, node) {
- u32 ethernet;
- if (ofnode_read_u32(pnode, "ethernet", ðernet))
- continue;
- pdata->master_node = ofnode_get_by_phandle(ethernet);
- pdata->cpu_port_node = pnode;
- break;
- }
- if (!ofnode_valid(pdata->master_node)) {
- dev_err(dev, "master eth node missing!\n");
- return -EINVAL;
- }
- if (ofnode_read_u32(pnode, "reg", &pdata->cpu_port)) {
- dev_err(dev, "CPU port node not valid!\n");
- return -EINVAL;
- }
- dev_dbg(dev, "master node %s on port %d\n",
- ofnode_get_name(pdata->master_node), pdata->cpu_port);
- for (i = 0; i < pdata->num_ports; i++) {
- char name[DSA_PORT_NAME_LENGTH];
- struct udevice *pdev;
- /*
- * 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.
- */
- if (i == pdata->cpu_port)
- continue;
- /*
- * Set up default port names. If present, DT port labels
- * will override the default port names.
- */
- snprintf(name, DSA_PORT_NAME_LENGTH, "%s@%d", dev->name, i);
- ofnode_for_each_subnode(pnode, node) {
- u32 reg;
- if (ofnode_read_u32(pnode, "reg", ®))
- continue;
- if (reg == i)
- break;
- }
- /*
- * skip registration if port id not found or if the port
- * is explicitly disabled in DT
- */
- if (!ofnode_valid(pnode) || !ofnode_is_available(pnode))
- continue;
- err = device_bind_driver_to_node(dev, DSA_PORT_CHILD_DRV_NAME,
- name, pnode, &pdev);
- if (pdev) {
- struct dsa_port_pdata *port_pdata;
- port_pdata = dev_get_parent_plat(pdev);
- strncpy(port_pdata->name, name, DSA_PORT_NAME_LENGTH);
- pdev->name = port_pdata->name;
- }
- /* try to bind all ports but keep 1st error */
- if (err && !first_err)
- first_err = err;
- }
- if (first_err)
- return first_err;
- dev_dbg(dev, "DSA ports successfully bound\n");
- return 0;
- }
- /**
- * Initialize the uclass per device internal state structure (priv).
- * TODO: pick up references to other switch devices here, if we're cascaded.
- */
- static int dsa_pre_probe(struct udevice *dev)
- {
- struct dsa_pdata *pdata = dev_get_uclass_plat(dev);
- struct dsa_priv *priv = dev_get_uclass_priv(dev);
- priv->num_ports = pdata->num_ports;
- priv->cpu_port = pdata->cpu_port;
- priv->cpu_port_fixed_phy = fixed_phy_create(pdata->cpu_port_node);
- if (!priv->cpu_port_fixed_phy) {
- dev_err(dev, "Failed to register fixed-link for CPU port\n");
- return -ENODEV;
- }
- uclass_find_device_by_ofnode(UCLASS_ETH, pdata->master_node,
- &priv->master_dev);
- return 0;
- }
- UCLASS_DRIVER(dsa) = {
- .id = UCLASS_DSA,
- .name = "dsa",
- .post_bind = dsa_post_bind,
- .pre_probe = dsa_pre_probe,
- .per_device_auto = sizeof(struct dsa_priv),
- .per_device_plat_auto = sizeof(struct dsa_pdata),
- .per_child_plat_auto = sizeof(struct dsa_port_pdata),
- .flags = DM_UC_FLAG_SEQ_ALIAS,
- };
|