root/drivers/net/ethernet/mellanox/mlxsw/core_env.c
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/* Copyright (c) 2018 Mellanox Technologies. All rights reserved */

#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/ethtool.h>
#include <linux/sfp.h>
#include <linux/mutex.h>

#include "core.h"
#include "core_env.h"
#include "item.h"
#include "reg.h"

struct mlxsw_env_module_info {
        u64 module_overheat_counter;
        bool is_overheat;
        int num_ports_mapped;
        int num_ports_up;
        enum ethtool_module_power_mode_policy power_mode_policy;
        enum mlxsw_reg_pmtm_module_type type;
};

struct mlxsw_env_line_card {
        u8 module_count;
        bool active;
        struct mlxsw_env_module_info module_info[];
};

struct mlxsw_env {
        struct mlxsw_core *core;
        const struct mlxsw_bus_info *bus_info;
        u8 max_module_count; /* Maximum number of modules per-slot. */
        u8 num_of_slots; /* Including the main board. */
        u8 max_eeprom_len; /* Maximum module EEPROM transaction length. */
        struct mutex line_cards_lock; /* Protects line cards. */
        struct mlxsw_env_line_card *line_cards[] __counted_by(num_of_slots);
};

static bool __mlxsw_env_linecard_is_active(struct mlxsw_env *mlxsw_env,
                                           u8 slot_index)
{
        return mlxsw_env->line_cards[slot_index]->active;
}

static bool mlxsw_env_linecard_is_active(struct mlxsw_env *mlxsw_env,
                                         u8 slot_index)
{
        bool active;

        mutex_lock(&mlxsw_env->line_cards_lock);
        active = __mlxsw_env_linecard_is_active(mlxsw_env, slot_index);
        mutex_unlock(&mlxsw_env->line_cards_lock);

        return active;
}

static struct
mlxsw_env_module_info *mlxsw_env_module_info_get(struct mlxsw_core *mlxsw_core,
                                                 u8 slot_index, u8 module)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);

        return &mlxsw_env->line_cards[slot_index]->module_info[module];
}

static int __mlxsw_env_validate_module_type(struct mlxsw_core *core,
                                            u8 slot_index, u8 module)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(core);
        struct mlxsw_env_module_info *module_info;
        int err;

        if (!__mlxsw_env_linecard_is_active(mlxsw_env, slot_index))
                return 0;

        module_info = mlxsw_env_module_info_get(core, slot_index, module);
        switch (module_info->type) {
        case MLXSW_REG_PMTM_MODULE_TYPE_TWISTED_PAIR:
                err = -EINVAL;
                break;
        default:
                err = 0;
        }

        return err;
}

static int mlxsw_env_validate_module_type(struct mlxsw_core *core,
                                          u8 slot_index, u8 module)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(core);
        int err;

        mutex_lock(&mlxsw_env->line_cards_lock);
        err = __mlxsw_env_validate_module_type(core, slot_index, module);
        mutex_unlock(&mlxsw_env->line_cards_lock);

        return err;
}

static int
mlxsw_env_validate_cable_ident(struct mlxsw_core *core, u8 slot_index, int id,
                               bool *qsfp, bool *cmis)
{
        char mcia_pl[MLXSW_REG_MCIA_LEN];
        char *eeprom_tmp;
        u8 ident;
        int err;

        err = mlxsw_env_validate_module_type(core, slot_index, id);
        if (err)
                return err;

        mlxsw_reg_mcia_pack(mcia_pl, slot_index, id,
                            MLXSW_REG_MCIA_PAGE0_LO_OFF, 0, 1,
                            MLXSW_REG_MCIA_I2C_ADDR_LOW);
        err = mlxsw_reg_query(core, MLXSW_REG(mcia), mcia_pl);
        if (err)
                return err;
        eeprom_tmp = mlxsw_reg_mcia_eeprom_data(mcia_pl);
        ident = eeprom_tmp[0];
        *cmis = false;
        switch (ident) {
        case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP:
                *qsfp = false;
                break;
        case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP:
        case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS:
        case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28:
                *qsfp = true;
                break;
        case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_DD:
        case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_OSFP:
                *qsfp = true;
                *cmis = true;
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

static int
mlxsw_env_query_module_eeprom(struct mlxsw_core *mlxsw_core, u8 slot_index,
                              int module, u16 offset, u16 size, void *data,
                              bool qsfp, unsigned int *p_read_size)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        char mcia_pl[MLXSW_REG_MCIA_LEN];
        char *eeprom_tmp;
        u16 i2c_addr;
        u8 page = 0;
        int status;
        int err;

        size = min_t(u16, size, mlxsw_env->max_eeprom_len);

        if (offset < MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH &&
            offset + size > MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH)
                /* Cross pages read, read until offset 256 in low page */
                size = MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH - offset;

        i2c_addr = MLXSW_REG_MCIA_I2C_ADDR_LOW;
        if (offset >= MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH) {
                if (qsfp) {
                        /* When reading upper pages 1, 2 and 3 the offset
                         * starts at 128. Please refer to "QSFP+ Memory Map"
                         * figure in SFF-8436 specification and to "CMIS Module
                         * Memory Map" figure in CMIS specification for
                         * graphical depiction.
                         */
                        page = MLXSW_REG_MCIA_PAGE_GET(offset);
                        offset -= MLXSW_REG_MCIA_EEPROM_UP_PAGE_LENGTH * page;
                        if (offset + size > MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH)
                                size = MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH - offset;
                } else {
                        /* When reading upper pages 1, 2 and 3 the offset
                         * starts at 0 and I2C high address is used. Please refer
                         * to "Memory Organization" figure in SFF-8472
                         * specification for graphical depiction.
                         */
                        i2c_addr = MLXSW_REG_MCIA_I2C_ADDR_HIGH;
                        offset -= MLXSW_REG_MCIA_EEPROM_PAGE_LENGTH;
                }
        }

        mlxsw_reg_mcia_pack(mcia_pl, slot_index, module, page, offset, size,
                            i2c_addr);

        err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mcia), mcia_pl);
        if (err)
                return err;

        status = mlxsw_reg_mcia_status_get(mcia_pl);
        if (status)
                return -EIO;

        eeprom_tmp = mlxsw_reg_mcia_eeprom_data(mcia_pl);
        memcpy(data, eeprom_tmp, size);
        *p_read_size = size;

        return 0;
}

int
mlxsw_env_module_temp_thresholds_get(struct mlxsw_core *core, u8 slot_index,
                                     int module, int off, int *temp)
{
        unsigned int module_temp, module_crit, module_emerg;
        union {
                u8 buf[MLXSW_REG_MCIA_TH_ITEM_SIZE];
                u16 temp;
        } temp_thresh;
        char mcia_pl[MLXSW_REG_MCIA_LEN] = {0};
        char mtmp_pl[MLXSW_REG_MTMP_LEN];
        char *eeprom_tmp;
        bool qsfp, cmis;
        int page;
        int err;

        mlxsw_reg_mtmp_pack(mtmp_pl, slot_index,
                            MLXSW_REG_MTMP_MODULE_INDEX_MIN + module, false,
                            false);
        err = mlxsw_reg_query(core, MLXSW_REG(mtmp), mtmp_pl);
        if (err)
                return err;
        mlxsw_reg_mtmp_unpack(mtmp_pl, &module_temp, NULL, &module_crit,
                              &module_emerg, NULL);
        if (!module_temp) {
                *temp = 0;
                return 0;
        }

        /* Validate if threshold reading is available through MTMP register,
         * otherwise fallback to read through MCIA.
         */
        if (module_emerg) {
                *temp = off == SFP_TEMP_HIGH_WARN ? module_crit : module_emerg;
                return 0;
        }

        /* Read Free Side Device Temperature Thresholds from page 03h
         * (MSB at lower byte address).
         * Bytes:
         * 128-129 - Temp High Alarm (SFP_TEMP_HIGH_ALARM);
         * 130-131 - Temp Low Alarm (SFP_TEMP_LOW_ALARM);
         * 132-133 - Temp High Warning (SFP_TEMP_HIGH_WARN);
         * 134-135 - Temp Low Warning (SFP_TEMP_LOW_WARN);
         */

        /* Validate module identifier value. */
        err = mlxsw_env_validate_cable_ident(core, slot_index, module, &qsfp,
                                             &cmis);
        if (err)
                return err;

        if (qsfp) {
                /* For QSFP/CMIS module-defined thresholds are located in page
                 * 02h, otherwise in page 03h.
                 */
                if (cmis)
                        page = MLXSW_REG_MCIA_TH_PAGE_CMIS_NUM;
                else
                        page = MLXSW_REG_MCIA_TH_PAGE_NUM;
                mlxsw_reg_mcia_pack(mcia_pl, slot_index, module, page,
                                    MLXSW_REG_MCIA_TH_PAGE_OFF + off,
                                    MLXSW_REG_MCIA_TH_ITEM_SIZE,
                                    MLXSW_REG_MCIA_I2C_ADDR_LOW);
        } else {
                mlxsw_reg_mcia_pack(mcia_pl, slot_index, module,
                                    MLXSW_REG_MCIA_PAGE0_LO,
                                    off, MLXSW_REG_MCIA_TH_ITEM_SIZE,
                                    MLXSW_REG_MCIA_I2C_ADDR_HIGH);
        }

        err = mlxsw_reg_query(core, MLXSW_REG(mcia), mcia_pl);
        if (err)
                return err;

        eeprom_tmp = mlxsw_reg_mcia_eeprom_data(mcia_pl);
        memcpy(temp_thresh.buf, eeprom_tmp, MLXSW_REG_MCIA_TH_ITEM_SIZE);
        *temp = temp_thresh.temp * 1000;

        return 0;
}

int mlxsw_env_get_module_info(struct net_device *netdev,
                              struct mlxsw_core *mlxsw_core, u8 slot_index,
                              int module, struct ethtool_modinfo *modinfo)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        u8 module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_SIZE];
        u16 offset = MLXSW_REG_MCIA_EEPROM_MODULE_INFO_SIZE;
        u8 module_rev_id, module_id, diag_mon;
        unsigned int read_size;
        int err;

        if (!mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) {
                netdev_err(netdev, "Cannot read EEPROM of module on an inactive line card\n");
                return -EIO;
        }

        err = mlxsw_env_validate_module_type(mlxsw_core, slot_index, module);
        if (err) {
                netdev_err(netdev,
                           "EEPROM is not equipped on port module type");
                return err;
        }

        err = mlxsw_env_query_module_eeprom(mlxsw_core, slot_index, module, 0,
                                            offset, module_info, false,
                                            &read_size);
        if (err)
                return err;

        if (read_size < offset)
                return -EIO;

        module_rev_id = module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_REV_ID];
        module_id = module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID];

        switch (module_id) {
        case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP:
                modinfo->type       = ETH_MODULE_SFF_8436;
                modinfo->eeprom_len = ETH_MODULE_SFF_8436_MAX_LEN;
                break;
        case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_PLUS:
        case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28:
                if (module_id == MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP28 ||
                    module_rev_id >=
                    MLXSW_REG_MCIA_EEPROM_MODULE_INFO_REV_ID_8636) {
                        modinfo->type       = ETH_MODULE_SFF_8636;
                        modinfo->eeprom_len = ETH_MODULE_SFF_8636_MAX_LEN;
                } else {
                        modinfo->type       = ETH_MODULE_SFF_8436;
                        modinfo->eeprom_len = ETH_MODULE_SFF_8436_MAX_LEN;
                }
                break;
        case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_SFP:
                /* Verify if transceiver provides diagnostic monitoring page */
                err = mlxsw_env_query_module_eeprom(mlxsw_core, slot_index,
                                                    module, SFP_DIAGMON, 1,
                                                    &diag_mon, false,
                                                    &read_size);
                if (err)
                        return err;

                if (read_size < 1)
                        return -EIO;

                modinfo->type       = ETH_MODULE_SFF_8472;
                if (diag_mon)
                        modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
                else
                        modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN / 2;
                break;
        case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_QSFP_DD:
        case MLXSW_REG_MCIA_EEPROM_MODULE_INFO_ID_OSFP:
                /* Use SFF_8636 as base type. ethtool should recognize specific
                 * type through the identifier value.
                 */
                modinfo->type       = ETH_MODULE_SFF_8636;
                /* Verify if module EEPROM is a flat memory. In case of flat
                 * memory only page 00h (0-255 bytes) can be read. Otherwise
                 * upper pages 01h and 02h can also be read. Upper pages 10h
                 * and 11h are currently not supported by the driver.
                 */
                if (module_info[MLXSW_REG_MCIA_EEPROM_MODULE_INFO_TYPE_ID] &
                    MLXSW_REG_MCIA_EEPROM_CMIS_FLAT_MEMORY)
                        modinfo->eeprom_len = ETH_MODULE_SFF_8636_LEN;
                else
                        modinfo->eeprom_len = ETH_MODULE_SFF_8472_LEN;
                break;
        default:
                return -EINVAL;
        }

        return 0;
}
EXPORT_SYMBOL(mlxsw_env_get_module_info);

int mlxsw_env_get_module_eeprom(struct net_device *netdev,
                                struct mlxsw_core *mlxsw_core, u8 slot_index,
                                int module, struct ethtool_eeprom *ee,
                                u8 *data)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        int offset = ee->offset;
        unsigned int read_size;
        bool qsfp, cmis;
        int i = 0;
        int err;

        if (!ee->len)
                return -EINVAL;

        if (!mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) {
                netdev_err(netdev, "Cannot read EEPROM of module on an inactive line card\n");
                return -EIO;
        }

        memset(data, 0, ee->len);
        /* Validate module identifier value. */
        err = mlxsw_env_validate_cable_ident(mlxsw_core, slot_index, module,
                                             &qsfp, &cmis);
        if (err)
                return err;

        while (i < ee->len) {
                err = mlxsw_env_query_module_eeprom(mlxsw_core, slot_index,
                                                    module, offset,
                                                    ee->len - i, data + i,
                                                    qsfp, &read_size);
                if (err) {
                        netdev_err(netdev, "Eeprom query failed\n");
                        return err;
                }

                i += read_size;
                offset += read_size;
        }

        return 0;
}
EXPORT_SYMBOL(mlxsw_env_get_module_eeprom);

static int mlxsw_env_mcia_status_process(const char *mcia_pl,
                                         struct netlink_ext_ack *extack)
{
        u8 status = mlxsw_reg_mcia_status_get(mcia_pl);

        switch (status) {
        case MLXSW_REG_MCIA_STATUS_GOOD:
                return 0;
        case MLXSW_REG_MCIA_STATUS_NO_EEPROM_MODULE:
                NL_SET_ERR_MSG_MOD(extack, "No response from module's EEPROM");
                return -EIO;
        case MLXSW_REG_MCIA_STATUS_MODULE_NOT_SUPPORTED:
                NL_SET_ERR_MSG_MOD(extack, "Module type not supported by the device");
                return -EOPNOTSUPP;
        case MLXSW_REG_MCIA_STATUS_MODULE_NOT_CONNECTED:
                NL_SET_ERR_MSG_MOD(extack, "No module present indication");
                return -EIO;
        case MLXSW_REG_MCIA_STATUS_I2C_ERROR:
                NL_SET_ERR_MSG_MOD(extack, "Error occurred while trying to access module's EEPROM using I2C");
                return -EIO;
        case MLXSW_REG_MCIA_STATUS_MODULE_DISABLED:
                NL_SET_ERR_MSG_MOD(extack, "Module is disabled");
                return -EIO;
        default:
                NL_SET_ERR_MSG_MOD(extack, "Unknown error");
                return -EIO;
        }
}

int
mlxsw_env_get_module_eeprom_by_page(struct mlxsw_core *mlxsw_core,
                                    u8 slot_index, u8 module,
                                    const struct ethtool_module_eeprom *page,
                                    struct netlink_ext_ack *extack)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        u32 bytes_read = 0;
        u16 device_addr;
        int err;

        if (!mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Cannot read EEPROM of module on an inactive line card");
                return -EIO;
        }

        err = mlxsw_env_validate_module_type(mlxsw_core, slot_index, module);
        if (err) {
                NL_SET_ERR_MSG_MOD(extack, "EEPROM is not equipped on port module type");
                return err;
        }

        /* Offset cannot be larger than 2 * ETH_MODULE_EEPROM_PAGE_LEN */
        device_addr = page->offset;

        while (bytes_read < page->length) {
                char mcia_pl[MLXSW_REG_MCIA_LEN];
                char *eeprom_tmp;
                u8 size;

                size = min_t(u8, page->length - bytes_read,
                             mlxsw_env->max_eeprom_len);

                mlxsw_reg_mcia_pack(mcia_pl, slot_index, module, page->page,
                                    device_addr + bytes_read, size,
                                    page->i2c_address);
                mlxsw_reg_mcia_bank_number_set(mcia_pl, page->bank);

                err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mcia), mcia_pl);
                if (err) {
                        NL_SET_ERR_MSG_MOD(extack, "Failed to access module's EEPROM");
                        return err;
                }

                err = mlxsw_env_mcia_status_process(mcia_pl, extack);
                if (err)
                        return err;

                eeprom_tmp = mlxsw_reg_mcia_eeprom_data(mcia_pl);
                memcpy(page->data + bytes_read, eeprom_tmp, size);
                bytes_read += size;
        }

        return bytes_read;
}
EXPORT_SYMBOL(mlxsw_env_get_module_eeprom_by_page);

int
mlxsw_env_set_module_eeprom_by_page(struct mlxsw_core *mlxsw_core,
                                    u8 slot_index, u8 module,
                                    const struct ethtool_module_eeprom *page,
                                    struct netlink_ext_ack *extack)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        u32 bytes_written = 0;
        u16 device_addr;
        int err;

        if (!mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Cannot write to EEPROM of a module on an inactive line card");
                return -EIO;
        }

        err = mlxsw_env_validate_module_type(mlxsw_core, slot_index, module);
        if (err) {
                NL_SET_ERR_MSG_MOD(extack, "EEPROM is not equipped on port module type");
                return err;
        }

        device_addr = page->offset;

        while (bytes_written < page->length) {
                char mcia_pl[MLXSW_REG_MCIA_LEN];
                char eeprom_tmp[128] = {};
                u8 size;

                size = min_t(u8, page->length - bytes_written,
                             mlxsw_env->max_eeprom_len);

                mlxsw_reg_mcia_pack(mcia_pl, slot_index, module, page->page,
                                    device_addr + bytes_written, size,
                                    page->i2c_address);
                mlxsw_reg_mcia_bank_number_set(mcia_pl, page->bank);
                memcpy(eeprom_tmp, page->data + bytes_written, size);
                mlxsw_reg_mcia_eeprom_memcpy_to(mcia_pl, eeprom_tmp);

                err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(mcia), mcia_pl);
                if (err) {
                        NL_SET_ERR_MSG_MOD(extack, "Failed to access module's EEPROM");
                        return err;
                }

                err = mlxsw_env_mcia_status_process(mcia_pl, extack);
                if (err)
                        return err;

                bytes_written += size;
        }

        return 0;
}
EXPORT_SYMBOL(mlxsw_env_set_module_eeprom_by_page);

static int mlxsw_env_module_reset(struct mlxsw_core *mlxsw_core, u8 slot_index,
                                  u8 module)
{
        char pmaos_pl[MLXSW_REG_PMAOS_LEN];

        mlxsw_reg_pmaos_pack(pmaos_pl, slot_index, module);
        mlxsw_reg_pmaos_rst_set(pmaos_pl, true);

        return mlxsw_reg_write(mlxsw_core, MLXSW_REG(pmaos), pmaos_pl);
}

int mlxsw_env_reset_module(struct net_device *netdev,
                           struct mlxsw_core *mlxsw_core, u8 slot_index,
                           u8 module, u32 *flags)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        struct mlxsw_env_module_info *module_info;
        u32 req = *flags;
        int err;

        if (!(req & ETH_RESET_PHY) &&
            !(req & (ETH_RESET_PHY << ETH_RESET_SHARED_SHIFT)))
                return 0;

        if (!mlxsw_env_linecard_is_active(mlxsw_env, slot_index)) {
                netdev_err(netdev, "Cannot reset module on an inactive line card\n");
                return -EIO;
        }

        mutex_lock(&mlxsw_env->line_cards_lock);

        err = __mlxsw_env_validate_module_type(mlxsw_core, slot_index, module);
        if (err) {
                netdev_err(netdev, "Reset module is not supported on port module type\n");
                goto out;
        }

        module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module);
        if (module_info->num_ports_up) {
                netdev_err(netdev, "Cannot reset module when ports using it are administratively up\n");
                err = -EINVAL;
                goto out;
        }

        if (module_info->num_ports_mapped > 1 &&
            !(req & (ETH_RESET_PHY << ETH_RESET_SHARED_SHIFT))) {
                netdev_err(netdev, "Cannot reset module without \"phy-shared\" flag when shared by multiple ports\n");
                err = -EINVAL;
                goto out;
        }

        err = mlxsw_env_module_reset(mlxsw_core, slot_index, module);
        if (err) {
                netdev_err(netdev, "Failed to reset module\n");
                goto out;
        }

        *flags &= ~(ETH_RESET_PHY | (ETH_RESET_PHY << ETH_RESET_SHARED_SHIFT));

out:
        mutex_unlock(&mlxsw_env->line_cards_lock);
        return err;
}
EXPORT_SYMBOL(mlxsw_env_reset_module);

int
mlxsw_env_get_module_power_mode(struct mlxsw_core *mlxsw_core, u8 slot_index,
                                u8 module,
                                struct ethtool_module_power_mode_params *params,
                                struct netlink_ext_ack *extack)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        struct mlxsw_env_module_info *module_info;
        char mcion_pl[MLXSW_REG_MCION_LEN];
        u32 status_bits;
        int err = 0;

        mutex_lock(&mlxsw_env->line_cards_lock);

        err = __mlxsw_env_validate_module_type(mlxsw_core, slot_index, module);
        if (err) {
                NL_SET_ERR_MSG_MOD(extack, "Power mode is not supported on port module type");
                goto out;
        }

        module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module);
        params->policy = module_info->power_mode_policy;

        /* Avoid accessing an inactive line card, as it will result in an error. */
        if (!__mlxsw_env_linecard_is_active(mlxsw_env, slot_index))
                goto out;

        mlxsw_reg_mcion_pack(mcion_pl, slot_index, module);
        err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mcion), mcion_pl);
        if (err) {
                NL_SET_ERR_MSG_MOD(extack, "Failed to retrieve module's power mode");
                goto out;
        }

        status_bits = mlxsw_reg_mcion_module_status_bits_get(mcion_pl);
        if (!(status_bits & MLXSW_REG_MCION_MODULE_STATUS_BITS_PRESENT_MASK))
                goto out;

        if (status_bits & MLXSW_REG_MCION_MODULE_STATUS_BITS_LOW_POWER_MASK)
                params->mode = ETHTOOL_MODULE_POWER_MODE_LOW;
        else
                params->mode = ETHTOOL_MODULE_POWER_MODE_HIGH;

out:
        mutex_unlock(&mlxsw_env->line_cards_lock);
        return err;
}
EXPORT_SYMBOL(mlxsw_env_get_module_power_mode);

static int mlxsw_env_module_enable_set(struct mlxsw_core *mlxsw_core,
                                       u8 slot_index, u8 module, bool enable)
{
        enum mlxsw_reg_pmaos_admin_status admin_status;
        char pmaos_pl[MLXSW_REG_PMAOS_LEN];

        mlxsw_reg_pmaos_pack(pmaos_pl, slot_index, module);
        admin_status = enable ? MLXSW_REG_PMAOS_ADMIN_STATUS_ENABLED :
                                MLXSW_REG_PMAOS_ADMIN_STATUS_DISABLED;
        mlxsw_reg_pmaos_admin_status_set(pmaos_pl, admin_status);
        mlxsw_reg_pmaos_ase_set(pmaos_pl, true);

        return mlxsw_reg_write(mlxsw_core, MLXSW_REG(pmaos), pmaos_pl);
}

static int mlxsw_env_module_low_power_set(struct mlxsw_core *mlxsw_core,
                                          u8 slot_index, u8 module,
                                          bool low_power)
{
        u16 eeprom_override_mask, eeprom_override;
        char pmmp_pl[MLXSW_REG_PMMP_LEN];

        mlxsw_reg_pmmp_pack(pmmp_pl, slot_index, module);
        mlxsw_reg_pmmp_sticky_set(pmmp_pl, true);
        /* Mask all the bits except low power mode. */
        eeprom_override_mask = ~MLXSW_REG_PMMP_EEPROM_OVERRIDE_LOW_POWER_MASK;
        mlxsw_reg_pmmp_eeprom_override_mask_set(pmmp_pl, eeprom_override_mask);
        eeprom_override = low_power ? MLXSW_REG_PMMP_EEPROM_OVERRIDE_LOW_POWER_MASK :
                                      0;
        mlxsw_reg_pmmp_eeprom_override_set(pmmp_pl, eeprom_override);

        return mlxsw_reg_write(mlxsw_core, MLXSW_REG(pmmp), pmmp_pl);
}

static int __mlxsw_env_set_module_power_mode(struct mlxsw_core *mlxsw_core,
                                             u8 slot_index, u8 module,
                                             bool low_power,
                                             struct netlink_ext_ack *extack)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        int err;

        /* Avoid accessing an inactive line card, as it will result in an error.
         * Cached configuration will be applied by mlxsw_env_got_active() when
         * line card becomes active.
         */
        if (!__mlxsw_env_linecard_is_active(mlxsw_env, slot_index))
                return 0;

        err = mlxsw_env_module_enable_set(mlxsw_core, slot_index, module, false);
        if (err) {
                NL_SET_ERR_MSG_MOD(extack, "Failed to disable module");
                return err;
        }

        err = mlxsw_env_module_low_power_set(mlxsw_core, slot_index, module,
                                             low_power);
        if (err) {
                NL_SET_ERR_MSG_MOD(extack, "Failed to set module's power mode");
                goto err_module_low_power_set;
        }

        err = mlxsw_env_module_enable_set(mlxsw_core, slot_index, module, true);
        if (err) {
                NL_SET_ERR_MSG_MOD(extack, "Failed to enable module");
                goto err_module_enable_set;
        }

        return 0;

err_module_enable_set:
        mlxsw_env_module_low_power_set(mlxsw_core, slot_index, module,
                                       !low_power);
err_module_low_power_set:
        mlxsw_env_module_enable_set(mlxsw_core, slot_index, module, true);
        return err;
}

static int
mlxsw_env_set_module_power_mode_apply(struct mlxsw_core *mlxsw_core,
                                      u8 slot_index, u8 module,
                                      enum ethtool_module_power_mode_policy policy,
                                      struct netlink_ext_ack *extack)
{
        struct mlxsw_env_module_info *module_info;
        bool low_power;
        int err = 0;

        err = __mlxsw_env_validate_module_type(mlxsw_core, slot_index, module);
        if (err) {
                NL_SET_ERR_MSG_MOD(extack,
                                   "Power mode set is not supported on port module type");
                goto out;
        }

        module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module);
        if (module_info->power_mode_policy == policy)
                goto out;

        /* If any ports are up, we are already in high power mode. */
        if (module_info->num_ports_up)
                goto out_set_policy;

        low_power = policy == ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO;
        err = __mlxsw_env_set_module_power_mode(mlxsw_core, slot_index, module,
                                                low_power, extack);
        if (err)
                goto out;

out_set_policy:
        module_info->power_mode_policy = policy;
out:
        return err;
}

int
mlxsw_env_set_module_power_mode(struct mlxsw_core *mlxsw_core, u8 slot_index,
                                u8 module,
                                enum ethtool_module_power_mode_policy policy,
                                struct netlink_ext_ack *extack)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        int err;

        if (policy != ETHTOOL_MODULE_POWER_MODE_POLICY_HIGH &&
            policy != ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO) {
                NL_SET_ERR_MSG_MOD(extack, "Unsupported power mode policy");
                return -EOPNOTSUPP;
        }

        mutex_lock(&mlxsw_env->line_cards_lock);
        err = mlxsw_env_set_module_power_mode_apply(mlxsw_core, slot_index,
                                                    module, policy, extack);
        mutex_unlock(&mlxsw_env->line_cards_lock);

        return err;
}
EXPORT_SYMBOL(mlxsw_env_set_module_power_mode);

static int mlxsw_env_module_has_temp_sensor(struct mlxsw_core *mlxsw_core,
                                            u8 slot_index, u8 module,
                                            bool *p_has_temp_sensor)
{
        char mtbr_pl[MLXSW_REG_MTBR_LEN];
        u16 temp;
        int err;

        mlxsw_reg_mtbr_pack(mtbr_pl, slot_index,
                            MLXSW_REG_MTBR_BASE_MODULE_INDEX + module);
        err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mtbr), mtbr_pl);
        if (err)
                return err;

        mlxsw_reg_mtbr_temp_unpack(mtbr_pl, 0, &temp, NULL);

        switch (temp) {
        case MLXSW_REG_MTBR_BAD_SENS_INFO:
        case MLXSW_REG_MTBR_NO_CONN:
        case MLXSW_REG_MTBR_NO_TEMP_SENS:
        case MLXSW_REG_MTBR_INDEX_NA:
                *p_has_temp_sensor = false;
                break;
        default:
                *p_has_temp_sensor = temp ? true : false;
        }
        return 0;
}

static int
mlxsw_env_temp_event_set(struct mlxsw_core *mlxsw_core, u8 slot_index,
                         u16 sensor_index, bool enable)
{
        char mtmp_pl[MLXSW_REG_MTMP_LEN] = {0};
        enum mlxsw_reg_mtmp_tee tee;
        int err, threshold_hi;

        mlxsw_reg_mtmp_slot_index_set(mtmp_pl, slot_index);
        mlxsw_reg_mtmp_sensor_index_set(mtmp_pl, sensor_index);
        err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mtmp), mtmp_pl);
        if (err)
                return err;

        if (enable) {
                err = mlxsw_env_module_temp_thresholds_get(mlxsw_core,
                                                           slot_index,
                                                           sensor_index -
                                                           MLXSW_REG_MTMP_MODULE_INDEX_MIN,
                                                           SFP_TEMP_HIGH_WARN,
                                                           &threshold_hi);
                /* In case it is not possible to query the module's threshold,
                 * use the default value.
                 */
                if (err)
                        threshold_hi = MLXSW_REG_MTMP_THRESH_HI;
                else
                        /* mlxsw_env_module_temp_thresholds_get() multiplies
                         * Celsius degrees by 1000 whereas MTMP expects
                         * temperature in 0.125 Celsius degrees units.
                         * Convert threshold_hi to correct units.
                         */
                        threshold_hi = threshold_hi / 1000 * 8;

                mlxsw_reg_mtmp_temperature_threshold_hi_set(mtmp_pl, threshold_hi);
                mlxsw_reg_mtmp_temperature_threshold_lo_set(mtmp_pl, threshold_hi -
                                                            MLXSW_REG_MTMP_HYSTERESIS_TEMP);
        }
        tee = enable ? MLXSW_REG_MTMP_TEE_GENERATE_EVENT : MLXSW_REG_MTMP_TEE_NO_EVENT;
        mlxsw_reg_mtmp_tee_set(mtmp_pl, tee);
        return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mtmp), mtmp_pl);
}

static int mlxsw_env_module_temp_event_enable(struct mlxsw_core *mlxsw_core,
                                              u8 slot_index)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        int i, err, sensor_index;
        bool has_temp_sensor;

        for (i = 0; i < mlxsw_env->line_cards[slot_index]->module_count; i++) {
                err = mlxsw_env_module_has_temp_sensor(mlxsw_core, slot_index,
                                                       i, &has_temp_sensor);
                if (err)
                        return err;

                if (!has_temp_sensor)
                        continue;

                sensor_index = i + MLXSW_REG_MTMP_MODULE_INDEX_MIN;
                err = mlxsw_env_temp_event_set(mlxsw_core, slot_index,
                                               sensor_index, true);
                if (err)
                        return err;
        }

        return 0;
}

struct mlxsw_env_module_temp_warn_event {
        struct mlxsw_env *mlxsw_env;
        char mtwe_pl[MLXSW_REG_MTWE_LEN];
        struct work_struct work;
};

static void mlxsw_env_mtwe_event_work(struct work_struct *work)
{
        struct mlxsw_env_module_temp_warn_event *event;
        struct mlxsw_env_module_info *module_info;
        struct mlxsw_env *mlxsw_env;
        int i, sensor_warning;
        bool is_overheat;

        event = container_of(work, struct mlxsw_env_module_temp_warn_event,
                             work);
        mlxsw_env = event->mlxsw_env;

        for (i = 0; i < mlxsw_env->max_module_count; i++) {
                /* 64-127 of sensor_index are mapped to the port modules
                 * sequentially (module 0 is mapped to sensor_index 64,
                 * module 1 to sensor_index 65 and so on)
                 */
                sensor_warning =
                        mlxsw_reg_mtwe_sensor_warning_get(event->mtwe_pl,
                                                          i + MLXSW_REG_MTMP_MODULE_INDEX_MIN);
                mutex_lock(&mlxsw_env->line_cards_lock);
                /* MTWE only supports main board. */
                module_info = mlxsw_env_module_info_get(mlxsw_env->core, 0, i);
                is_overheat = module_info->is_overheat;

                if ((is_overheat && sensor_warning) ||
                    (!is_overheat && !sensor_warning)) {
                        /* Current state is "warning" and MTWE still reports
                         * warning OR current state in "no warning" and MTWE
                         * does not report warning.
                         */
                        mutex_unlock(&mlxsw_env->line_cards_lock);
                        continue;
                } else if (is_overheat && !sensor_warning) {
                        /* MTWE reports "no warning", turn is_overheat off.
                         */
                        module_info->is_overheat = false;
                        mutex_unlock(&mlxsw_env->line_cards_lock);
                } else {
                        /* Current state is "no warning" and MTWE reports
                         * "warning", increase the counter and turn is_overheat
                         * on.
                         */
                        module_info->is_overheat = true;
                        module_info->module_overheat_counter++;
                        mutex_unlock(&mlxsw_env->line_cards_lock);
                }
        }

        kfree(event);
}

static void
mlxsw_env_mtwe_listener_func(const struct mlxsw_reg_info *reg, char *mtwe_pl,
                             void *priv)
{
        struct mlxsw_env_module_temp_warn_event *event;
        struct mlxsw_env *mlxsw_env = priv;

        event = kmalloc_obj(*event, GFP_ATOMIC);
        if (!event)
                return;

        event->mlxsw_env = mlxsw_env;
        memcpy(event->mtwe_pl, mtwe_pl, MLXSW_REG_MTWE_LEN);
        INIT_WORK(&event->work, mlxsw_env_mtwe_event_work);
        mlxsw_core_schedule_work(&event->work);
}

static const struct mlxsw_listener mlxsw_env_temp_warn_listener =
        MLXSW_CORE_EVENTL(mlxsw_env_mtwe_listener_func, MTWE);

static int mlxsw_env_temp_warn_event_register(struct mlxsw_core *mlxsw_core)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);

        return mlxsw_core_trap_register(mlxsw_core,
                                        &mlxsw_env_temp_warn_listener,
                                        mlxsw_env);
}

static void mlxsw_env_temp_warn_event_unregister(struct mlxsw_env *mlxsw_env)
{
        mlxsw_core_trap_unregister(mlxsw_env->core,
                                   &mlxsw_env_temp_warn_listener, mlxsw_env);
}

struct mlxsw_env_module_plug_unplug_event {
        struct mlxsw_env *mlxsw_env;
        u8 slot_index;
        u8 module;
        struct work_struct work;
};

static void mlxsw_env_pmpe_event_work(struct work_struct *work)
{
        struct mlxsw_env_module_plug_unplug_event *event;
        struct mlxsw_env_module_info *module_info;
        struct mlxsw_env *mlxsw_env;
        bool has_temp_sensor;
        u16 sensor_index;
        int err;

        event = container_of(work, struct mlxsw_env_module_plug_unplug_event,
                             work);
        mlxsw_env = event->mlxsw_env;

        mutex_lock(&mlxsw_env->line_cards_lock);
        module_info = mlxsw_env_module_info_get(mlxsw_env->core,
                                                event->slot_index,
                                                event->module);
        module_info->is_overheat = false;
        mutex_unlock(&mlxsw_env->line_cards_lock);

        err = mlxsw_env_module_has_temp_sensor(mlxsw_env->core,
                                               event->slot_index,
                                               event->module,
                                               &has_temp_sensor);
        /* Do not disable events on modules without sensors or faulty sensors
         * because FW returns errors.
         */
        if (err)
                goto out;

        if (!has_temp_sensor)
                goto out;

        sensor_index = event->module + MLXSW_REG_MTMP_MODULE_INDEX_MIN;
        mlxsw_env_temp_event_set(mlxsw_env->core, event->slot_index,
                                 sensor_index, true);

out:
        kfree(event);
}

static void
mlxsw_env_pmpe_listener_func(const struct mlxsw_reg_info *reg, char *pmpe_pl,
                             void *priv)
{
        u8 slot_index = mlxsw_reg_pmpe_slot_index_get(pmpe_pl);
        struct mlxsw_env_module_plug_unplug_event *event;
        enum mlxsw_reg_pmpe_module_status module_status;
        u8 module = mlxsw_reg_pmpe_module_get(pmpe_pl);
        struct mlxsw_env *mlxsw_env = priv;

        if (WARN_ON_ONCE(module >= mlxsw_env->max_module_count ||
                         slot_index >= mlxsw_env->num_of_slots))
                return;

        module_status = mlxsw_reg_pmpe_module_status_get(pmpe_pl);
        if (module_status != MLXSW_REG_PMPE_MODULE_STATUS_PLUGGED_ENABLED)
                return;

        event = kmalloc_obj(*event, GFP_ATOMIC);
        if (!event)
                return;

        event->mlxsw_env = mlxsw_env;
        event->slot_index = slot_index;
        event->module = module;
        INIT_WORK(&event->work, mlxsw_env_pmpe_event_work);
        mlxsw_core_schedule_work(&event->work);
}

static const struct mlxsw_listener mlxsw_env_module_plug_listener =
        MLXSW_CORE_EVENTL(mlxsw_env_pmpe_listener_func, PMPE);

static int
mlxsw_env_module_plug_event_register(struct mlxsw_core *mlxsw_core)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);

        return mlxsw_core_trap_register(mlxsw_core,
                                        &mlxsw_env_module_plug_listener,
                                        mlxsw_env);
}

static void
mlxsw_env_module_plug_event_unregister(struct mlxsw_env *mlxsw_env)
{
        mlxsw_core_trap_unregister(mlxsw_env->core,
                                   &mlxsw_env_module_plug_listener,
                                   mlxsw_env);
}

static int
mlxsw_env_module_oper_state_event_enable(struct mlxsw_core *mlxsw_core,
                                         u8 slot_index)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        int i, err;

        for (i = 0; i < mlxsw_env->line_cards[slot_index]->module_count; i++) {
                char pmaos_pl[MLXSW_REG_PMAOS_LEN];

                mlxsw_reg_pmaos_pack(pmaos_pl, slot_index, i);
                mlxsw_reg_pmaos_e_set(pmaos_pl,
                                      MLXSW_REG_PMAOS_E_GENERATE_EVENT);
                mlxsw_reg_pmaos_ee_set(pmaos_pl, true);
                err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(pmaos), pmaos_pl);
                if (err)
                        return err;
        }
        return 0;
}

int
mlxsw_env_module_overheat_counter_get(struct mlxsw_core *mlxsw_core, u8 slot_index,
                                      u8 module, u64 *p_counter)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        struct mlxsw_env_module_info *module_info;

        mutex_lock(&mlxsw_env->line_cards_lock);
        module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module);
        *p_counter = module_info->module_overheat_counter;
        mutex_unlock(&mlxsw_env->line_cards_lock);

        return 0;
}
EXPORT_SYMBOL(mlxsw_env_module_overheat_counter_get);

void mlxsw_env_module_port_map(struct mlxsw_core *mlxsw_core, u8 slot_index,
                               u8 module)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        struct mlxsw_env_module_info *module_info;

        mutex_lock(&mlxsw_env->line_cards_lock);
        module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module);
        module_info->num_ports_mapped++;
        mutex_unlock(&mlxsw_env->line_cards_lock);
}
EXPORT_SYMBOL(mlxsw_env_module_port_map);

void mlxsw_env_module_port_unmap(struct mlxsw_core *mlxsw_core, u8 slot_index,
                                 u8 module)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        struct mlxsw_env_module_info *module_info;

        mutex_lock(&mlxsw_env->line_cards_lock);
        module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module);
        module_info->num_ports_mapped--;
        mutex_unlock(&mlxsw_env->line_cards_lock);
}
EXPORT_SYMBOL(mlxsw_env_module_port_unmap);

int mlxsw_env_module_port_up(struct mlxsw_core *mlxsw_core, u8 slot_index,
                             u8 module)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        struct mlxsw_env_module_info *module_info;
        int err = 0;

        mutex_lock(&mlxsw_env->line_cards_lock);

        module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module);
        if (module_info->power_mode_policy !=
            ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO)
                goto out_inc;

        if (module_info->num_ports_up != 0)
                goto out_inc;

        /* Transition to high power mode following first port using the module
         * being put administratively up.
         */
        err = __mlxsw_env_set_module_power_mode(mlxsw_core, slot_index, module,
                                                false, NULL);
        if (err)
                goto out_unlock;

out_inc:
        module_info->num_ports_up++;
out_unlock:
        mutex_unlock(&mlxsw_env->line_cards_lock);
        return err;
}
EXPORT_SYMBOL(mlxsw_env_module_port_up);

void mlxsw_env_module_port_down(struct mlxsw_core *mlxsw_core, u8 slot_index,
                                u8 module)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        struct mlxsw_env_module_info *module_info;

        mutex_lock(&mlxsw_env->line_cards_lock);

        module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index, module);
        module_info->num_ports_up--;

        if (module_info->power_mode_policy !=
            ETHTOOL_MODULE_POWER_MODE_POLICY_AUTO)
                goto out_unlock;

        if (module_info->num_ports_up != 0)
                goto out_unlock;

        /* Transition to low power mode following last port using the module
         * being put administratively down.
         */
        __mlxsw_env_set_module_power_mode(mlxsw_core, slot_index, module, true,
                                          NULL);

out_unlock:
        mutex_unlock(&mlxsw_env->line_cards_lock);
}
EXPORT_SYMBOL(mlxsw_env_module_port_down);

static int mlxsw_env_line_cards_alloc(struct mlxsw_env *env)
{
        struct mlxsw_env_module_info *module_info;
        int i, j;

        for (i = 0; i < env->num_of_slots; i++) {
                env->line_cards[i] = kzalloc_flex(*env->line_cards[i],
                                                  module_info,
                                                  env->max_module_count);
                if (!env->line_cards[i])
                        goto kzalloc_err;

                /* Firmware defaults to high power mode policy where modules
                 * are transitioned to high power mode following plug-in.
                 */
                for (j = 0; j < env->max_module_count; j++) {
                        module_info = &env->line_cards[i]->module_info[j];
                        module_info->power_mode_policy =
                                        ETHTOOL_MODULE_POWER_MODE_POLICY_HIGH;
                }
        }

        return 0;

kzalloc_err:
        for (i--; i >= 0; i--)
                kfree(env->line_cards[i]);
        return -ENOMEM;
}

static void mlxsw_env_line_cards_free(struct mlxsw_env *env)
{
        int i = env->num_of_slots;

        for (i--; i >= 0; i--)
                kfree(env->line_cards[i]);
}

static int
mlxsw_env_module_event_enable(struct mlxsw_env *mlxsw_env, u8 slot_index)
{
        int err;

        err = mlxsw_env_module_oper_state_event_enable(mlxsw_env->core,
                                                       slot_index);
        if (err)
                return err;

        err = mlxsw_env_module_temp_event_enable(mlxsw_env->core, slot_index);
        if (err)
                return err;

        return 0;
}

static void
mlxsw_env_module_event_disable(struct mlxsw_env *mlxsw_env, u8 slot_index)
{
}

static int
mlxsw_env_module_type_set(struct mlxsw_core *mlxsw_core, u8 slot_index)
{
        struct mlxsw_env *mlxsw_env = mlxsw_core_env(mlxsw_core);
        int i;

        for (i = 0; i < mlxsw_env->line_cards[slot_index]->module_count; i++) {
                struct mlxsw_env_module_info *module_info;
                char pmtm_pl[MLXSW_REG_PMTM_LEN];
                int err;

                mlxsw_reg_pmtm_pack(pmtm_pl, slot_index, i);
                err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(pmtm), pmtm_pl);
                if (err)
                        return err;

                module_info = mlxsw_env_module_info_get(mlxsw_core, slot_index,
                                                        i);
                module_info->type = mlxsw_reg_pmtm_module_type_get(pmtm_pl);
        }

        return 0;
}

static void
mlxsw_env_linecard_modules_power_mode_apply(struct mlxsw_core *mlxsw_core,
                                            struct mlxsw_env *env,
                                            u8 slot_index)
{
        int i;

        for (i = 0; i < env->line_cards[slot_index]->module_count; i++) {
                enum ethtool_module_power_mode_policy policy;
                struct mlxsw_env_module_info *module_info;
                struct netlink_ext_ack extack;
                int err;

                module_info = &env->line_cards[slot_index]->module_info[i];
                policy = module_info->power_mode_policy;
                err = mlxsw_env_set_module_power_mode_apply(mlxsw_core,
                                                            slot_index, i,
                                                            policy, &extack);
                if (err)
                        dev_err(env->bus_info->dev, "%s\n", extack._msg);
        }
}

static void
mlxsw_env_got_active(struct mlxsw_core *mlxsw_core, u8 slot_index, void *priv)
{
        struct mlxsw_env *mlxsw_env = priv;
        char mgpir_pl[MLXSW_REG_MGPIR_LEN];
        int err;

        mutex_lock(&mlxsw_env->line_cards_lock);
        if (__mlxsw_env_linecard_is_active(mlxsw_env, slot_index))
                goto out_unlock;

        mlxsw_reg_mgpir_pack(mgpir_pl, slot_index);
        err = mlxsw_reg_query(mlxsw_env->core, MLXSW_REG(mgpir), mgpir_pl);
        if (err)
                goto out_unlock;

        mlxsw_reg_mgpir_unpack(mgpir_pl, NULL, NULL, NULL,
                               &mlxsw_env->line_cards[slot_index]->module_count,
                               NULL);

        err = mlxsw_env_module_event_enable(mlxsw_env, slot_index);
        if (err) {
                dev_err(mlxsw_env->bus_info->dev, "Failed to enable port module events for line card in slot %d\n",
                        slot_index);
                goto err_mlxsw_env_module_event_enable;
        }
        err = mlxsw_env_module_type_set(mlxsw_env->core, slot_index);
        if (err) {
                dev_err(mlxsw_env->bus_info->dev, "Failed to set modules' type for line card in slot %d\n",
                        slot_index);
                goto err_type_set;
        }

        mlxsw_env->line_cards[slot_index]->active = true;
        /* Apply power mode policy. */
        mlxsw_env_linecard_modules_power_mode_apply(mlxsw_core, mlxsw_env,
                                                    slot_index);
        mutex_unlock(&mlxsw_env->line_cards_lock);

        return;

err_type_set:
        mlxsw_env_module_event_disable(mlxsw_env, slot_index);
err_mlxsw_env_module_event_enable:
out_unlock:
        mutex_unlock(&mlxsw_env->line_cards_lock);
}

static void
mlxsw_env_got_inactive(struct mlxsw_core *mlxsw_core, u8 slot_index,
                       void *priv)
{
        struct mlxsw_env *mlxsw_env = priv;

        mutex_lock(&mlxsw_env->line_cards_lock);
        if (!__mlxsw_env_linecard_is_active(mlxsw_env, slot_index))
                goto out_unlock;
        mlxsw_env->line_cards[slot_index]->active = false;
        mlxsw_env_module_event_disable(mlxsw_env, slot_index);
        mlxsw_env->line_cards[slot_index]->module_count = 0;
out_unlock:
        mutex_unlock(&mlxsw_env->line_cards_lock);
}

static struct mlxsw_linecards_event_ops mlxsw_env_event_ops = {
        .got_active = mlxsw_env_got_active,
        .got_inactive = mlxsw_env_got_inactive,
};

static void mlxsw_env_max_module_eeprom_len_query(struct mlxsw_env *mlxsw_env)
{
        char mcam_pl[MLXSW_REG_MCAM_LEN];
        bool mcia_128b_supported = false;
        int err;

        mlxsw_reg_mcam_pack(mcam_pl,
                            MLXSW_REG_MCAM_FEATURE_GROUP_ENHANCED_FEATURES);
        err = mlxsw_reg_query(mlxsw_env->core, MLXSW_REG(mcam), mcam_pl);
        if (!err)
                mlxsw_reg_mcam_unpack(mcam_pl, MLXSW_REG_MCAM_MCIA_128B,
                                      &mcia_128b_supported);

        mlxsw_env->max_eeprom_len = mcia_128b_supported ? 128 : 48;
}

int mlxsw_env_init(struct mlxsw_core *mlxsw_core,
                   const struct mlxsw_bus_info *bus_info,
                   struct mlxsw_env **p_env)
{
        u8 module_count, num_of_slots, max_module_count;
        char mgpir_pl[MLXSW_REG_MGPIR_LEN];
        struct mlxsw_env *env;
        int err;

        mlxsw_reg_mgpir_pack(mgpir_pl, 0);
        err = mlxsw_reg_query(mlxsw_core, MLXSW_REG(mgpir), mgpir_pl);
        if (err)
                return err;

        mlxsw_reg_mgpir_unpack(mgpir_pl, NULL, NULL, NULL, &module_count,
                               &num_of_slots);
        /* If the system is modular, get the maximum number of modules per-slot.
         * Otherwise, get the maximum number of modules on the main board.
         */
        max_module_count = num_of_slots ?
                           mlxsw_reg_mgpir_max_modules_per_slot_get(mgpir_pl) :
                           module_count;

        env = kzalloc_flex(*env, line_cards, num_of_slots + 1);
        if (!env)
                return -ENOMEM;

        env->core = mlxsw_core;
        env->bus_info = bus_info;
        env->num_of_slots = num_of_slots + 1;
        env->max_module_count = max_module_count;
        err = mlxsw_env_line_cards_alloc(env);
        if (err)
                goto err_mlxsw_env_line_cards_alloc;

        mutex_init(&env->line_cards_lock);
        *p_env = env;

        err = mlxsw_linecards_event_ops_register(env->core,
                                                 &mlxsw_env_event_ops, env);
        if (err)
                goto err_linecards_event_ops_register;

        err = mlxsw_env_temp_warn_event_register(mlxsw_core);
        if (err)
                goto err_temp_warn_event_register;

        err = mlxsw_env_module_plug_event_register(mlxsw_core);
        if (err)
                goto err_module_plug_event_register;

        /* Set 'module_count' only for main board. Actual count for line card
         * is to be set after line card is activated.
         */
        env->line_cards[0]->module_count = num_of_slots ? 0 : module_count;
        /* Enable events only for main board. Line card events are to be
         * configured only after line card is activated. Before that, access to
         * modules on line cards is not allowed.
         */
        err = mlxsw_env_module_event_enable(env, 0);
        if (err)
                goto err_mlxsw_env_module_event_enable;

        err = mlxsw_env_module_type_set(mlxsw_core, 0);
        if (err)
                goto err_type_set;

        mlxsw_env_max_module_eeprom_len_query(env);
        env->line_cards[0]->active = true;

        return 0;

err_type_set:
        mlxsw_env_module_event_disable(env, 0);
err_mlxsw_env_module_event_enable:
        mlxsw_env_module_plug_event_unregister(env);
err_module_plug_event_register:
        mlxsw_env_temp_warn_event_unregister(env);
err_temp_warn_event_register:
        mlxsw_linecards_event_ops_unregister(env->core,
                                             &mlxsw_env_event_ops, env);
err_linecards_event_ops_register:
        mutex_destroy(&env->line_cards_lock);
        mlxsw_env_line_cards_free(env);
err_mlxsw_env_line_cards_alloc:
        kfree(env);
        return err;
}

void mlxsw_env_fini(struct mlxsw_env *env)
{
        env->line_cards[0]->active = false;
        mlxsw_env_module_event_disable(env, 0);
        mlxsw_env_module_plug_event_unregister(env);
        /* Make sure there is no more event work scheduled. */
        mlxsw_core_flush_owq();
        mlxsw_env_temp_warn_event_unregister(env);
        mlxsw_linecards_event_ops_unregister(env->core,
                                             &mlxsw_env_event_ops, env);
        mutex_destroy(&env->line_cards_lock);
        mlxsw_env_line_cards_free(env);
        kfree(env);
}