root/drivers/net/pse-pd/pd692x0.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Driver for the Microchip PD692X0 PoE PSE Controller driver (I2C bus)
 *
 * Copyright (c) 2023 Bootlin, Kory Maincent <kory.maincent@bootlin.com>
 */

#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pse-pd/pse.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>

#define PD692X0_PSE_NAME "pd692x0_pse"

#define PD692X0_MAX_PIS 48
#define PD692X0_MAX_MANAGERS            12
#define PD692X0_MAX_MANAGER_PORTS       8
#define PD692X0_MAX_HW_PORTS    (PD692X0_MAX_MANAGERS * PD692X0_MAX_MANAGER_PORTS)

#define PD69200_BT_PROD_VER     24
#define PD69210_BT_PROD_VER     26
#define PD69220_BT_PROD_VER     29

#define PD692X0_FW_MAJ_VER      3
#define PD692X0_FW_MIN_VER      5
#define PD692X0_FW_PATCH_VER    5

#define PD692X0_USER_BYTE       42

enum pd692x0_fw_state {
        PD692X0_FW_UNKNOWN,
        PD692X0_FW_OK,
        PD692X0_FW_BROKEN,
        PD692X0_FW_NEED_UPDATE,
        PD692X0_FW_PREPARE,
        PD692X0_FW_WRITE,
        PD692X0_FW_COMPLETE,
};

struct pd692x0_msg {
        u8 key;
        u8 echo;
        u8 sub[3];
        u8 data[8];
        __be16 chksum;
} __packed;

struct pd692x0_msg_ver {
        u8 prod;
        u8 maj_sw_ver;
        u8 min_sw_ver;
        u8 pa_sw_ver;
        u8 param;
        u8 build;
};

enum {
        PD692X0_KEY_CMD,
        PD692X0_KEY_PRG,
        PD692X0_KEY_REQ,
        PD692X0_KEY_TLM,
        PD692X0_KEY_TEST,
        PD692X0_KEY_REPORT = 0x52
};

enum {
        PD692X0_MSG_RESET,
        PD692X0_MSG_GET_SYS_STATUS,
        PD692X0_MSG_GET_SW_VER,
        PD692X0_MSG_SET_TMP_PORT_MATRIX,
        PD692X0_MSG_PRG_PORT_MATRIX,
        PD692X0_MSG_SET_PORT_PARAM,
        PD692X0_MSG_GET_PORT_STATUS,
        PD692X0_MSG_DOWNLOAD_CMD,
        PD692X0_MSG_GET_PORT_CLASS,
        PD692X0_MSG_GET_PORT_MEAS,
        PD692X0_MSG_GET_PORT_PARAM,
        PD692X0_MSG_GET_POWER_BANK,
        PD692X0_MSG_SET_POWER_BANK,
        PD692X0_MSG_SET_USER_BYTE,

        /* add new message above here */
        PD692X0_MSG_CNT
};

struct pd692x0_matrix {
        u8 hw_port_a;
        u8 hw_port_b;
};

struct pd692x0_priv {
        struct i2c_client *client;
        struct pse_controller_dev pcdev;
        struct device_node *np;

        enum pd692x0_fw_state fw_state;
        struct fw_upload *fwl;
        bool cancel_request;

        u8 msg_id;
        bool last_cmd_key;
        unsigned long last_cmd_key_time;

        bool cfg_saved;
        enum ethtool_c33_pse_admin_state admin_state[PD692X0_MAX_PIS];
        struct regulator_dev *manager_reg[PD692X0_MAX_MANAGERS];
        int manager_pw_budget[PD692X0_MAX_MANAGERS];
        int nmanagers;
        struct pd692x0_matrix *port_matrix;
};

/* Template list of communication messages. The non-null bytes defined here
 * constitute the fixed portion of the messages. The remaining bytes will
 * be configured later within the functions. Refer to the "PD692x0 BT Serial
 * Communication Protocol User Guide" for comprehensive details on messages
 * content.
 */
static const struct pd692x0_msg pd692x0_msg_template_list[PD692X0_MSG_CNT] = {
        [PD692X0_MSG_RESET] = {
                .key = PD692X0_KEY_CMD,
                .sub = {0x07, 0x55, 0x00},
                .data = {0x55, 0x00, 0x55, 0x4e,
                         0x4e, 0x4e, 0x4e, 0x4e},
        },
        [PD692X0_MSG_GET_SYS_STATUS] = {
                .key = PD692X0_KEY_REQ,
                .sub = {0x07, 0xd0, 0x4e},
                .data = {0x4e, 0x4e, 0x4e, 0x4e,
                         0x4e, 0x4e, 0x4e, 0x4e},
        },
        [PD692X0_MSG_GET_SW_VER] = {
                .key = PD692X0_KEY_REQ,
                .sub = {0x07, 0x1e, 0x21},
                .data = {0x4e, 0x4e, 0x4e, 0x4e,
                         0x4e, 0x4e, 0x4e, 0x4e},
        },
        [PD692X0_MSG_SET_TMP_PORT_MATRIX] = {
                .key = PD692X0_KEY_CMD,
                .sub     = {0x05, 0x43},
                .data = {   0, 0x4e, 0x4e, 0x4e,
                         0x4e, 0x4e, 0x4e, 0x4e},
        },
        [PD692X0_MSG_PRG_PORT_MATRIX] = {
                .key = PD692X0_KEY_CMD,
                .sub = {0x07, 0x43, 0x4e},
                .data = {0x4e, 0x4e, 0x4e, 0x4e,
                         0x4e, 0x4e, 0x4e, 0x4e},
        },
        [PD692X0_MSG_SET_PORT_PARAM] = {
                .key = PD692X0_KEY_CMD,
                .sub = {0x05, 0xc0},
                .data = { 0xf, 0xff, 0xff, 0xff,
                         0x4e, 0x4e, 0x4e, 0x4e},
        },
        [PD692X0_MSG_GET_PORT_STATUS] = {
                .key = PD692X0_KEY_REQ,
                .sub = {0x05, 0xc1},
                .data = {0x4e, 0x4e, 0x4e, 0x4e,
                         0x4e, 0x4e, 0x4e, 0x4e},
        },
        [PD692X0_MSG_DOWNLOAD_CMD] = {
                .key = PD692X0_KEY_PRG,
                .sub = {0xff, 0x99, 0x15},
                .data = {0x16, 0x16, 0x99, 0x4e,
                         0x4e, 0x4e, 0x4e, 0x4e},
        },
        [PD692X0_MSG_GET_PORT_CLASS] = {
                .key = PD692X0_KEY_REQ,
                .sub = {0x05, 0xc4},
                .data = {0x4e, 0x4e, 0x4e, 0x4e,
                         0x4e, 0x4e, 0x4e, 0x4e},
        },
        [PD692X0_MSG_GET_PORT_MEAS] = {
                .key = PD692X0_KEY_REQ,
                .sub = {0x05, 0xc5},
                .data = {0x4e, 0x4e, 0x4e, 0x4e,
                         0x4e, 0x4e, 0x4e, 0x4e},
        },
        [PD692X0_MSG_GET_PORT_PARAM] = {
                .key = PD692X0_KEY_REQ,
                .sub = {0x05, 0xc0},
                .data = {0x4e, 0x4e, 0x4e, 0x4e,
                         0x4e, 0x4e, 0x4e, 0x4e},
        },
        [PD692X0_MSG_GET_POWER_BANK] = {
                .key = PD692X0_KEY_REQ,
                .sub = {0x07, 0x0b, 0x57},
                .data = {   0, 0x4e, 0x4e, 0x4e,
                         0x4e, 0x4e, 0x4e, 0x4e},
        },
        [PD692X0_MSG_SET_POWER_BANK] = {
                .key = PD692X0_KEY_CMD,
                .sub = {0x07, 0x0b, 0x57},
        },
        [PD692X0_MSG_SET_USER_BYTE] = {
                .key = PD692X0_KEY_PRG,
                .sub = {0x41, PD692X0_USER_BYTE, 0x4e},
                .data = {0x4e, 0x4e, 0x4e, 0x4e,
                         0x4e, 0x4e, 0x4e, 0x4e},
        },
};

static u8 pd692x0_build_msg(struct pd692x0_msg *msg, u8 echo)
{
        u8 *data = (u8 *)msg;
        u16 chksum = 0;
        int i;

        msg->echo = echo++;
        if (echo == 0xff)
                echo = 0;

        for (i = 0; i < sizeof(*msg) - sizeof(msg->chksum); i++)
                chksum += data[i];

        msg->chksum = cpu_to_be16(chksum);

        return echo;
}

static int pd692x0_send_msg(struct pd692x0_priv *priv, struct pd692x0_msg *msg)
{
        const struct i2c_client *client = priv->client;
        int ret;

        if (msg->key == PD692X0_KEY_CMD && priv->last_cmd_key) {
                int cmd_msleep;

                cmd_msleep = 30 - jiffies_to_msecs(jiffies - priv->last_cmd_key_time);
                if (cmd_msleep > 0)
                        msleep(cmd_msleep);
        }

        /* Add echo and checksum bytes to the message */
        priv->msg_id = pd692x0_build_msg(msg, priv->msg_id);

        ret = i2c_master_send(client, (u8 *)msg, sizeof(*msg));
        if (ret != sizeof(*msg))
                return -EIO;

        return 0;
}

static int pd692x0_reset(struct pd692x0_priv *priv)
{
        const struct i2c_client *client = priv->client;
        struct pd692x0_msg msg, buf = {0};
        int ret;

        msg = pd692x0_msg_template_list[PD692X0_MSG_RESET];
        ret = pd692x0_send_msg(priv, &msg);
        if (ret) {
                dev_err(&client->dev,
                        "Failed to reset the controller (%pe)\n", ERR_PTR(ret));
                return ret;
        }

        msleep(30);

        ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf));
        if (ret != sizeof(buf))
                return ret < 0 ? ret : -EIO;

        /* Is the reply a successful report message */
        if (buf.key != PD692X0_KEY_REPORT || buf.sub[0] || buf.sub[1])
                return -EIO;

        msleep(300);

        ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf));
        if (ret != sizeof(buf))
                return ret < 0 ? ret : -EIO;

        /* Is the boot status without error */
        if (buf.key != 0x03 || buf.echo != 0xff || buf.sub[0] & 0x1) {
                dev_err(&client->dev, "PSE controller error\n");
                return -EIO;
        }

        return 0;
}

static bool pd692x0_try_recv_msg(const struct i2c_client *client,
                                 struct pd692x0_msg *msg,
                                 struct pd692x0_msg *buf)
{
        /* Wait 30ms before readback as mandated by the protocol */
        msleep(30);

        memset(buf, 0, sizeof(*buf));
        i2c_master_recv(client, (u8 *)buf, sizeof(*buf));
        if (buf->key)
                return 0;

        msleep(100);

        memset(buf, 0, sizeof(*buf));
        i2c_master_recv(client, (u8 *)buf, sizeof(*buf));
        if (buf->key)
                return 0;

        return 1;
}

/* Implementation of I2C communication, specifically addressing scenarios
 * involving communication loss. Refer to the "Synchronization During
 * Communication Loss" section in the Communication Protocol document for
 * further details.
 */
static int pd692x0_recv_msg(struct pd692x0_priv *priv,
                            struct pd692x0_msg *msg,
                            struct pd692x0_msg *buf)
{
        const struct i2c_client *client = priv->client;
        int ret;

        ret = pd692x0_try_recv_msg(client, msg, buf);
        if (!ret)
                goto out_success;

        dev_warn(&client->dev,
                 "Communication lost, rtnl is locked until communication is back!");

        ret = pd692x0_send_msg(priv, msg);
        if (ret)
                return ret;

        ret = pd692x0_try_recv_msg(client, msg, buf);
        if (!ret)
                goto out_success2;

        msleep(10000);

        ret = pd692x0_send_msg(priv, msg);
        if (ret)
                return ret;

        ret = pd692x0_try_recv_msg(client, msg, buf);
        if (!ret)
                goto out_success2;

        return pd692x0_reset(priv);

out_success2:
        dev_warn(&client->dev, "Communication is back, rtnl is unlocked!");
out_success:
        if (msg->key == PD692X0_KEY_CMD) {
                priv->last_cmd_key = true;
                priv->last_cmd_key_time = jiffies;
        } else {
                priv->last_cmd_key = false;
        }

        return 0;
}

static int pd692x0_sendrecv_msg(struct pd692x0_priv *priv,
                                struct pd692x0_msg *msg,
                                struct pd692x0_msg *buf)
{
        struct device *dev = &priv->client->dev;
        int ret;

        ret = pd692x0_send_msg(priv, msg);
        if (ret)
                return ret;

        ret = pd692x0_recv_msg(priv, msg, buf);
        if (ret)
                return ret;

        if (msg->echo != buf->echo) {
                dev_err(dev,
                        "Wrong match in message ID, expect %d received %d.\n",
                        msg->echo, buf->echo);
                return -EIO;
        }

        /* If the reply is a report message is it successful */
        if (buf->key == PD692X0_KEY_REPORT &&
            (buf->sub[0] || buf->sub[1])) {
                return -EIO;
        }

        return 0;
}

static struct pd692x0_priv *to_pd692x0_priv(struct pse_controller_dev *pcdev)
{
        return container_of(pcdev, struct pd692x0_priv, pcdev);
}

static int pd692x0_fw_unavailable(struct pd692x0_priv *priv)
{
        switch (priv->fw_state) {
        case PD692X0_FW_OK:
                return 0;
        case PD692X0_FW_PREPARE:
        case PD692X0_FW_WRITE:
        case PD692X0_FW_COMPLETE:
                dev_err(&priv->client->dev, "Firmware update in progress!\n");
                return -EBUSY;
        case PD692X0_FW_BROKEN:
        case PD692X0_FW_NEED_UPDATE:
        default:
                dev_err(&priv->client->dev,
                        "Firmware issue. Please update it!\n");
                return -EOPNOTSUPP;
        }
}

static int pd692x0_pi_enable(struct pse_controller_dev *pcdev, int id)
{
        struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
        struct pd692x0_msg msg, buf = {0};
        int ret;

        ret = pd692x0_fw_unavailable(priv);
        if (ret)
                return ret;

        if (priv->admin_state[id] == ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED)
                return 0;

        msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM];
        msg.data[0] = 0x1;
        msg.sub[2] = id;
        ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
        if (ret < 0)
                return ret;

        priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;

        return 0;
}

static int pd692x0_pi_disable(struct pse_controller_dev *pcdev, int id)
{
        struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
        struct pd692x0_msg msg, buf = {0};
        int ret;

        ret = pd692x0_fw_unavailable(priv);
        if (ret)
                return ret;

        if (priv->admin_state[id] == ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED)
                return 0;

        msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM];
        msg.data[0] = 0x0;
        msg.sub[2] = id;
        ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
        if (ret < 0)
                return ret;

        priv->admin_state[id] = ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;

        return 0;
}

struct pd692x0_pse_ext_state_mapping {
        u32 status_code;
        enum ethtool_c33_pse_ext_state pse_ext_state;
        u32 pse_ext_substate;
};

static const struct pd692x0_pse_ext_state_mapping
pd692x0_pse_ext_state_map[] = {
        {0x06, ETHTOOL_C33_PSE_EXT_STATE_OPTION_VPORT_LIM,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_VPORT_LIM_HIGH_VOLTAGE},
        {0x07, ETHTOOL_C33_PSE_EXT_STATE_OPTION_VPORT_LIM,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_VPORT_LIM_LOW_VOLTAGE},
        {0x08, ETHTOOL_C33_PSE_EXT_STATE_MR_PSE_ENABLE,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_MR_PSE_ENABLE_DISABLE_PIN_ACTIVE},
        {0x0C, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_NON_EXISTING_PORT},
        {0x11, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNDEFINED_PORT},
        {0x12, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_INTERNAL_HW_FAULT},
        {0x1B, ETHTOOL_C33_PSE_EXT_STATE_OPTION_DETECT_TED,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_DETECT_TED_DET_IN_PROCESS},
        {0x1C, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNKNOWN_PORT_STATUS},
        {0x1E, ETHTOOL_C33_PSE_EXT_STATE_MR_MPS_VALID,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_MR_MPS_VALID_DETECTED_UNDERLOAD},
        {0x1F, ETHTOOL_C33_PSE_EXT_STATE_OVLD_DETECTED,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_OVLD_DETECTED_OVERLOAD},
        {0x20, ETHTOOL_C33_PSE_EXT_STATE_POWER_NOT_AVAILABLE,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_POWER_NOT_AVAILABLE_BUDGET_EXCEEDED},
        {0x21, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_INTERNAL_HW_FAULT},
        {0x22, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_CONFIG_CHANGE},
        {0x24, ETHTOOL_C33_PSE_EXT_STATE_OPTION_VPORT_LIM,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_VPORT_LIM_VOLTAGE_INJECTION},
        {0x25, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNKNOWN_PORT_STATUS},
        {0x34, ETHTOOL_C33_PSE_EXT_STATE_SHORT_DETECTED,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_SHORT_DETECTED_SHORT_CONDITION},
        {0x35, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_DETECTED_OVER_TEMP},
        {0x36, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_DETECTED_OVER_TEMP},
        {0x37, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNKNOWN_PORT_STATUS},
        {0x3C, ETHTOOL_C33_PSE_EXT_STATE_POWER_NOT_AVAILABLE,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_POWER_NOT_AVAILABLE_PORT_PW_LIMIT_EXCEEDS_CONTROLLER_BUDGET},
        {0x3D, ETHTOOL_C33_PSE_EXT_STATE_POWER_NOT_AVAILABLE,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_POWER_NOT_AVAILABLE_PD_REQUEST_EXCEEDS_PORT_LIMIT},
        {0x41, ETHTOOL_C33_PSE_EXT_STATE_POWER_NOT_AVAILABLE,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_POWER_NOT_AVAILABLE_HW_PW_LIMIT},
        {0x43, ETHTOOL_C33_PSE_EXT_STATE_ERROR_CONDITION,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_ERROR_CONDITION_UNKNOWN_PORT_STATUS},
        {0xA7, ETHTOOL_C33_PSE_EXT_STATE_OPTION_DETECT_TED,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_OPTION_DETECT_TED_CONNECTION_CHECK_ERROR},
        {0xA8, ETHTOOL_C33_PSE_EXT_STATE_MR_MPS_VALID,
                ETHTOOL_C33_PSE_EXT_SUBSTATE_MR_MPS_VALID_CONNECTION_OPEN},
        { /* sentinel */ }
};

static int
pd692x0_pi_get_ext_state(struct pse_controller_dev *pcdev, int id,
                         struct pse_ext_state_info *ext_state_info)
{
        struct ethtool_c33_pse_ext_state_info *c33_ext_state_info;
        const struct pd692x0_pse_ext_state_mapping *ext_state_map;
        struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
        struct pd692x0_msg msg, buf = {0};
        int ret;

        ret = pd692x0_fw_unavailable(priv);
        if (ret)
                return ret;

        msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
        msg.sub[2] = id;
        ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
        if (ret < 0)
                return ret;

        c33_ext_state_info = &ext_state_info->c33_ext_state_info;
        ext_state_map = pd692x0_pse_ext_state_map;
        while (ext_state_map->status_code) {
                if (ext_state_map->status_code == buf.sub[0]) {
                        c33_ext_state_info->c33_pse_ext_state = ext_state_map->pse_ext_state;
                        c33_ext_state_info->__c33_pse_ext_substate = ext_state_map->pse_ext_substate;
                        return  0;
                }
                ext_state_map++;
        }

        return 0;
}

struct pd692x0_class_pw {
        int class;
        int class_cfg_value;
        int class_pw;
        int max_added_class_pw;
};

#define PD692X0_CLASS_PW_TABLE_SIZE 4
/* 4/2 pairs class configuration power table in compliance mode.
 * Need to be arranged in ascending order of power support.
 */
static const struct pd692x0_class_pw
pd692x0_class_pw_table[PD692X0_CLASS_PW_TABLE_SIZE] = {
        {.class = 3, .class_cfg_value = 0x3, .class_pw = 15000, .max_added_class_pw = 3100},
        {.class = 4, .class_cfg_value = 0x2, .class_pw = 30000, .max_added_class_pw = 8000},
        {.class = 6, .class_cfg_value = 0x1, .class_pw = 60000, .max_added_class_pw = 5000},
        {.class = 8, .class_cfg_value = 0x0, .class_pw = 90000, .max_added_class_pw = 7500},
};

static int pd692x0_pi_get_pw_from_table(int op_mode, int added_pw)
{
        const struct pd692x0_class_pw *pw_table;
        int i;

        pw_table = pd692x0_class_pw_table;
        for (i = 0; i < PD692X0_CLASS_PW_TABLE_SIZE; i++, pw_table++) {
                if (pw_table->class_cfg_value == op_mode)
                        return pw_table->class_pw + added_pw * 100;
        }

        return -ERANGE;
}

static int pd692x0_pi_set_pw_from_table(struct device *dev,
                                        struct pd692x0_msg *msg, int pw)
{
        const struct pd692x0_class_pw *pw_table;
        int i;

        pw_table = pd692x0_class_pw_table;
        if (pw < pw_table->class_pw) {
                dev_err(dev,
                        "Power limit %dmW not supported. Ranges minimal available: [%d-%d]\n",
                        pw,
                        pw_table->class_pw,
                        pw_table->class_pw + pw_table->max_added_class_pw);
                return -ERANGE;
        }

        for (i = 0; i < PD692X0_CLASS_PW_TABLE_SIZE; i++, pw_table++) {
                if (pw > (pw_table->class_pw + pw_table->max_added_class_pw))
                        continue;

                if (pw < pw_table->class_pw) {
                        dev_err(dev,
                                "Power limit %dmW not supported. Ranges available: [%d-%d] or [%d-%d]\n",
                                pw,
                                (pw_table - 1)->class_pw,
                                (pw_table - 1)->class_pw + (pw_table - 1)->max_added_class_pw,
                                pw_table->class_pw,
                                pw_table->class_pw + pw_table->max_added_class_pw);
                        return -ERANGE;
                }

                msg->data[2] = pw_table->class_cfg_value;
                msg->data[3] = (pw - pw_table->class_pw) / 100;
                return 0;
        }

        pw_table--;
        dev_warn(dev,
                 "Power limit %dmW not supported. Set to highest power limit %dmW\n",
                 pw, pw_table->class_pw + pw_table->max_added_class_pw);
        msg->data[2] = pw_table->class_cfg_value;
        msg->data[3] = pw_table->max_added_class_pw / 100;
        return 0;
}

static int
pd692x0_pi_get_pw_limit_ranges(struct pse_controller_dev *pcdev, int id,
                               struct pse_pw_limit_ranges *pw_limit_ranges)
{
        struct ethtool_c33_pse_pw_limit_range *c33_pw_limit_ranges;
        const struct pd692x0_class_pw *pw_table;
        int i;

        pw_table = pd692x0_class_pw_table;
        c33_pw_limit_ranges = kzalloc_objs(*c33_pw_limit_ranges,
                                           PD692X0_CLASS_PW_TABLE_SIZE);
        if (!c33_pw_limit_ranges)
                return -ENOMEM;

        for (i = 0; i < PD692X0_CLASS_PW_TABLE_SIZE; i++, pw_table++) {
                c33_pw_limit_ranges[i].min = pw_table->class_pw;
                c33_pw_limit_ranges[i].max = pw_table->class_pw +
                                             pw_table->max_added_class_pw;
        }

        pw_limit_ranges->c33_pw_limit_ranges = c33_pw_limit_ranges;
        return i;
}

static int
pd692x0_pi_get_admin_state(struct pse_controller_dev *pcdev, int id,
                           struct pse_admin_state *admin_state)
{
        struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
        struct pd692x0_msg msg, buf = {0};
        int ret;

        ret = pd692x0_fw_unavailable(priv);
        if (ret)
                return ret;

        msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
        msg.sub[2] = id;
        ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
        if (ret < 0)
                return ret;

        if (buf.sub[1])
                admin_state->c33_admin_state =
                        ETHTOOL_C33_PSE_ADMIN_STATE_ENABLED;
        else
                admin_state->c33_admin_state =
                        ETHTOOL_C33_PSE_ADMIN_STATE_DISABLED;

        priv->admin_state[id] = admin_state->c33_admin_state;

        return 0;
}

static int
pd692x0_pi_get_pw_status(struct pse_controller_dev *pcdev, int id,
                         struct pse_pw_status *pw_status)
{
        struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
        struct pd692x0_msg msg, buf = {0};
        int ret;

        ret = pd692x0_fw_unavailable(priv);
        if (ret)
                return ret;

        msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
        msg.sub[2] = id;
        ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
        if (ret < 0)
                return ret;

        /* Compare Port Status (Communication Protocol Document par. 7.1) */
        if ((buf.sub[0] & 0xf0) == 0x80 || (buf.sub[0] & 0xf0) == 0x90)
                pw_status->c33_pw_status =
                        ETHTOOL_C33_PSE_PW_D_STATUS_DELIVERING;
        else if (buf.sub[0] == 0x1b || buf.sub[0] == 0x22)
                pw_status->c33_pw_status =
                        ETHTOOL_C33_PSE_PW_D_STATUS_SEARCHING;
        else if (buf.sub[0] == 0x12)
                pw_status->c33_pw_status =
                        ETHTOOL_C33_PSE_PW_D_STATUS_FAULT;
        else
                pw_status->c33_pw_status =
                        ETHTOOL_C33_PSE_PW_D_STATUS_DISABLED;

        return 0;
}

static int
pd692x0_pi_get_pw_class(struct pse_controller_dev *pcdev, int id)
{
        struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
        struct pd692x0_msg msg, buf = {0};
        u32 class;
        int ret;

        ret = pd692x0_fw_unavailable(priv);
        if (ret)
                return ret;

        msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_CLASS];
        msg.sub[2] = id;
        ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
        if (ret < 0)
                return ret;

        class = buf.data[3] >> 4;
        if (class <= 8)
                return class;

        return 0;
}

static int
pd692x0_pi_get_actual_pw(struct pse_controller_dev *pcdev, int id)
{
        struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
        struct pd692x0_msg msg, buf = {0};
        int ret;

        ret = pd692x0_fw_unavailable(priv);
        if (ret)
                return ret;

        msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_STATUS];
        msg.sub[2] = id;
        ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
        if (ret < 0)
                return ret;

        return (buf.data[0] << 4 | buf.data[1]) * 100;
}

static int
pd692x0_pi_get_prio(struct pse_controller_dev *pcdev, int id)
{
        struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
        struct pd692x0_msg msg, buf = {0};
        int ret;

        ret = pd692x0_fw_unavailable(priv);
        if (ret)
                return ret;

        msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM];
        msg.sub[2] = id;
        ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
        if (ret < 0)
                return ret;
        if (!buf.data[2] || buf.data[2] > pcdev->pis_prio_max + 1)
                return -ERANGE;

        /* PSE core priority start at 0 */
        return buf.data[2] - 1;
}

static struct pd692x0_msg_ver pd692x0_get_sw_version(struct pd692x0_priv *priv)
{
        struct device *dev = &priv->client->dev;
        struct pd692x0_msg msg, buf = {0};
        struct pd692x0_msg_ver ver = {0};
        int ret;

        msg = pd692x0_msg_template_list[PD692X0_MSG_GET_SW_VER];
        ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
        if (ret < 0) {
                dev_err(dev, "Failed to get PSE version (%pe)\n", ERR_PTR(ret));
                return ver;
        }

        /* Extract version from the message */
        ver.prod = buf.sub[2];
        ver.maj_sw_ver = (buf.data[0] << 8 | buf.data[1]) / 100;
        ver.min_sw_ver = ((buf.data[0] << 8 | buf.data[1]) / 10) % 10;
        ver.pa_sw_ver = (buf.data[0] << 8 | buf.data[1]) % 10;
        ver.param = buf.data[2];
        ver.build = buf.data[3];

        return ver;
}

struct pd692x0_manager {
        struct device_node *port_node[PD692X0_MAX_MANAGER_PORTS];
        struct device_node *node;
        int nports;
};

static int
pd692x0_of_get_ports_manager(struct pd692x0_priv *priv,
                             struct pd692x0_manager *manager,
                             struct device_node *np)
{
        struct device_node *node;
        int ret, nports, i;

        nports = 0;
        for_each_child_of_node(np, node) {
                u32 port;

                if (!of_node_name_eq(node, "port"))
                        continue;

                ret = of_property_read_u32(node, "reg", &port);
                if (ret)
                        goto out;

                if (port >= PD692X0_MAX_MANAGER_PORTS || port != nports) {
                        dev_err(&priv->client->dev,
                                "wrong number or order of manager ports (%d)\n",
                                port);
                        ret = -EINVAL;
                        goto out;
                }

                of_node_get(node);
                manager->port_node[port] = node;
                nports++;
        }

        manager->nports = nports;
        return 0;

out:
        for (i = 0; i < nports; i++) {
                of_node_put(manager->port_node[i]);
                manager->port_node[i] = NULL;
        }
        of_node_put(node);
        return ret;
}

static int
pd692x0_of_get_managers(struct pd692x0_priv *priv,
                        struct pd692x0_manager *manager)
{
        struct device_node *managers_node, *node;
        int ret, nmanagers, i, j;

        if (!priv->np)
                return -EINVAL;

        nmanagers = 0;
        managers_node = of_get_child_by_name(priv->np, "managers");
        if (!managers_node)
                return -EINVAL;

        for_each_child_of_node(managers_node, node) {
                u32 manager_id;

                if (!of_node_name_eq(node, "manager"))
                        continue;

                ret = of_property_read_u32(node, "reg", &manager_id);
                if (ret)
                        goto out;

                if (manager_id >= PD692X0_MAX_MANAGERS ||
                    manager_id != nmanagers) {
                        dev_err(&priv->client->dev,
                                "wrong number or order of managers (%d)\n",
                                manager_id);
                        ret = -EINVAL;
                        goto out;
                }

                ret = pd692x0_of_get_ports_manager(priv, &manager[manager_id],
                                                   node);
                if (ret)
                        goto out;

                of_node_get(node);
                manager[manager_id].node = node;
                nmanagers++;
        }

        of_node_put(managers_node);
        priv->nmanagers = nmanagers;
        return 0;

out:
        for (i = 0; i < nmanagers; i++) {
                for (j = 0; j < manager[i].nports; j++) {
                        of_node_put(manager[i].port_node[j]);
                        manager[i].port_node[j] = NULL;
                }
                of_node_put(manager[i].node);
                manager[i].node = NULL;
        }

        of_node_put(node);
        of_node_put(managers_node);
        return ret;
}

static const struct regulator_ops dummy_ops;

static struct regulator_dev *
pd692x0_register_manager_regulator(struct device *dev, char *reg_name,
                                   struct device_node *node)
{
        struct regulator_init_data *rinit_data;
        struct regulator_config rconfig = {0};
        struct regulator_desc *rdesc;
        struct regulator_dev *rdev;

        rinit_data = devm_kzalloc(dev, sizeof(*rinit_data),
                                  GFP_KERNEL);
        if (!rinit_data)
                return ERR_PTR(-ENOMEM);

        rdesc = devm_kzalloc(dev, sizeof(*rdesc), GFP_KERNEL);
        if (!rdesc)
                return ERR_PTR(-ENOMEM);

        rdesc->name = reg_name;
        rdesc->type = REGULATOR_VOLTAGE;
        rdesc->ops = &dummy_ops;
        rdesc->owner = THIS_MODULE;

        rinit_data->supply_regulator = "vmain";

        rconfig.dev = dev;
        rconfig.init_data = rinit_data;
        rconfig.of_node = node;

        rdev = devm_regulator_register(dev, rdesc, &rconfig);
        if (IS_ERR(rdev)) {
                dev_err_probe(dev, PTR_ERR(rdev),
                              "Failed to register regulator\n");
                return rdev;
        }

        return rdev;
}

static int
pd692x0_register_managers_regulator(struct pd692x0_priv *priv,
                                    const struct pd692x0_manager *manager)
{
        struct device *dev = &priv->client->dev;
        size_t reg_name_len;
        int i;

        /* Each regulator name len is dev name + 12 char +
         * int max digit number (10) + 1
         */
        reg_name_len = strlen(dev_name(dev)) + 23;

        for (i = 0; i < priv->nmanagers; i++) {
                static const char * const regulators[] = { "vaux5", "vaux3p3" };
                struct regulator_dev *rdev;
                char *reg_name;
                int ret;

                reg_name = devm_kzalloc(dev, reg_name_len, GFP_KERNEL);
                if (!reg_name)
                        return -ENOMEM;
                snprintf(reg_name, 26, "pse-%s-manager%d", dev_name(dev), i);
                rdev = pd692x0_register_manager_regulator(dev, reg_name,
                                                          manager[i].node);
                if (IS_ERR(rdev))
                        return PTR_ERR(rdev);

                /* VMAIN is described as main supply for the manager.
                 * Add other VAUX power supplies and link them to the
                 * virtual device rdev->dev.
                 */
                ret = devm_regulator_bulk_get_enable(&rdev->dev,
                                                     ARRAY_SIZE(regulators),
                                                     regulators);
                if (ret)
                        return dev_err_probe(&rdev->dev, ret,
                                             "Failed to enable regulators\n");

                priv->manager_reg[i] = rdev;
        }

        return 0;
}

static int
pd692x0_conf_manager_power_budget(struct pd692x0_priv *priv, int id)
{
        struct pd692x0_msg msg, buf;
        int ret, pw_mW;

        pw_mW = priv->manager_pw_budget[id] / 1000;
        if (!pw_mW)
                return 0;

        msg = pd692x0_msg_template_list[PD692X0_MSG_GET_POWER_BANK];
        msg.data[0] = id;
        ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
        if (ret < 0)
                return ret;

        msg = pd692x0_msg_template_list[PD692X0_MSG_SET_POWER_BANK];
        msg.data[0] = id;
        msg.data[1] = pw_mW >> 8;
        msg.data[2] = pw_mW & 0xff;
        msg.data[3] = buf.sub[2];
        msg.data[4] = buf.data[0];
        msg.data[5] = buf.data[1];
        msg.data[6] = buf.data[2];
        msg.data[7] = buf.data[3];
        return pd692x0_sendrecv_msg(priv, &msg, &buf);
}

static int
pd692x0_req_managers_pw_budget(struct pd692x0_priv *priv)
{
        int i, ret;

        for (i = 0; i < priv->nmanagers; i++) {
                struct regulator *supply = priv->manager_reg[i]->supply;
                int pw_budget;

                pw_budget = regulator_get_unclaimed_power_budget(supply);
                if (!pw_budget)
                        /* Do nothing if no power budget */
                        continue;

                /* Max power budget per manager */
                if (pw_budget > 6000000)
                        pw_budget = 6000000;
                ret = regulator_request_power_budget(supply, pw_budget);
                if (ret < 0)
                        return ret;

                priv->manager_pw_budget[i] = pw_budget;
        }

        return 0;
}

static int
pd692x0_configure_managers(struct pd692x0_priv *priv)
{
        int i, ret;

        for (i = 0; i < priv->nmanagers; i++) {
                ret = pd692x0_conf_manager_power_budget(priv, i);
                if (ret < 0)
                        return ret;
        }

        return 0;
}

static int
pd692x0_set_port_matrix(const struct pse_pi_pairset *pairset,
                        const struct pd692x0_manager *manager,
                        int nmanagers, struct pd692x0_matrix *port_matrix)
{
        int i, j, port_cnt;
        bool found = false;

        if (!pairset->np)
                return 0;

        /* Look on every managers */
        port_cnt = 0;
        for (i = 0; i < nmanagers; i++) {
                /* Look on every ports of the manager */
                for (j = 0; j < manager[i].nports; j++) {
                        if (pairset->np == manager[i].port_node[j]) {
                                found = true;
                                break;
                        }
                }
                port_cnt += j;

                if (found)
                        break;
        }

        if (!found)
                return -ENODEV;

        if (pairset->pinout == ALTERNATIVE_A)
                port_matrix->hw_port_a = port_cnt;
        else if (pairset->pinout == ALTERNATIVE_B)
                port_matrix->hw_port_b = port_cnt;

        return 0;
}

static int
pd692x0_set_ports_matrix(struct pd692x0_priv *priv,
                         const struct pd692x0_manager *manager)
{
        struct pd692x0_matrix *port_matrix = priv->port_matrix;
        struct pse_controller_dev *pcdev = &priv->pcdev;
        int i, ret;

        /* Init Matrix */
        for (i = 0; i < PD692X0_MAX_PIS; i++) {
                port_matrix[i].hw_port_a = 0xff;
                port_matrix[i].hw_port_b = 0xff;
        }

        /* Update with values for every PSE PIs */
        for (i = 0; i < pcdev->nr_lines; i++) {
                ret = pd692x0_set_port_matrix(&pcdev->pi[i].pairset[0],
                                              manager, priv->nmanagers,
                                              &port_matrix[i]);
                if (ret) {
                        dev_err(&priv->client->dev,
                                "unable to configure pi %d pairset 0", i);
                        return ret;
                }

                ret = pd692x0_set_port_matrix(&pcdev->pi[i].pairset[1],
                                              manager, priv->nmanagers,
                                              &port_matrix[i]);
                if (ret) {
                        dev_err(&priv->client->dev,
                                "unable to configure pi %d pairset 1", i);
                        return ret;
                }
        }

        return 0;
}

static int
pd692x0_write_ports_matrix(struct pd692x0_priv *priv)
{
        struct pd692x0_matrix *port_matrix = priv->port_matrix;
        struct pd692x0_msg msg, buf;
        int ret, i;

        /* Write temporary Matrix */
        msg = pd692x0_msg_template_list[PD692X0_MSG_SET_TMP_PORT_MATRIX];
        for (i = 0; i < PD692X0_MAX_PIS; i++) {
                msg.sub[2] = i;
                msg.data[0] = port_matrix[i].hw_port_b;
                msg.data[1] = port_matrix[i].hw_port_a;

                ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
                if (ret < 0)
                        return ret;
        }

        /* Program Matrix */
        msg = pd692x0_msg_template_list[PD692X0_MSG_PRG_PORT_MATRIX];
        ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
        if (ret < 0)
                return ret;

        return 0;
}

static int pd692x0_hw_conf_init(struct pd692x0_priv *priv)
{
        int ret;

        /* Is PD692x0 ready to be configured? */
        if (priv->fw_state != PD692X0_FW_OK &&
            priv->fw_state != PD692X0_FW_COMPLETE)
                return 0;

        ret = pd692x0_configure_managers(priv);
        if (ret)
                return ret;

        ret = pd692x0_write_ports_matrix(priv);
        if (ret)
                return ret;

        return 0;
}

static void pd692x0_of_put_managers(struct pd692x0_priv *priv,
                                    struct pd692x0_manager *manager)
{
        int i, j;

        for (i = 0; i < priv->nmanagers; i++) {
                for (j = 0; j < manager[i].nports; j++)
                        of_node_put(manager[i].port_node[j]);
                of_node_put(manager[i].node);
        }
}

static void pd692x0_managers_free_pw_budget(struct pd692x0_priv *priv)
{
        int i;

        for (i = 0; i < PD692X0_MAX_MANAGERS; i++) {
                struct regulator *supply;

                if (!priv->manager_reg[i] || !priv->manager_pw_budget[i])
                        continue;

                supply = priv->manager_reg[i]->supply;
                if (!supply)
                        continue;

                regulator_free_power_budget(supply,
                                            priv->manager_pw_budget[i]);
        }
}

static int
pd692x0_save_user_byte(struct pd692x0_priv *priv)
{
        struct pd692x0_msg msg, buf;

        msg = pd692x0_msg_template_list[PD692X0_MSG_SET_USER_BYTE];
        return pd692x0_sendrecv_msg(priv, &msg, &buf);
}

static int pd692x0_setup_pi_matrix(struct pse_controller_dev *pcdev)
{
        struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
        struct pd692x0_matrix *port_matrix;
        struct pd692x0_manager *manager;
        int ret;

        manager = kzalloc_objs(*manager, PD692X0_MAX_MANAGERS);
        if (!manager)
                return -ENOMEM;

        port_matrix = devm_kcalloc(&priv->client->dev, PD692X0_MAX_PIS,
                                   sizeof(*port_matrix), GFP_KERNEL);
        if (!port_matrix) {
                ret = -ENOMEM;
                goto err_free_manager;
        }
        priv->port_matrix = port_matrix;

        ret = pd692x0_of_get_managers(priv, manager);
        if (ret < 0)
                goto err_free_manager;

        ret = pd692x0_register_managers_regulator(priv, manager);
        if (ret)
                goto err_of_managers;

        ret = pd692x0_req_managers_pw_budget(priv);
        if (ret)
                goto err_of_managers;

        ret = pd692x0_set_ports_matrix(priv, manager);
        if (ret)
                goto err_managers_req_pw;

        /* Do not init the conf if it is already saved */
        if (!priv->cfg_saved) {
                ret = pd692x0_hw_conf_init(priv);
                if (ret)
                        goto err_managers_req_pw;

                ret = pd692x0_save_user_byte(priv);
                if (ret)
                        goto err_managers_req_pw;
        }

        pd692x0_of_put_managers(priv, manager);
        kfree(manager);
        return 0;

err_managers_req_pw:
        pd692x0_managers_free_pw_budget(priv);
err_of_managers:
        pd692x0_of_put_managers(priv, manager);
err_free_manager:
        kfree(manager);
        return ret;
}

static int pd692x0_pi_get_voltage(struct pse_controller_dev *pcdev, int id)
{
        struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
        struct pd692x0_msg msg, buf = {0};
        int ret;

        ret = pd692x0_fw_unavailable(priv);
        if (ret)
                return ret;

        msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_MEAS];
        msg.sub[2] = id;
        ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
        if (ret < 0)
                return ret;

        /* Convert 0.1V unit to uV */
        return (buf.sub[0] << 8 | buf.sub[1]) * 100000;
}

static int pd692x0_pi_get_pw_limit(struct pse_controller_dev *pcdev,
                                   int id)
{
        struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
        struct pd692x0_msg msg, buf = {0};
        int ret;

        msg = pd692x0_msg_template_list[PD692X0_MSG_GET_PORT_PARAM];
        msg.sub[2] = id;
        ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
        if (ret < 0)
                return ret;

        return pd692x0_pi_get_pw_from_table(buf.data[0], buf.data[1]);
}

static int pd692x0_pi_set_pw_limit(struct pse_controller_dev *pcdev,
                                   int id, int max_mW)
{
        struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
        struct device *dev = &priv->client->dev;
        struct pd692x0_msg msg, buf = {0};
        int ret;

        ret = pd692x0_fw_unavailable(priv);
        if (ret)
                return ret;

        msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM];
        msg.sub[2] = id;
        ret = pd692x0_pi_set_pw_from_table(dev, &msg, max_mW);
        if (ret)
                return ret;

        return pd692x0_sendrecv_msg(priv, &msg, &buf);
}

static int pd692x0_pi_set_prio(struct pse_controller_dev *pcdev, int id,
                               unsigned int prio)
{
        struct pd692x0_priv *priv = to_pd692x0_priv(pcdev);
        struct pd692x0_msg msg, buf = {0};
        int ret;

        ret = pd692x0_fw_unavailable(priv);
        if (ret)
                return ret;

        msg = pd692x0_msg_template_list[PD692X0_MSG_SET_PORT_PARAM];
        msg.sub[2] = id;
        /* Controller priority from 1 to 3 */
        msg.data[4] = prio + 1;

        return pd692x0_sendrecv_msg(priv, &msg, &buf);
}

static const struct pse_controller_ops pd692x0_ops = {
        .setup_pi_matrix = pd692x0_setup_pi_matrix,
        .pi_get_admin_state = pd692x0_pi_get_admin_state,
        .pi_get_pw_status = pd692x0_pi_get_pw_status,
        .pi_get_ext_state = pd692x0_pi_get_ext_state,
        .pi_get_pw_class = pd692x0_pi_get_pw_class,
        .pi_get_actual_pw = pd692x0_pi_get_actual_pw,
        .pi_enable = pd692x0_pi_enable,
        .pi_disable = pd692x0_pi_disable,
        .pi_get_voltage = pd692x0_pi_get_voltage,
        .pi_get_pw_limit = pd692x0_pi_get_pw_limit,
        .pi_set_pw_limit = pd692x0_pi_set_pw_limit,
        .pi_get_pw_limit_ranges = pd692x0_pi_get_pw_limit_ranges,
        .pi_get_prio = pd692x0_pi_get_prio,
        .pi_set_prio = pd692x0_pi_set_prio,
};

#define PD692X0_FW_LINE_MAX_SZ 0xff
static int pd692x0_fw_get_next_line(const u8 *data,
                                    char *line, size_t size)
{
        size_t line_size;
        int i;

        line_size = min_t(size_t, size, PD692X0_FW_LINE_MAX_SZ);

        memset(line, 0, PD692X0_FW_LINE_MAX_SZ);
        for (i = 0; i < line_size - 1; i++) {
                if (*data == '\r' && *(data + 1) == '\n') {
                        line[i] = '\r';
                        line[i + 1] = '\n';
                        return i + 2;
                }
                line[i] = *data;
                data++;
        }

        return -EIO;
}

static enum fw_upload_err
pd692x0_fw_recv_resp(const struct i2c_client *client, unsigned long ms_timeout,
                     const char *msg_ok, unsigned int msg_size)
{
        /* Maximum controller response size */
        char fw_msg_buf[5] = {0};
        unsigned long timeout;
        int ret;

        if (msg_size > sizeof(fw_msg_buf))
                return FW_UPLOAD_ERR_RW_ERROR;

        /* Read until we get something */
        timeout = msecs_to_jiffies(ms_timeout) + jiffies;
        while (true) {
                if (time_is_before_jiffies(timeout))
                        return FW_UPLOAD_ERR_TIMEOUT;

                ret = i2c_master_recv(client, fw_msg_buf, 1);
                if (ret < 0 || *fw_msg_buf == 0) {
                        usleep_range(1000, 2000);
                        continue;
                } else {
                        break;
                }
        }

        /* Read remaining characters */
        ret = i2c_master_recv(client, fw_msg_buf + 1, msg_size - 1);
        if (strncmp(fw_msg_buf, msg_ok, msg_size)) {
                dev_err(&client->dev,
                        "Wrong FW download process answer (%*pE)\n",
                        msg_size, fw_msg_buf);
                return FW_UPLOAD_ERR_HW_ERROR;
        }

        return FW_UPLOAD_ERR_NONE;
}

static int pd692x0_fw_write_line(const struct i2c_client *client,
                                 const char line[PD692X0_FW_LINE_MAX_SZ],
                                 const bool last_line)
{
        int ret;

        while (*line != 0) {
                ret = i2c_master_send(client, line, 1);
                if (ret < 0)
                        return FW_UPLOAD_ERR_RW_ERROR;
                line++;
        }

        if (last_line) {
                ret = pd692x0_fw_recv_resp(client, 100, "TP\r\n",
                                           sizeof("TP\r\n") - 1);
                if (ret)
                        return ret;
        } else {
                ret = pd692x0_fw_recv_resp(client, 100, "T*\r\n",
                                           sizeof("T*\r\n") - 1);
                if (ret)
                        return ret;
        }

        return FW_UPLOAD_ERR_NONE;
}

static enum fw_upload_err pd692x0_fw_reset(const struct i2c_client *client)
{
        const struct pd692x0_msg zero = {0};
        struct pd692x0_msg buf = {0};
        unsigned long timeout;
        char cmd[] = "RST";
        int ret;

        ret = i2c_master_send(client, cmd, strlen(cmd));
        if (ret < 0) {
                dev_err(&client->dev,
                        "Failed to reset the controller (%pe)\n",
                        ERR_PTR(ret));
                return ret;
        }

        timeout = msecs_to_jiffies(10000) + jiffies;
        while (true) {
                if (time_is_before_jiffies(timeout))
                        return FW_UPLOAD_ERR_TIMEOUT;

                ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf));
                if (ret < 0 ||
                    !memcmp(&buf, &zero, sizeof(buf)))
                        usleep_range(1000, 2000);
                else
                        break;
        }

        /* Is the reply a successful report message */
        if (buf.key != PD692X0_KEY_TLM || buf.echo != 0xff ||
            buf.sub[0] & 0x01) {
                dev_err(&client->dev, "PSE controller error\n");
                return FW_UPLOAD_ERR_HW_ERROR;
        }

        /* Is the firmware operational */
        if (buf.sub[0] & 0x02) {
                dev_err(&client->dev,
                        "PSE firmware error. Please update it.\n");
                return FW_UPLOAD_ERR_HW_ERROR;
        }

        return FW_UPLOAD_ERR_NONE;
}

static enum fw_upload_err pd692x0_fw_prepare(struct fw_upload *fwl,
                                             const u8 *data, u32 size)
{
        struct pd692x0_priv *priv = fwl->dd_handle;
        const struct i2c_client *client = priv->client;
        enum pd692x0_fw_state last_fw_state;
        int ret;

        priv->cancel_request = false;
        last_fw_state = priv->fw_state;

        priv->fw_state = PD692X0_FW_PREPARE;

        /* Enter program mode */
        if (last_fw_state == PD692X0_FW_BROKEN) {
                const char *msg = "ENTR";
                const char *c;

                c = msg;
                do {
                        ret = i2c_master_send(client, c, 1);
                        if (ret < 0)
                                return FW_UPLOAD_ERR_RW_ERROR;
                        if (*(c + 1))
                                usleep_range(10000, 20000);
                } while (*(++c));
        } else {
                struct pd692x0_msg msg, buf;

                msg = pd692x0_msg_template_list[PD692X0_MSG_DOWNLOAD_CMD];
                ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
                if (ret < 0) {
                        dev_err(&client->dev,
                                "Failed to enter programming mode (%pe)\n",
                                ERR_PTR(ret));
                        return FW_UPLOAD_ERR_RW_ERROR;
                }
        }

        ret = pd692x0_fw_recv_resp(client, 100, "TPE\r\n", sizeof("TPE\r\n") - 1);
        if (ret)
                goto err_out;

        if (priv->cancel_request) {
                ret = FW_UPLOAD_ERR_CANCELED;
                goto err_out;
        }

        return FW_UPLOAD_ERR_NONE;

err_out:
        pd692x0_fw_reset(priv->client);
        priv->fw_state = last_fw_state;
        return ret;
}

static enum fw_upload_err pd692x0_fw_write(struct fw_upload *fwl,
                                           const u8 *data, u32 offset,
                                           u32 size, u32 *written)
{
        struct pd692x0_priv *priv = fwl->dd_handle;
        char line[PD692X0_FW_LINE_MAX_SZ];
        const struct i2c_client *client;
        int ret, i;
        char cmd;

        client = priv->client;
        priv->fw_state = PD692X0_FW_WRITE;

        /* Erase */
        cmd = 'E';
        ret = i2c_master_send(client, &cmd, 1);
        if (ret < 0) {
                dev_err(&client->dev,
                        "Failed to boot programming mode (%pe)\n",
                        ERR_PTR(ret));
                return FW_UPLOAD_ERR_RW_ERROR;
        }

        ret = pd692x0_fw_recv_resp(client, 100, "TOE\r\n", sizeof("TOE\r\n") - 1);
        if (ret)
                return ret;

        ret = pd692x0_fw_recv_resp(client, 5000, "TE\r\n", sizeof("TE\r\n") - 1);
        if (ret)
                dev_warn(&client->dev,
                         "Failed to erase internal memory, however still try to write Firmware\n");

        ret = pd692x0_fw_recv_resp(client, 100, "TPE\r\n", sizeof("TPE\r\n") - 1);
        if (ret)
                dev_warn(&client->dev,
                         "Failed to erase internal memory, however still try to write Firmware\n");

        if (priv->cancel_request)
                return FW_UPLOAD_ERR_CANCELED;

        /* Program */
        cmd = 'P';
        ret = i2c_master_send(client, &cmd, sizeof(char));
        if (ret < 0) {
                dev_err(&client->dev,
                        "Failed to boot programming mode (%pe)\n",
                        ERR_PTR(ret));
                return ret;
        }

        ret = pd692x0_fw_recv_resp(client, 100, "TOP\r\n", sizeof("TOP\r\n") - 1);
        if (ret)
                return ret;

        i = 0;
        while (i < size) {
                ret = pd692x0_fw_get_next_line(data, line, size - i);
                if (ret < 0) {
                        ret = FW_UPLOAD_ERR_FW_INVALID;
                        goto err;
                }

                i += ret;
                data += ret;
                if (line[0] == 'S' && line[1] == '0') {
                        continue;
                } else if (line[0] == 'S' && line[1] == '7') {
                        ret = pd692x0_fw_write_line(client, line, true);
                        if (ret)
                                goto err;
                } else {
                        ret = pd692x0_fw_write_line(client, line, false);
                        if (ret)
                                goto err;
                }

                if (priv->cancel_request) {
                        ret = FW_UPLOAD_ERR_CANCELED;
                        goto err;
                }
        }
        *written = i;

        msleep(400);

        return FW_UPLOAD_ERR_NONE;

err:
        strscpy_pad(line, "S7\r\n", sizeof(line));
        pd692x0_fw_write_line(client, line, true);
        return ret;
}

static enum fw_upload_err pd692x0_fw_poll_complete(struct fw_upload *fwl)
{
        struct pd692x0_priv *priv = fwl->dd_handle;
        const struct i2c_client *client = priv->client;
        struct pd692x0_msg_ver ver;
        int ret;

        priv->fw_state = PD692X0_FW_COMPLETE;

        ret = pd692x0_fw_reset(client);
        if (ret)
                return ret;

        ver = pd692x0_get_sw_version(priv);
        if (ver.maj_sw_ver < PD692X0_FW_MAJ_VER) {
                dev_err(&client->dev,
                        "Too old firmware version. Please update it\n");
                priv->fw_state = PD692X0_FW_NEED_UPDATE;
                return FW_UPLOAD_ERR_FW_INVALID;
        }

        ret = pd692x0_hw_conf_init(priv);
        if (ret < 0) {
                dev_err(&client->dev, "Error configuring ports matrix (%pe)\n",
                        ERR_PTR(ret));
                priv->fw_state = PD692X0_FW_NEED_UPDATE;
                return FW_UPLOAD_ERR_HW_ERROR;
        }

        priv->fw_state = PD692X0_FW_OK;
        return FW_UPLOAD_ERR_NONE;
}

static void pd692x0_fw_cancel(struct fw_upload *fwl)
{
        struct pd692x0_priv *priv = fwl->dd_handle;

        priv->cancel_request = true;
}

static void pd692x0_fw_cleanup(struct fw_upload *fwl)
{
        struct pd692x0_priv *priv = fwl->dd_handle;

        switch (priv->fw_state) {
        case PD692X0_FW_WRITE:
                pd692x0_fw_reset(priv->client);
                fallthrough;
        case PD692X0_FW_COMPLETE:
                priv->fw_state = PD692X0_FW_BROKEN;
                break;
        default:
                break;
        }
}

static const struct fw_upload_ops pd692x0_fw_ops = {
        .prepare = pd692x0_fw_prepare,
        .write = pd692x0_fw_write,
        .poll_complete = pd692x0_fw_poll_complete,
        .cancel = pd692x0_fw_cancel,
        .cleanup = pd692x0_fw_cleanup,
};

static int pd692x0_i2c_probe(struct i2c_client *client)
{
        static const char * const regulators[] = { "vdd", "vdda" };
        struct pd692x0_msg msg, buf = {0}, zero = {0};
        struct device *dev = &client->dev;
        struct gpio_desc *disable_ports;
        struct pd692x0_msg_ver ver;
        struct pd692x0_priv *priv;
        struct fw_upload *fwl;
        int ret;

        ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulators),
                                             regulators);
        if (ret)
                return dev_err_probe(dev, ret,
                                     "Failed to enable regulators\n");

        if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
                dev_err(dev, "i2c check functionality failed\n");
                return -ENXIO;
        }

        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
        if (!priv)
                return -ENOMEM;

        priv->client = client;
        i2c_set_clientdata(client, priv);

        disable_ports = devm_gpiod_get_optional(dev, "disable-ports", GPIOD_OUT_LOW);
        if (IS_ERR(disable_ports))
                return dev_err_probe(&client->dev, PTR_ERR(disable_ports),
                                     "Failed to get disable ports GPIO\n");

        ret = i2c_master_recv(client, (u8 *)&buf, sizeof(buf));
        if (ret != sizeof(buf)) {
                dev_err(dev, "Failed to get device status\n");
                return -EIO;
        }

        /* Probe has been already run and the status dumped */
        if (!memcmp(&buf, &zero, sizeof(buf))) {
                /* Ask again the controller status */
                msg = pd692x0_msg_template_list[PD692X0_MSG_GET_SYS_STATUS];
                ret = pd692x0_sendrecv_msg(priv, &msg, &buf);
                if (ret < 0) {
                        dev_err(dev, "Failed to get device status\n");
                        return ret;
                }
        }

        if (buf.key != 0x03 || buf.sub[0] & 0x01) {
                dev_err(dev, "PSE controller error\n");
                return -EIO;
        }
        if (buf.sub[0] & 0x02) {
                dev_err(dev, "PSE firmware error. Please update it.\n");
                priv->fw_state = PD692X0_FW_BROKEN;
        } else {
                ver = pd692x0_get_sw_version(priv);
                dev_info(&client->dev, "Software version %d.%02d.%d.%d\n",
                         ver.prod, ver.maj_sw_ver, ver.min_sw_ver,
                         ver.pa_sw_ver);

                if (ver.maj_sw_ver < PD692X0_FW_MAJ_VER) {
                        dev_err(dev, "Too old firmware version. Please update it\n");
                        priv->fw_state = PD692X0_FW_NEED_UPDATE;
                } else {
                        priv->fw_state = PD692X0_FW_OK;
                }
        }

        if (buf.data[2] == PD692X0_USER_BYTE)
                priv->cfg_saved = true;

        priv->np = dev->of_node;
        priv->pcdev.nr_lines = PD692X0_MAX_PIS;
        priv->pcdev.owner = THIS_MODULE;
        priv->pcdev.ops = &pd692x0_ops;
        priv->pcdev.dev = dev;
        priv->pcdev.types = ETHTOOL_PSE_C33;
        priv->pcdev.supp_budget_eval_strategies = PSE_BUDGET_EVAL_STRAT_DYNAMIC;
        priv->pcdev.pis_prio_max = 2;
        ret = devm_pse_controller_register(dev, &priv->pcdev);
        if (ret)
                return dev_err_probe(dev, ret,
                                     "failed to register PSE controller\n");

        fwl = firmware_upload_register(THIS_MODULE, dev, dev_name(dev),
                                       &pd692x0_fw_ops, priv);
        if (IS_ERR(fwl))
                return dev_err_probe(dev, PTR_ERR(fwl),
                                     "failed to register to the Firmware Upload API\n");
        priv->fwl = fwl;

        return 0;
}

static void pd692x0_i2c_remove(struct i2c_client *client)
{
        struct pd692x0_priv *priv = i2c_get_clientdata(client);

        pd692x0_managers_free_pw_budget(priv);
        firmware_upload_unregister(priv->fwl);
}

static const struct i2c_device_id pd692x0_id[] = {
        { .name = PD692X0_PSE_NAME },
        { }
};
MODULE_DEVICE_TABLE(i2c, pd692x0_id);

static const struct of_device_id pd692x0_of_match[] = {
        { .compatible = "microchip,pd69200", },
        { .compatible = "microchip,pd69210", },
        { .compatible = "microchip,pd69220", },
        { },
};
MODULE_DEVICE_TABLE(of, pd692x0_of_match);

static struct i2c_driver pd692x0_driver = {
        .probe          = pd692x0_i2c_probe,
        .remove         = pd692x0_i2c_remove,
        .id_table       = pd692x0_id,
        .driver         = {
                .name           = PD692X0_PSE_NAME,
                .of_match_table = pd692x0_of_match,
        },
};
module_i2c_driver(pd692x0_driver);

MODULE_AUTHOR("Kory Maincent <kory.maincent@bootlin.com>");
MODULE_DESCRIPTION("Microchip PD692x0 PoE PSE Controller driver");
MODULE_LICENSE("GPL");