root/drivers/net/dsa/mv88e6xxx/pcs-6185.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Marvell 88E6185 family SERDES PCS support
 *
 * Copyright (c) 2008 Marvell Semiconductor
 *
 * Copyright (c) 2017 Andrew Lunn <andrew@lunn.ch>
 */
#include <linux/phylink.h>

#include "global2.h"
#include "port.h"
#include "serdes.h"

struct mv88e6185_pcs {
        struct phylink_pcs phylink_pcs;
        unsigned int irq;
        char name[64];

        struct mv88e6xxx_chip *chip;
        int port;
};

static struct mv88e6185_pcs *pcs_to_mv88e6185_pcs(struct phylink_pcs *pcs)
{
        return container_of(pcs, struct mv88e6185_pcs, phylink_pcs);
}

static irqreturn_t mv88e6185_pcs_handle_irq(int irq, void *dev_id)
{
        struct mv88e6185_pcs *mpcs = dev_id;
        struct mv88e6xxx_chip *chip;
        irqreturn_t ret = IRQ_NONE;
        bool link_up;
        u16 status;
        int port;
        int err;

        chip = mpcs->chip;
        port = mpcs->port;

        mv88e6xxx_reg_lock(chip);
        err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_STS, &status);
        mv88e6xxx_reg_unlock(chip);

        if (!err) {
                link_up = !!(status & MV88E6XXX_PORT_STS_LINK);

                phylink_pcs_change(&mpcs->phylink_pcs, link_up);

                ret = IRQ_HANDLED;
        }

        return ret;
}

static void mv88e6185_pcs_get_state(struct phylink_pcs *pcs,
                                    unsigned int neg_mode,
                                    struct phylink_link_state *state)
{
        struct mv88e6185_pcs *mpcs = pcs_to_mv88e6185_pcs(pcs);
        struct mv88e6xxx_chip *chip = mpcs->chip;
        int port = mpcs->port;
        u16 status;
        int err;

        mv88e6xxx_reg_lock(chip);
        err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_STS, &status);
        mv88e6xxx_reg_unlock(chip);

        if (err)
                status = 0;

        state->link = !!(status & MV88E6XXX_PORT_STS_LINK);
        if (state->link) {
                state->duplex = status & MV88E6XXX_PORT_STS_DUPLEX ?
                        DUPLEX_FULL : DUPLEX_HALF;

                switch (status & MV88E6XXX_PORT_STS_SPEED_MASK) {
                case MV88E6XXX_PORT_STS_SPEED_1000:
                        state->speed = SPEED_1000;
                        break;

                case MV88E6XXX_PORT_STS_SPEED_100:
                        state->speed = SPEED_100;
                        break;

                case MV88E6XXX_PORT_STS_SPEED_10:
                        state->speed = SPEED_10;
                        break;

                default:
                        state->link = false;
                        break;
                }
        }
}

static int mv88e6185_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
                                phy_interface_t interface,
                                const unsigned long *advertising,
                                bool permit_pause_to_mac)
{
        return 0;
}

static void mv88e6185_pcs_an_restart(struct phylink_pcs *pcs)
{
}

static const struct phylink_pcs_ops mv88e6185_phylink_pcs_ops = {
        .pcs_get_state = mv88e6185_pcs_get_state,
        .pcs_config = mv88e6185_pcs_config,
        .pcs_an_restart = mv88e6185_pcs_an_restart,
};

static int mv88e6185_pcs_init(struct mv88e6xxx_chip *chip, int port)
{
        struct mv88e6185_pcs *mpcs;
        struct device *dev;
        unsigned int irq;
        int err;

        /* There are no configurable serdes lanes on this switch chip, so
         * we use the static cmode configuration to determine whether we
         * have a PCS or not.
         */
        if (chip->ports[port].cmode != MV88E6185_PORT_STS_CMODE_SERDES &&
            chip->ports[port].cmode != MV88E6185_PORT_STS_CMODE_1000BASE_X)
                return 0;

        dev = chip->dev;

        mpcs = kzalloc_obj(*mpcs);
        if (!mpcs)
                return -ENOMEM;

        mpcs->chip = chip;
        mpcs->port = port;
        mpcs->phylink_pcs.ops = &mv88e6185_phylink_pcs_ops;

        irq = mv88e6xxx_serdes_irq_mapping(chip, port);
        if (irq) {
                snprintf(mpcs->name, sizeof(mpcs->name),
                         "mv88e6xxx-%s-serdes-%d", dev_name(dev), port);

                err = request_threaded_irq(irq, NULL, mv88e6185_pcs_handle_irq,
                                           IRQF_ONESHOT, mpcs->name, mpcs);
                if (err) {
                        kfree(mpcs);
                        return err;
                }

                mpcs->irq = irq;
        } else {
                mpcs->phylink_pcs.poll = true;
        }

        chip->ports[port].pcs_private = &mpcs->phylink_pcs;

        return 0;
}

static void mv88e6185_pcs_teardown(struct mv88e6xxx_chip *chip, int port)
{
        struct mv88e6185_pcs *mpcs;

        mpcs = chip->ports[port].pcs_private;
        if (!mpcs)
                return;

        if (mpcs->irq)
                free_irq(mpcs->irq, mpcs);

        kfree(mpcs);

        chip->ports[port].pcs_private = NULL;
}

static struct phylink_pcs *mv88e6185_pcs_select(struct mv88e6xxx_chip *chip,
                                                int port,
                                                phy_interface_t interface)
{
        return chip->ports[port].pcs_private;
}

const struct mv88e6xxx_pcs_ops mv88e6185_pcs_ops = {
        .pcs_init = mv88e6185_pcs_init,
        .pcs_teardown = mv88e6185_pcs_teardown,
        .pcs_select = mv88e6185_pcs_select,
};