#include "lip_phy_defs.h"
#if LIP_PHY_ENABLE
#include "ltimer.h"
#include "lip/ll/lip_ll.h"
#include "lip/lip_log.h"
#include "lip_phy.h"
#include "lip/lip_private.h"

#include "lip_phy_cfg_defs.h"
#include "lip_phy_mdio_regs.h"
#include "lip_phy_stats.h"
#include "lip/link/lip_port_link_private.h"

typedef enum {
    LIP_PHY_INIT_STATE_NO_INIT, /* начальное состояние, указывает, что инициализация еще не была выполнена */
    LIP_PHY_INIT_STATE_WAIT_RST, /* ожидание аппаратного сброса PHY (если определены соответствующие пользовательские функции) */
    LIP_PHY_INIT_STATE_CFG_REQUIRED, /* требуется выполнение конфигурации PHY */
    LIP_PHY_INIT_STATE_DONE, /* рабочий режим после завершения настройки */
    LIP_PHY_INIT_STATE_ERROR, /* ошибка работы с PHY, требуется ручная переинициализация */
    LIP_PHY_INIT_STATE_TEST /* режим теста PHY */
} t_lip_phy_init_state;



/* Таблица с поддерживаемыми PHY */
extern t_lip_phy_info lip_phy_dp83848j;
extern t_lip_phy_info lip_phy_dp83848c;
extern t_lip_phy_info lip_phy_lan8720;
extern t_lip_phy_info lip_phy_lan8742;
extern t_lip_phy_info lip_phy_ksz8567;
extern t_lip_phy_info lip_phy_ksz9131;
extern t_lip_phy_info lip_phy_yt8521s;
extern t_lip_phy_info lip_phy_rtl8211f;

static const t_lip_phy_info *f_supported_phys[] = {
    #if LIP_PHY_SUPPORT_DP83848J
        &lip_phy_dp83848j,
    #endif
    #if LIP_PHY_SUPPORT_DP83848C
        &lip_phy_dp83848c,
    #endif
    #if LIP_PHY_SUPPORT_LAN8720
        &lip_phy_lan8720,
    #endif
    #if LIP_PHY_SUPPORT_LAN8742
        &lip_phy_lan8742,
    #endif
    #if LIP_PHY_SUPPORT_KSZ8567
        &lip_phy_ksz8567,
    #endif
    #if LIP_PHY_SUPPORT_KSZ9131
        &lip_phy_ksz9131,
    #endif
    #if LIP_PHY_SUPPORT_YT8521S
        &lip_phy_yt8521s,
    #endif
#if LIP_PHY_SUPPORT_RTL8211F
        &lip_phy_rtl8211f,
#endif
};


static void f_phy_update_mode(t_lip_phy_ctx *phy_ctx, const t_lip_phy_mode *mode) {
    if ((phy_ctx->state.mode.speed != mode->speed) || (phy_ctx->state.mode.flags != mode->flags)) {
        phy_ctx->state.mode = *mode;
        if (phy_ctx->iface_descr->cb.mode_changed) {
            phy_ctx->iface_descr->cb.mode_changed(phy_ctx);
        }
    }
}


static bool f_phy_get_link_up(const t_lip_phy_mdio_acc *regs_acc) {
    uint16_t state_reg;
    t_lip_errs err = lip_phy_mdio_reg_read(regs_acc, LIP_PHY_REGADDR_BASIC_STAT, &state_reg);
    return (err == LIP_ERR_SUCCESS) && (state_reg & LIP_PHY_REGFLD_BASIC_STAT_LINK_UP);
}

static t_lip_errs f_phy_link_up_update(t_lip_phy_ctx *phy_ctx, bool link_up) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    bool link_up_old = phy_ctx->state.flags & LIP_PHY_STATE_FLAG_LINK_UP;
    if (link_up != link_up_old) {
        LBITFIELD_UPD(phy_ctx->state.flags, LIP_PHY_STATE_FLAG_LINK_UP, link_up);
#if LIP_PHY_USE_STATS
        if (link_up) {
            lip_phy_link_stats_clear(&phy_ctx->stats);
        } else {
            ++phy_ctx->stats.results.link_drop_cnt;
        }
#endif
        /* После link-up, если разрешен autonegation, получаем от phy реально
         * согласованный режим */
        if (link_up && (phy_ctx->state.flags & LIP_PHY_STATE_FLAG_AN_EN)) {
            err = lip_phy_update_mode(phy_ctx);
            if (err == LIP_ERR_SUCCESS) {
                phy_ctx->internal.init_state = LIP_PHY_INIT_STATE_DONE;
            }
        }

        lip_printf(LIP_LOG_MODULE_PHY, LIP_LOG_LVL_DETAIL, 0,
                   "lip phy: %s - link %s\n", phy_ctx->iface_descr->name, link_up ? "up" : "down");
        if (phy_ctx->iface_descr->cb.link_status_changed) {
            phy_ctx->iface_descr->cb.link_status_changed(phy_ctx);
        }
        if (phy_ctx->iface_descr->port_idx >= 0) {
            lip_port_link_status_changed(phy_ctx->iface_descr->port_idx, link_up);
        }
    }
    return err;
}

static void f_phy_set_err(t_lip_phy_ctx *ctx) {
    ctx->state.flags |= LIP_PHY_STATE_FLAG_ERROR;
    ctx->state.flags &= ~LIP_PHY_STATE_FLAG_WORK_CFG;
    ctx->internal.init_state = LIP_PHY_INIT_STATE_ERROR;
}


/***************************************************************************//**
    Используется явно при нестандартном для контроллера подключении, при штатном
    для порта подключении вызывается во время инициализации стека автоматически.
    @param[out] ctx             Контекст взаимодействия с PHY, заполняется параметрами
                                в результате инициализации.
    @param[in]     iface_descr  Описание интерфейса доступа к PHY.
    @param[in]     user_ptr     Указатель на дополнительный пользовательский контекст, если требуется.
    @return                     Код ошибки.
********************************************************************************/
t_lip_errs lip_phy_ctx_init(t_lip_phy_ctx *phy_ctx, const t_lip_phy_iface_descr *iface_descr, void *user_ptr) {
    memset(phy_ctx, 0, sizeof(*phy_ctx));
    phy_ctx->iface_descr = iface_descr;
    phy_ctx->user_ctx = user_ptr;
    return LIP_ERR_SUCCESS;
}

/***************************************************************************//**
    Функция только заполняет параметры в контексте PHY, реальный процесс настройки
    в соответствии с параметрами и контекста запускается при вызове lip_phy_configure()
    (который также может вызываться из lip_phy_pull()).

    Если PHY уже обнаружено, то вызов данной функции приводит к тому, что при
    планируется реконфигурация PHY с помощью lip_phy_configure() при последующем
    вызове lip_phy_pull().

    @param[in,out] ctx          Контекст взаимодействия с PHY.
    @param[in]     cfg          Параметры конфигурации PHY, сохраяняемые в контексте.
    @return                     Код ошибки.
 ******************************************************************************/
t_lip_errs lip_phy_set_cfg(t_lip_phy_ctx *phy_ctx, const t_lip_phy_cfg *cfg) {
    phy_ctx->cfg = *cfg;
    if (phy_ctx->devinfo != NULL) {
        phy_ctx->internal.init_state = LIP_PHY_INIT_STATE_CFG_REQUIRED;
    }
    return LIP_ERR_SUCCESS;
}

/***************************************************************************//**
    Поиск подкюченного по MDIO PHY в соответствии с описанием интерфейса подключения
    phy_ctx->iface_descr.
    Функция пробует прочитать ID микросхемы PHY с использованием заданного
    интерфейса доступа к регистрам (descr->regs_iface), проходя по разрешенным
    в descr->phy_addr_msk адресам PHY, и найти соответствующую поддерживаемую
    модель PHY.
    При успешном выполнении информация о способе доступа сохраняется в reg_acc,
    а информация о типе найденной микросхемы PHY сохраняется в devinfo.
 ******************************************************************************/
t_lip_errs lip_phy_detect(t_lip_phy_ctx *phy_ctx) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    if (!phy_ctx->iface_descr || !phy_ctx->iface_descr->regs_iface) {
        err = LIP_ERR_PHY_NO_ACC_DESCR;
    } else {
        const t_lip_phy_info *fnd_info = NULL;
        t_lip_phy_mdio_acc reg_acc;
        reg_acc.iface = phy_ctx->iface_descr->regs_iface;
        for (uint8_t phy_addr = 0; (phy_addr < LIP_PHY_MDIO_ADDR_MAX) && (fnd_info == NULL); ++phy_addr) {
            if (LIP_PHY_ADDR_MSK_FIXED(phy_addr) & phy_ctx->iface_descr->phy_addr_msk) {
                uint32_t phy_id;

                reg_acc.phy_addr = phy_addr;
                err = lip_phy_mdio_get_phy_id(&reg_acc, &phy_id);
                if (err == LIP_ERR_SUCCESS) {
                    lip_printf(LIP_LOG_MODULE_PHY, LIP_LOG_LVL_DEBUG_MEDIUM, 0,
                               "lip phy: %s: check addr %d, phy id 0x%08X\n", phy_ctx->iface_descr->name, phy_addr, phy_id);
                    for (uint8_t phy_type_idx = 0; phy_type_idx < sizeof(f_supported_phys)/sizeof(f_supported_phys[0])
                         && (fnd_info == NULL); phy_type_idx++) {
                        const t_lip_phy_info *check_info = f_supported_phys[phy_type_idx];
                        if (LIP_PHY_MODEL_CHECK(phy_id, check_info->model_id)) {
                            fnd_info = f_supported_phys[phy_type_idx];
                            phy_ctx->reg_acc = reg_acc;
                        }
                    }
                }
            }
        }

        if (fnd_info) {
            phy_ctx->devinfo = fnd_info;
            lip_printf(LIP_LOG_MODULE_PHY, LIP_LOG_LVL_DEBUG_HIGH, 0,
                      "lip phy: %s - detect phy %s with addr %d\n", phy_ctx->iface_descr->name, fnd_info->name, reg_acc.phy_addr);
        } else {
            err = LIP_ERR_INIT_PHY_NOTFOUND;
            lip_printf(LIP_LOG_MODULE_PHY, LIP_LOG_LVL_ERROR_FATAL, res,
                    "lip phy: %s - cannot find ethernet phy!\n", phy_ctx->iface_descr->name);
        }
    }
    return err;
}

/* сопоставление режима работы phy и флага autonegotiation */
static inline t_lip_phy_an_feature f_get_an_mode_feature(const t_lip_phy_mode *mode) {
    const bool fd = mode->flags & LIP_PHY_MODE_FLAG_DUPLEX_FULL;
    return (mode->speed == LIP_PHY_SPEED_10) ? (fd ? LIP_PHY_AN_FEAT_MODE_10_T_FD : LIP_PHY_AN_FEAT_MODE_10_T_HD) :
           (mode->speed == LIP_PHY_SPEED_100) ? (fd ? LIP_PHY_AN_FEAT_MODE_100_TX_FD : LIP_PHY_AN_FEAT_MODE_100_TX_HD) :
           (mode->speed == LIP_PHY_SPEED_1000) ? (fd ? LIP_PHY_AN_FEAT_MODE_1000_T_FD : LIP_PHY_AN_FEAT_MODE_1000_T_HD) : 0;
}

/* установка вручную режима PHY без autonegotiation */
static t_lip_errs f_phy_set_manual_mode(t_lip_phy_ctx *phy_ctx, const t_lip_phy_mode *mode) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    uint16_t basic_ctl = 0;
    t_lip_phy_an_feature an_mode = f_get_an_mode_feature(mode);
    if ((an_mode & phy_ctx->devinfo->an_features) == 0) {
        err = LIP_ERR_PHY_UNSUP_MODE;
    } else {
        if (mode->flags & LIP_PHY_MODE_FLAG_DUPLEX_FULL) {
            basic_ctl |= LIP_PHY_REGFLD_BASIC_CTL_DUPLEX;
        }
        if (mode->speed == LIP_PHY_SPEED_1000) {
            basic_ctl |= LIP_PHY_REGFLDMSK_BASIC_CTL_SPPED_1000;
        } else if (mode->speed == LIP_PHY_SPEED_100) {
            basic_ctl |= LIP_PHY_REGFLDMSK_BASIC_CTL_SPPED_100;
        } else if (mode->speed == LIP_PHY_SPEED_100) {
            basic_ctl |= LIP_PHY_REGFLDMSK_BASIC_CTL_SPPED_10;
        }

        if (phy_ctx->cfg.opmode == LIP_PHY_OPMODE_LOCAL_LOOP) {
            basic_ctl |= LIP_PHY_REGFLD_BASIC_CTL_LOOPBACK;
        }

        err = lip_phy_mdio_reg_write(&phy_ctx->reg_acc, LIP_PHY_REGADDR_BASIC_CTL, basic_ctl);

        if ((err == LIP_ERR_SUCCESS) && (mode->speed == LIP_PHY_SPEED_1000)) {
            uint16_t ms_cfg = 0;
            ms_cfg |= LIP_PHY_REGFLD_MS_CTL_MS_MAN_EN;
            LBITFIELD_UPD(ms_cfg, LIP_PHY_REGFLD_MS_CTL_MS_MAN_CFG,
                          (mode->flags & LIP_PHY_MODE_FLAG_MASTER) ? LIP_PHY_REGFLDVAL_MS_CFG_MASTER : LIP_PHY_REGFLDVAL_MS_CFG_SLAVE);
            err = lip_phy_mdio_reg_write(&phy_ctx->reg_acc, LIP_PHY_REGADDR_MS_CTL, ms_cfg);
        }
    }

    if (err == LIP_ERR_SUCCESS) {
        phy_ctx->state.flags &= ~LIP_PHY_STATE_FLAG_AN_EN;
    }
    return err;
}

static t_lip_errs f_phy_set_an_mode(t_lip_phy_ctx *phy_ctx) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    bool an_cfg_changed = false;
    const uint32_t phy_an_feat = phy_ctx->devinfo->an_features;
    const uint32_t adv_an_feats = phy_an_feat & ~phy_ctx->cfg.an_dis_features;

    if (err == LIP_ERR_SUCCESS) {
        uint16_t an_adv = LBITFIELD_SET(LIP_PHY_REGFLD_AN_ADV_SEL, LIP_PHY_REGFLDVAL_AN_ADV_SEL_IEEE_802_3);
        if (adv_an_feats & LIP_PHY_AN_FEAT_MODE_10_T_HD)
            an_adv |= LIP_PHY_REGFLD_AN_ADV_TA_10_T_HD;
        if (adv_an_feats & LIP_PHY_AN_FEAT_MODE_10_T_FD)
            an_adv |= LIP_PHY_REGFLD_AN_ADV_TA_10_T_FD;
        if (adv_an_feats & LIP_PHY_AN_FEAT_MODE_100_TX_HD)
            an_adv |= LIP_PHY_REGFLD_AN_ADV_TA_100_TX_HD;
        if (adv_an_feats & LIP_PHY_AN_FEAT_MODE_100_TX_FD)
            an_adv |= LIP_PHY_REGFLD_AN_ADV_TA_100_TX_FD;
        if (adv_an_feats & LIP_PHY_AN_FEAT_MODE_100_T4)
            an_adv |= LIP_PHY_REGFLD_AN_ADV_TA_100_T4;
        if (adv_an_feats & LIP_PHY_AN_FEAT_PAUSE_ASYM)
            an_adv |= LIP_PHY_REGFLD_AN_ADV_TA_PAUSE_ASYM;
        if (adv_an_feats & LIP_PHY_AN_FEAT_PAUSE_SYM)
            an_adv |= LIP_PHY_REGFLD_AN_ADV_TA_PAUSE_SYM;
        err = lip_phy_mdio_reg_write_if_modified(&phy_ctx->reg_acc, LIP_PHY_REGADDR_AN_ADV, an_adv, 0xFFFF, &an_cfg_changed);

        lip_phy_mdio_reg_read(&phy_ctx->reg_acc, LIP_PHY_REGADDR_AN_ADV, &an_adv);
    }

    if ((err == LIP_ERR_SUCCESS) && (phy_an_feat & (LIP_PHY_AN_FEAT_MODE_1000_T_FD | LIP_PHY_AN_FEAT_MODE_1000_T_HD))) {
        uint16_t ms_ctl = 0;
        if (adv_an_feats & LIP_PHY_AN_FEAT_MODE_1000_T_FD)
            ms_ctl |= LIP_PHY_REGFLD_MS_CTL_ADV_1000_T_FD;
        if (adv_an_feats & LIP_PHY_AN_FEAT_MODE_1000_T_HD)
            ms_ctl |= LIP_PHY_REGFLD_MS_CTL_ADV_1000_T_HD;
        ms_ctl |= LBITFIELD_SET(LIP_PHY_REGFLD_MS_CTL_PORT_TYPE, (phy_ctx->cfg.flags & LIP_PHY_CFG_FLAG_MULTIPORT) ?
                                    LIP_PHY_REGFLDVAL_MS_CTL_PORT_TYPE_MULTI : LIP_PHY_REGFLDVAL_MS_CTL_PORT_TYPE_SINGLE);
        err = lip_phy_mdio_reg_write_if_modified(&phy_ctx->reg_acc, LIP_PHY_REGADDR_MS_CTL, ms_ctl,
                                                 LIP_PHY_REGFLD_MS_CTL_ADV_1000_T_FD | LIP_PHY_REGFLD_MS_CTL_ADV_1000_T_HD | LIP_PHY_REGFLD_MS_CTL_PORT_TYPE,
                                                 &an_cfg_changed);
    }

    if ((err == LIP_ERR_SUCCESS) && (phy_an_feat & (LIP_PHY_AN_FEAT_EEE_100_TX | LIP_PHY_AN_FEAT_EEE_1000_T))) {
       uint16_t eee_adv = 0;
       if (adv_an_feats & LIP_PHY_AN_FEAT_EEE_100_TX) {
           eee_adv |= LIP_PHY_REGFLD_AN_EEE_ADV1_100_TX;
       }
       if (adv_an_feats & LIP_PHY_AN_FEAT_EEE_1000_T) {
           eee_adv |= LIP_PHY_REGFLD_AN_EEE_ADV1_1000_T;
       }
       err = lip_phy_mdio_reg_write_if_modified(&phy_ctx->reg_acc, LIP_PHY_REGADDR_AN_EEE_ADV1, eee_adv,
                                                LIP_PHY_REGFLD_AN_EEE_ADV1_100_TX | LIP_PHY_REGFLD_AN_EEE_ADV1_1000_T, &an_cfg_changed);
    }


    if (err == LIP_ERR_SUCCESS) {
        uint16_t basic_ctl = LIP_PHY_REGFLD_BASIC_CTL_AN_EN;
        if (an_cfg_changed) {
            basic_ctl |= LIP_PHY_REGFLD_BASIC_CTL_AN_RSTART;
        }

        err = lip_phy_mdio_reg_write_if_modified(&phy_ctx->reg_acc,  LIP_PHY_REGADDR_BASIC_CTL, basic_ctl,
                                                 LIP_PHY_REGFLD_BASIC_CTL_AN_EN
                                                     | LIP_PHY_REGFLD_BASIC_CTL_AN_RSTART
                                                     | LIP_PHY_REGFLD_BASIC_CTL_LOOPBACK, /* если был loopback, то он должен быть снят */
                                                 &an_cfg_changed);

        lip_phy_mdio_reg_read(&phy_ctx->reg_acc,  LIP_PHY_REGADDR_BASIC_CTL, &basic_ctl);
    }

    if (err == LIP_ERR_SUCCESS) {
        phy_ctx->state.flags |= LIP_PHY_STATE_FLAG_AN_EN;
    }

    return err;
}


/***************************************************************************//**
    Функция на основе параметров конфигурации из контекста опроса PHY осуществляет
    запись требуемых значений в регистры PHY.
    В том числе вызывается пользовательская функция конфигурации, если она была
    задана.

    @param[in] ctx              Контекст взаимодействия с PHY.
    @return                     Код ошибки.
 ******************************************************************************/
t_lip_errs lip_phy_configure(t_lip_phy_ctx *phy_ctx) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    if (phy_ctx->devinfo == NULL) {
        err = LIP_ERR_INIT_PHY_NOTFOUND;
    } else {
        f_phy_link_up_update(phy_ctx, false);
        if (phy_ctx->devinfo->spec_cfg != NULL) {
            err = phy_ctx->devinfo->spec_cfg(&phy_ctx->reg_acc, &phy_ctx->cfg);
        }
        if ((err == LIP_ERR_SUCCESS) && (phy_ctx->iface_descr->cb.user_cfg)) {
            err = phy_ctx->iface_descr->cb.user_cfg(phy_ctx);
        }
    }

    if (err == LIP_ERR_SUCCESS) {
        if (phy_ctx->cfg.opmode == LIP_PHY_OPMODE_NORMAL) {
            if (phy_ctx->cfg.flags & LIP_PHY_CFG_FLAG_AN_DISABLED) {
                err = f_phy_set_manual_mode(phy_ctx, &phy_ctx->cfg.manual_mode);
                if (err == LIP_ERR_SUCCESS) {
                    f_phy_update_mode(phy_ctx, &phy_ctx->cfg.manual_mode);
                }
            } else {
                err = f_phy_set_an_mode(phy_ctx);
            }
        } else if (phy_ctx->cfg.opmode == LIP_PHY_OPMODE_LOCAL_LOOP) {
            /* в режиме кольца AutoNegotiation не нужен - явно
             * устанавливаем режим и ждем link up */
           err = f_phy_set_manual_mode(phy_ctx, &phy_ctx->cfg.manual_mode);
           if (err == LIP_ERR_SUCCESS) {
               f_phy_update_mode(phy_ctx, &phy_ctx->cfg.manual_mode);
           }
        }
    }

    if (err == LIP_ERR_SUCCESS) {
        bool cfg_done = (phy_ctx->cfg.opmode != LIP_PHY_OPMODE_PD);
        LBITFIELD_UPD(phy_ctx->state.flags, LIP_PHY_STATE_FLAG_WORK_CFG, cfg_done);
        phy_ctx->internal.init_state = LIP_PHY_INIT_STATE_DONE;
    }

    return err;
}


/***************************************************************************//**
    Функция определяет по регистрам PHY текущее состояние подключения линка
    и обновляет эту информацию в контексте PHY.
    В случае перехода из в подключенное состояния функция также обновляет информацию
    о режиме работы PHY, которая была согласована в случае разрешения Autonegotion
    с другой стороной.
    Также при смене состояния подключения вызывается пользовательская функция
    обратного вызвова, если она была задана.
    Как правило используется только самим модулем опроса PHY, извне в большинстве
    случаев предпочтительнее использовать lip_phy_check_link_req().

    @param[in] ctx              Контекст взаимодействия с PHY.
    @return                     Код ошибки.
 ******************************************************************************/
t_lip_errs lip_phy_check_link(t_lip_phy_ctx *phy_ctx) {
    t_lip_errs err = f_phy_link_up_update(phy_ctx, f_phy_get_link_up(&phy_ctx->reg_acc));
    if (err == LIP_ERR_SUCCESS) {
        phy_ctx->internal.link_check_time = lclock_get_ticks();
        phy_ctx->internal.link_bg_check_req = false;
        /* повторное чтение статуса, т.к. признак пропажи линка может сохраняться
         * до чтения даже если линк уже восстановлен.
         * В этом случае первое чтение обнаржут пропажу линка, а второе переведет
         * состояние подключения снова в link-up */
        err = f_phy_link_up_update(phy_ctx, f_phy_get_link_up(&phy_ctx->reg_acc));
    }
    return err;
}

/***************************************************************************//**
    Функция планирует вызов lip_phy_check_link() при последующем выполнении
    фоновых задач при вызове lip_poll() с учетом текущего состояния иницициализации
    PHY и т.д.

    @param[in] ctx              Контекст взаимодействия с PHY.
    @return                     Код ошибки.
 ******************************************************************************/
t_lip_errs lip_phy_check_link_req(t_lip_phy_ctx *phy_ctx) {
    phy_ctx->internal.link_bg_check_req = true;
    return LIP_ERR_SUCCESS;
}


/***************************************************************************//**
    Функция выполняет необходимые фоновые задачи по инициализации, проверки связи,
    обновления статстики PHY и т.д.
    Должна вызываться периодически при продвижении стека.
    Вызывается вручную, если используется пользовательское подключение, либо автоматом
    при продвижении стека, если стандартное для порта подключение.

    @param[in] ctx              Контекст взаимодействия с PHY.
    @return                     Код ошибки.
 ******************************************************************************/
t_lip_errs lip_phy_pull(t_lip_phy_ctx *phy_ctx) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    if (phy_ctx->iface_descr) {
        if (phy_ctx->internal.init_state == LIP_PHY_INIT_STATE_NO_INIT) {
            phy_ctx->state.flags = 0;
            /* если требуется аппаратный сброс PHY, то запускается соответствующий
             * процесс сброса, иначе сразу переход к конфигурации PHY */
            if (phy_ctx->iface_descr->hw_reset.start) {
                phy_ctx->iface_descr->hw_reset.start(phy_ctx);
                phy_ctx->internal.init_state = LIP_PHY_INIT_STATE_WAIT_RST;
            } else {
                phy_ctx->internal.init_state = LIP_PHY_INIT_STATE_CFG_REQUIRED;
            }
        } else if (phy_ctx->internal.init_state == LIP_PHY_INIT_STATE_WAIT_RST) {
            if (phy_ctx->iface_descr->hw_reset.check_done(phy_ctx)) {
                phy_ctx->internal.init_state = LIP_PHY_INIT_STATE_CFG_REQUIRED;
            }
        }

        if (phy_ctx->internal.init_state == LIP_PHY_INIT_STATE_CFG_REQUIRED) {
            /* если PHY еще не был обнаружен, то перед конфигурацией необходимо
             * выполнить обнаружение PHY */
            if (phy_ctx->devinfo == NULL) {
                if (phy_ctx->iface_descr->cb.user_pre_detect) {
                    phy_ctx->iface_descr->cb.user_pre_detect(phy_ctx);
                }
                err = lip_phy_detect(phy_ctx);
            }

            if (err == LIP_ERR_SUCCESS) {
                /* конфигурация настроек PHY */
                err = lip_phy_configure(phy_ctx);
                if (err == LIP_ERR_SUCCESS) {
                    /* проверка подключения */
                    err = lip_phy_check_link(phy_ctx);
                }
            }

            if (err != LIP_ERR_SUCCESS) {
                f_phy_set_err(phy_ctx);
            }
        }

#if LIP_PHY_USE_CABLE_TEST
        if (phy_ctx->internal.init_state == LIP_PHY_INIT_STATE_TEST) {
            err = phy_ctx->devinfo->cable_check.check(&phy_ctx->reg_acc, &phy_ctx->internal.cable_test.ctx, &phy_ctx->internal.cable_test.result);
            if ((err == LIP_ERR_SUCCESS) && phy_ctx->internal.cable_test.result.finished) {
                /* после завершения теста требуется повторная реконфигурация PHY */
                phy_ctx->internal.init_state = LIP_PHY_INIT_STATE_CFG_REQUIRED;
            }
        }
#endif

        if (phy_ctx->internal.init_state == LIP_PHY_INIT_STATE_DONE) {
            /* после завершения инициализации выполняем проверку состояния линка
             * либо по явному запросу, либо периодически, если задано флагом
             * описания порта доступа */
            if (phy_ctx->internal.link_bg_check_req ||
                    ((phy_ctx->iface_descr->flags & LIP_PHY_IFACE_FLAG_STATUS_POLL) &&
                    ((lclock_get_ticks() - phy_ctx->internal.link_check_time) >
                        LTIMER_MS_TO_CLOCK_TICKS(LIP_PHY_LINK_CHECK_INTERVAL)))) {
                err = lip_phy_check_link(phy_ctx);
            }
#if LIP_PHY_USE_STATS
            lip_phy_stats_poll(phy_ctx);
#endif
        }
    }

    return err;
}

/***************************************************************************//**
    Функция выполняет завершение и освобождение ресурсов по работе с PHY. Должна
    вызываться на каждый lip_phy_ctx_init(), когда взаимодействие с PHY больше
    не требуется.

    Если определен способ сброса PHY, то данная функция вызывает этот метод, чтобы
    оставить PHY в выключенном состоянии.

    @param[in] ctx              Контекст взаимодействия с PHY.
    @return                     Код ошибки.
 ******************************************************************************/
void lip_phy_close(t_lip_phy_ctx *phy_ctx) {
    f_phy_link_up_update(phy_ctx, false);
    phy_ctx->internal.init_state = LIP_PHY_INIT_STATE_NO_INIT;
    phy_ctx->state.flags = 0;
    if (phy_ctx->iface_descr->hw_reset.start) {
        phy_ctx->iface_descr->hw_reset.start(phy_ctx);
    }
}

/***************************************************************************//**
    Функция вычитывает из PHY инфорацию о текущем режиме работе и заполняет
    соответствующие поля контекста работы с PHY.
    Данная фукнция вызывается автоматом при обнаружении link-up в случае настроенного
    режима autonegotiation.

    @param[in] ctx              Контекст взаимодействия с PHY.
    @return                     Код ошибки.
 ******************************************************************************/
t_lip_errs lip_phy_update_mode(t_lip_phy_ctx *phy_ctx) {
    t_lip_phy_mode mode;
    t_lip_errs err = LIP_ERR_SUCCESS;
    if (phy_ctx->devinfo == NULL) {
        err = LIP_ERR_INIT_PHY_NOTFOUND;
    }

    if (err == LIP_ERR_SUCCESS) {
        err = phy_ctx->devinfo->get_mode(&phy_ctx->reg_acc, &mode);
    }
    if (err == LIP_ERR_SUCCESS) {
        if (mode.speed == LIP_PHY_SPEED_INVALID) {
            err = LIP_ERR_INIT_PHY_MODE;
        }
    }

    if (err == LIP_ERR_SUCCESS) {
        lip_printf(LIP_LOG_MODULE_PHY, LIP_LOG_LVL_DETAIL, 0,
                   "lip phy: %s mode - %d Mbit/s %s-duplex %s\n",
                   phy_ctx->iface_descr->name,
                    lip_phy_speed_mbit(mode.speed),
                    (mode.flags &  LIP_PHY_MODE_FLAG_DUPLEX_FULL) ? "full" : "half",
                    (mode.speed == LIP_PHY_SPEED_1000) ?
                    ((mode.flags & LIP_PHY_MODE_FLAG_MASTER) ? "master" : "slave") : "");
        f_phy_update_mode(phy_ctx, &mode);
    }
    return err;
}


/***************************************************************************//**
    Функция вычитывает из регистров PHY текущее состояние флагов прерываний и выполняет
    из сброс. Как правило выполняется в ответ от индикации прерывания от PHY для
    определения конкретного источника прерывания.
    Значения флагов зависят от импользуемой микросхемы PHY.

    @param[in]  ctx              Контекст взаимодействия с PHY.
    @param[out] intflags         Прочитанные значения флагов, определяющих, какие прерывания произошли.
    @return                      Код ошибки.
 ******************************************************************************/
t_lip_errs lip_phy_rd_intflags(t_lip_phy_ctx *phy_ctx, uint32_t *intflags) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    if (phy_ctx->devinfo == NULL) {
        err = LIP_ERR_INIT_PHY_NOTFOUND;
    } else if (phy_ctx->devinfo->rd_intflags != NULL) {
        err = phy_ctx->devinfo->rd_intflags(&phy_ctx->reg_acc, intflags);
    } else if (intflags != NULL) {
        *intflags = 0;
    }
    return err;
}

/***************************************************************************//**
    Функция выполняет программный сброс PHY (серез бит сброса в Basic Control регистре)
    и ожидает завершения сброса. После успешного выполнения PHY находится в состоянии,
    требующего выполнения новой конфигурации режима (либо явно, либо при последующей
    фоновой обработке).

    @param[in] ctx              Контекст взаимодействия с PHY.
    @param[in] tout             Таймаут в мс на выполнение сброса.
    @return                     Код ошибки.
 ******************************************************************************/
t_lip_errs lip_phy_sw_reset(t_lip_phy_ctx *phy_ctx, unsigned tout) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    if (phy_ctx->devinfo == NULL) {
        err = LIP_ERR_INIT_PHY_NOTFOUND;
    } else {
        phy_ctx->state.flags &= ~LIP_PHY_STATE_FLAG_WORK_CFG;
        f_phy_link_up_update(phy_ctx, false);
        err = lip_phy_mdio_reg_write(&phy_ctx->reg_acc, LIP_PHY_REGADDR_BASIC_CTL, LIP_PHY_REGFLD_BASIC_CTL_RST);
        if (err == LIP_ERR_SUCCESS) {
            bool rst_end = false;
            t_ltimer tmr;
            ltimer_set_ms(&tmr, tout);

            phy_ctx->internal.init_state = LIP_PHY_INIT_STATE_CFG_REQUIRED;
            do {
                uint16_t basic_ctl;
                err = lip_phy_mdio_reg_read(&phy_ctx->reg_acc, LIP_PHY_REGADDR_BASIC_CTL, &basic_ctl);
                if ((err == LIP_ERR_SUCCESS) && ((basic_ctl & LIP_PHY_REGFLD_BASIC_CTL_RST) == 0)) {
                    rst_end = true;
                }
            } while ((err == LIP_ERR_SUCCESS) && !rst_end && !ltimer_expired(&tmr));

            if (!rst_end && (err == LIP_ERR_SUCCESS)) {
                err = LIP_ERR_PHY_RESET_TIMEOUT;
            }
        }
    }
    return err;
}


#if LIP_PHY_USE_CABLE_TEST
t_lip_errs lip_phy_cable_test_start(t_lip_phy_ctx *phy_ctx, uint32_t pair_mask) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    if (phy_ctx->devinfo == NULL) {
        err = LIP_ERR_INIT_PHY_NOTFOUND;
    } else if (phy_ctx->devinfo->cable_check.start == NULL) {
        err = LIP_ERR_PHY_FEAT_NOT_SUPPORTED;
    } else {
        memset(&phy_ctx->internal.cable_test.result.finished, 0, sizeof(phy_ctx->internal.cable_test.result));
        err = phy_ctx->devinfo->cable_check.start(&phy_ctx->reg_acc, &phy_ctx->internal.cable_test.ctx, pair_mask);
        if (err == LIP_ERR_SUCCESS) {
            phy_ctx->internal.init_state = LIP_PHY_INIT_STATE_TEST;
        } else {
            f_phy_set_err(phy_ctx);
        }

    }
    return err;
}

const t_lip_phy_cable_test_result *lip_phy_cable_test_results(t_lip_phy_ctx *phy_ctx) {
    return &phy_ctx->internal.cable_test.result;
}
#endif
#endif
