-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: mp2882芯片处理

local skynet = require 'skynet'
local class = require 'mc.class'
local context = require 'mc.context'
local file_sec = require 'utils.file'
local crc8 = require 'mc.crc8'
local log = require 'mc.logging'
local defs = require 'independent_vrd.ind_vrd_defs'

local MPS2882_USR_VERSION_PAGE = 0x01
local MPS2882_USR_VERSION_REG = 0x01
local MPS2882_PAGE_REG = 0x00
local MP2882_STATUS_CML_REG = 0x7e

local PMBUS_READ_HEAD_LEN = 3
local PMBUS_PEC_LEN = 1
local MAX_RETRY = 10

local EXPECT_CLOUMN_NUMBER = 7
local INDEX_CLOUMN = 2
local DATA_CLOUMN = 5
local HEXADECIMAL_BASE = 16
local END_FLAG = "END"
local MAX_LINE_SIZE = 1024
local MP2882_MTP_SIZE = 3072
local OVERWRITING_RETRY = 3

local BYTE_BITS_NUM = 8

local mp2882_pmbus = class(nil, nil, true)

function mp2882_pmbus:ctor(tab, position)
    -- tab.object 是原始的VRDFirmware配置对象
    self.obj = tab.object
    self.position = position
end

-- 检查芯片是否可访问
function mp2882_pmbus:check_chip_accessible()
    log:notice('[mp2882] Checking chip accessibility, UID: %s', self.obj.UID)

    if not self.obj.RefChip then
        log:error('[mp2882] RefChip not configured')
        return false
    end

    log:notice('[mp2882] Chip accessibility check passed')
    return true
end

-- 总长度datalen + PMBUS_READ_HEAD_LEN + PMBUS_PEC_LEN
function mp2882_pmbus:read_data(chip, cmd, datalen, addr)
    -- 头三个字节：addr, cmd, 第一个字节最低位置1
    local head = string.char(addr, cmd, addr | 1)

    for _ = 1, MAX_RETRY do
        local ok, payload = pcall(function()
            return chip:Read(context.get_context_or_default(), cmd, datalen + PMBUS_PEC_LEN)
        end)
        if not ok or not payload or #payload < (datalen + PMBUS_PEC_LEN) then
            log:error('[mp2882] Read failed due to insufficient data/errors')
            goto continue
        end

        -- 拼接完整码流
        local full_stream = head .. payload
        -- 计算CRC
        local crc = crc8(string.sub(full_stream, 1, datalen + PMBUS_READ_HEAD_LEN))
        local expected_crc = string.byte(full_stream, datalen + PMBUS_READ_HEAD_LEN + PMBUS_PEC_LEN)
        if expected_crc == crc then
            -- crc校验
            return defs.RET.OK, string.sub(payload, 1, datalen)
        end
        ::continue::
    end

    log:error('[mp2882] Read cmd:0x%02X failed after %d retries', cmd, MAX_RETRY)
    return defs.RET.ERR
end

-- 总长度datalen + PMBUS_WRITE_HEAD_LEN + PMBUS_PEC_LEN
function mp2882_pmbus:write_data(chip, cmd, data, addr)
    -- 头三个字节：addr, cmd
    local head = string.char(addr, cmd)
    local write_data = data .. string.char(crc8(head .. data))
    local ok, payload
    for _ = 1, MAX_RETRY do
        ok, payload = pcall(function()
            return chip:Write(context.get_context_or_default(), cmd, write_data)
        end)
        if ok then
            return defs.RET.OK
        end
        skynet.sleep(1) -- 10ms
    end

    log:error('[mp2882] Write failed, cmd:%s, err: %s', cmd, payload)
    return defs.RET.ERR
end

-- 索引从0开始，大小为3072字节的数组，承载写入数据流
local function init_data_buf(len)
    local data = {}
    for i = 0, len - 1 do
        data[i] = 0x00
    end
    return data
end

local function mp2882_vrd_pmbus_bytewrite(chip, cmd, data, addr)
    local data_bytes = string.char(data)
    return mp2882_pmbus:write_data(chip, cmd, data_bytes, addr)
end

local function mp2882_vrd_pmbus_wordwrite(chip, cmd, data, addr)
    -- 按WORD写入，低字节在前，高字节在后
    local data_bytes = string.char(data & 0xFF, (data >> BYTE_BITS_NUM) & 0xFF)
    return mp2882_pmbus:write_data(chip, cmd, data_bytes, addr)
end

local function write_serial_cmd(chip, data_arr, addr)
    for _, cmd in ipairs(data_arr) do
        local ret = cmd.func(chip, cmd.command, cmd.data, addr)
        if ret ~= defs.RET.OK then
            return defs.RET.ERR
        end
    end
    return defs.RET.OK
end

function mp2882_pmbus:mp2882_select_page(chip, page, addr)
    -- 切页
    local ret_page = mp2882_vrd_pmbus_bytewrite(chip, MPS2882_PAGE_REG, page, addr)
    if ret_page ~= defs.RET.OK then
        return defs.RET.ERR
    end

    -- 回读页号，确保切页成功
    local ret, data = self:read_data(chip, MPS2882_PAGE_REG, 1, addr)
    if ret ~= defs.RET.OK or not data or data == 255 then
        log:error('[mp2882] Failed to get page: %s', data)
        return defs.RET.ERR
    end
    local page_read = string.byte(data, 1)
    if page_read == page then
        log:notice('[mp2882] select page: %s success, page_read: %s', page, page_read)
        return defs.RET.OK
    end

    log:error('[mp2882] Failed to check page: %s, page_read: %s', page, page_read)
    return defs.RET.ERR
end

-- 从固件文件解析固件内容
-- 文件每一行有7列，均按照如下格式排列（以tab符分隔）
--  0001    0   index(16进制)   index(10进制)   地址标识    data(16进制)    data(10进制)
function mp2882_pmbus:read_data_from_data_file(file, data_buf_len)
    local ret = defs.RET.ERR
    local line = 0
    local data_buf = init_data_buf(data_buf_len)
    local fp, err = file_sec.open_s(file, 'r')
    if not fp then
        log:error('open file failed, err: %s', err)
        return defs.RET.ERR
    end

    -- 读取固件文件并格式化成数据流
    while true do
        local line_buf = fp:read('*line')
        if not line_buf then break end
        line = line + 1

        -- 检查END标志
        if line_buf:match("^%s*" .. END_FLAG) then
            ret = defs.RET.OK
            break
        end

        -- 检查行数是否超限
        if line > MP2882_MTP_SIZE then
            log:error('[mp2882] read_data_from_data_file failed, line exceed limit %d', MP2882_MTP_SIZE)
            break
        end

        -- 按tab分割每行数据
        local str_arr = {}
        for field in line_buf:gmatch("[^\t]+") do
            table.insert(str_arr, field)
        end

        -- 检查列数是否符合格式
        if #str_arr ~= EXPECT_CLOUMN_NUMBER then
            log:error('[mp2882] read_data_from_data_file failed, line[%s] incorrect format, expected %d columns, got %d columns', 
                        line, EXPECT_CLOUMN_NUMBER, #str_arr)
            break
        end

        -- 解析索引
        local index_str = str_arr[INDEX_CLOUMN + 1]
        local index = tonumber(index_str, HEXADECIMAL_BASE)
        if not index or index_str:match("[^0-9a-fA-F]") then
            log:error('[mp2882] read_data_from_data_file failed, invalid index format at line %d: %s', line, index_str)
            break
        end

        -- 解析数据
        local data_str = str_arr[DATA_CLOUMN + 1]
        local data_val = tonumber(data_str, HEXADECIMAL_BASE)
        if not data_val or data_str:match("[^0-9a-fA-F]") then
            log:error('[mp2882] read_data_from_data_file failed, invalid data format at line %d: %s', line, index_str)
            break
        end

        log:info('[mp2882] read_data_from_data_file: line = %d, index = 0x%04X, data = 0x%02X', line, index, data_val)

        -- 索引超出buffer长度则数据异常
        if index >= data_buf_len then
            log:warn('[mp2882] read_data_from_data_file: index %d >= buffer len %d, skip', index, data_buf_len)
            break
        end

        -- 将格式化后数据写入buffer
        data_buf[index] = data_val
    end

    fp:close()
    return ret, data_buf
end

-- 解锁MTP并进入烧录模式
function mp2882_pmbus:unlock_and_enter_overwriting_mode()
    local data_arr = {
        { command = 0x00, data = 0x01, func = mp2882_vrd_pmbus_bytewrite},
        { command = 0xA4, data = 0x008C, func = mp2882_vrd_pmbus_wordwrite},
        { command = 0x00, data = 0x01, func = mp2882_vrd_pmbus_bytewrite},
        { command = 0xA2, data = 0x2480, func = mp2882_vrd_pmbus_wordwrite},
        { command = 0x00, data = 0x00, func = mp2882_vrd_pmbus_bytewrite},
        { command = 0x10, data = 0x8509, func = mp2882_vrd_pmbus_wordwrite}
    }
    return write_serial_cmd(self.obj.RefChip, data_arr, self.obj.Address)
end

-- 退出烧录模式
function mp2882_pmbus:exit_overwriting_mode()
    local data_arr = {
        { command = 0x00, data = 0x02, func = mp2882_vrd_pmbus_bytewrite},
        { command = 0x73, data = 0x0000, func =mp2882_vrd_pmbus_wordwrite},
        { command = 0x72, data = 0x0000, func = mp2882_vrd_pmbus_wordwrite},
        { command = 0x70, data = 0xFF00, func = mp2882_vrd_pmbus_wordwrite},
        { command = 0x00, data = 0x00, func = mp2882_vrd_pmbus_bytewrite}
    }
    return write_serial_cmd(self.obj.RefChip, data_arr, self.obj.Address)
end

-- 烧录VRD固件内容
function mp2882_pmbus:execute_overwriting_action(data)
    local chip = self.obj.RefChip
    -- 烧录固件3次重传
    for retry = 1, OVERWRITING_RETRY do
        log:notice('[mp2882] retry %d overwriting start', retry)
        local ok = skynet.unpack(chip:PluginRequest(context.get_context_or_default(), 'general_hardware', 'mp2882_upgrade',
        skynet.packstring(data, self.obj.Address)))
        if not ok then
            log:error('[mp2882] retry %d overwriting failed', retry)
        else
            return defs.RET.OK
        end
    end
    return defs.RET.ERR
end

-- 记录状态寄存器
function mp2882_pmbus:check_cml_status()
    local cml_status = 0
    -- 遍历2个page读取状态寄存器
    for i = 0, 1 do
        local ret_page = self:mp2882_select_page(self.obj.RefChip, i, self.obj.Address)
        if ret_page ~= defs.RET.OK then
            log:error('[mp2882] Failed to select page, VrdId: %s', self.obj.VrdId)
            goto continue
        end
        local ret, payload = self:read_data(self.obj.RefChip, MP2882_STATUS_CML_REG, 1, self.obj.Address)
        if ret ~= defs.RET.OK then
            log:error('[mp2882] read STATUS_CML failed, VrdId: %s', self.obj.VrdId)
            goto continue
        end
        cml_status = string.byte(payload, 1)
        if (cml_status & 0x11) == 0 then
            log:notice('[mp2882] VrdId:%s last store success, STATUS_CML of page%d is %s', self.obj.VrdId, i, cml_status)
        else
            log:error('[mp2882] VrdId:%s last store failed, STATUS_CML of page%d is %s', self.obj.VrdId, i, cml_status)
        end
        ::continue::
    end
end

-- 获取VRD版本信息
function mp2882_pmbus:get_version()
    -- 从Page 1 addr 01h 高8位获取版本信息，即寄存器地址 MP2882_USR_VERSION
    local ret_page = self:mp2882_select_page(self.obj.RefChip, MPS2882_USR_VERSION_PAGE, self.obj.Address)
    if ret_page ~= defs.RET.OK then
        log:error('[mp2882] Failed to select page, VrdId: %s', self.obj.VrdId)
        return ''
    end

    local ret, data = self:read_data(self.obj.RefChip, MPS2882_USR_VERSION_REG, 2, self.obj.Address)
    -- 255为异常版本
    if ret ~= defs.RET.OK or not data or data == 255 then
        log:error('[mp2882] Failed to get_version: %s', data)
        return ''
    end
    -- 低字节在前，高字节在后
    log:notice('[mp2882] get_version VrdId: %s, data: %s', self.obj.VrdId, string.byte(data, 2))
    self:check_cml_status()
    return string.byte(data, 2)
end

-- 主升级接口
function mp2882_pmbus:upgrade(dir)
    log:notice('[mp2882] ===== Start VRD upgrade =====')
    -- 芯片检测
    if not self:check_chip_accessible() then
        log:error('[mp2882] Chip accessibility check failed')
        return defs.RET.ERR
    end

    -- step 1, read date from data file.
    if self.obj.UpgradeFileName.FirmwareFileName == '' then
        log:error('[mp2882] not find FirmwareFileName')
        return defs.RET.ERR
    end
    local txt_file = dir .. self.obj.UpgradeFileName.FirmwareFileName
    local ret, data = self:read_data_from_data_file(txt_file, MP2882_MTP_SIZE)
    if ret ~= defs.RET.OK then
        return defs.RET.ERR
    end

    -- step 2, unlock and enter the MTP overwriting mode.
    ret = self:unlock_and_enter_overwriting_mode()
    if ret ~= defs.RET.OK then
        log:error('[mp2882] unlock and enter overwriting mode failed, VrdId: %s', self.obj.VrdId)
        return defs.RET.ERR
    end

    -- step 3, execute the MTP overwriting action.
    ret = self:execute_overwriting_action(data)
    if ret ~= defs.RET.OK then
        log:error('[mp2882] execute overwriting action failed, VrdId: %s', self.obj.VrdId)
        return defs.RET.ERR
    end

    -- step 4, exit the MTP overwriting mode.
    ret = self:exit_overwriting_mode()
    if ret ~= defs.RET.OK then
        log:error('[mp2882] unlock and enter overwriting mode failed, VrdId: %s', self.obj.VrdId)
        return defs.RET.ERR
    end

    log:notice('[mp2882] ===== VRD upgrade completed successfully =====')
    return defs.RET.OK
end

-- VRD生效
function mp2882_pmbus:valid_vrd()
    if not self.obj.ValidateReg then
        log:error('[mp2882] ValidateReg not configured')
        return defs.RET.ERR
    end
    local ok, err = pcall(function()
            self.obj.ValidateReg = 1
        end)
    if not ok then
        log:error('[mp2882] Set vrd vaild failed, err: %s', err)
        return defs.RET.ERR
    end
    log:notice('[mp2882] Set vrd vaild successfully')
    return defs.RET.OK
end

return mp2882_pmbus