-- Copyright (c) 2024 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.


local log = require 'mc.logging'
local crc8 = require 'mc.crc8'
local ctx = require 'mc.context'
local bs = require 'mc.bitstring'
local _, skynet = pcall(require, 'skynet')
local utils = require 'power_mgmt_utils'
local enums = require 'macros.power_mgmt_enums'
local fw_def = require 'device_tree.adapters.power_mgmt.protocol.upgrade.fw_def'
local upgrade = require 'device_tree.adapters.power_mgmt.protocol.upgrade.pmbus_upgrade'
local c_tasks = require 'mc.orm.tasks'
local utils_vos = require 'utils.vos'

local pmbus = {}
pmbus.__index = pmbus

local PS_I_SCALING_FACTOR <const> = 100

local ADDR_WR_LEN <const> = 1
local CMD_LEN <const> = 1
local ADDR_RD_LEN <const> = 1
local BYTE_CNT_LEN <const> = 1
local CS_LEN <const> = 1
local BUFFER_MAX_LEN <const> = 32
local ADDR_WR_OFF <const> = 0

local CMD_OFF <const> = ADDR_WR_OFF + ADDR_WR_LEN
local REQ_DATA_OFF <const> = CMD_OFF + CMD_LEN
local ADDR_RD_OFF <const> = CMD_OFF + CMD_LEN
local RESP_DATA_OFF <const> = ADDR_RD_OFF + ADDR_RD_LEN
local BYTE_CNT_OFF <const> = RESP_DATA_OFF
local LOAD_INFO_RESP_LEN <const> = 36
local VOUT_EXPONENT<const> = 5

local E_ERR = 1
local E_OK  = 0
pmbus.E_ERR = E_ERR
pmbus.E_OK  = E_OK
local E_FAILED = '' -- 空错误信息

local INPUT_TYPE_NONE<const> = 0xff
local INPUT_TYPE_DC <const> = 0
local INPUT_TYPE_AC <const> = 1
local INPUT_TYPE_ACORDC <const> = 2
local RETRY_TIMES <const> = 3
local SOFT_INFO_SIZE <const> = 8
local BLACK_BOX_MAX_LEN_POWERCONVERTER_PMBUS<const> = 12416

-- 电源寄存器地址
pmbus.cmd = {
    VOUT_MODE           = 0x20,
    VOUT_COMMOND        = 0x21,
    PS_RATE             = 0x31,
    MFR_QB_SOFT_VER     = 0x6A,
    STATUS_BYTE         = 0x78,
    STATUS_WORD         = 0x79,
    STATUS_VOUT         = 0x7A,
    STATUS_IOUT         = 0x7B,
    STATUS_INPUT        = 0x7C,
    STATUS_TEMPERATURE  = 0x7D,
    STATUS_CML          = 0x7E,
    STATUS_OTHER        = 0x7F,
    STATUS_MFR_SPECIFIC = 0x80,
    STATUS_FANS_1_2     = 0x81,
    READ_VIN            = 0x88,
    READ_IIN            = 0x89,
    READ_12VOUT         = 0x8B,
    READ_12VIOUT        = 0x8C,
    READ_TEMPERATURE_1  = 0x8D,
    READ_TEMPERATURE_2  = 0x8E,
    READ_TEMPERATURE_3  = 0x8F,
    READ_POUT           = 0x96,
    READ_PIN            = 0x97,
    REVISON             = 0x98,
    MFR_ID              = 0x99,
    MFR_MODEL           = 0x9A,
    MFR_REVISION        = 0x9B,
    MFR_DATE            = 0x9D,
    -- MFR_SERIAL，序列号，电源砖使用0xFB，pmbus电源使用0x9E
    MFR_SERIAL          = 0xFB,
    EVENT               = 0xD0,
    PART_NUMBER         = 0xDE,
    INPUT_TYPE          = 0xDF,
    CONTROL_CMD         = 0xCE,
    DC_VERSION          = 0xe4,
    PFC_VERSION         = 0xe7,
    -- 读取电源砖黑匣子的总帧数（init.lua使用）
    READ_FRAME_CNT      = 0xEF,
    -- 设置下一步要提取的电源砖黑匣子帧编号（init.lua使用）
    WRITE_FRAME_ID      = 0xEA,
    -- 读取电源砖黑匣子第i帧（init.lua使用）
    READ_FRAME_DATA     = 0xEB,
    -- LOAD_INFO_INQUIRY，查询电源砖固件信息，电源砖使用0xF8，pmbus电源使用0xFB
    TIME_SERVICE        = 0xFA,
    LOAD_INFO_INQUIRY   = 0xF8,
    QB_VERSION          = 0xF3,    -- QB900电源砖新增命令字，查询QB900电源砖信息
    TIME_SERVICE        = 0xFA,
    LOAD_CTRL           = 0xFC,
    LOAD_DATA           = 0xFD
}

pmbus.firmware = {
    DCDC_SOFT = 0x80,
    PFC_SOFT  = 0x40,
    QB_SOFT   = 0x20
}

local PS_STANDARD_READ = {
    DC_VERSION_OFFSET = 0xf3
}

local function get_pbit(val)
    return 1 << val
end

function pmbus.new(psu_slot)
    return setmetatable({
        ps_id = psu_slot['SlotNumber'],
        psu_chip = psu_slot['PsuChip'][enums.PSU_CHIP_OBJECT.BLOCKIO],
        slot_i2c_addr = psu_slot['SlotI2cAddr'],
        fru_chip = psu_slot['FruChip'],
        fru_i2c_addr = psu_slot['FruI2cAddr'],
        is_support_power_on_upgrade = psu_slot['IsSupportPowerOnUpgrade'],
        utils = utils.get_instance()
    }, pmbus)
end

-- 封装crc8校验，与read_data比较
local function check_data(check_sum, data)
    local crc = crc8(data)
    if crc ~= check_sum then
        log:debug('get crc: %d, real crc: %d', check_sum, crc)
        return E_ERR
    end
    return E_OK
end

local function get_addr_wr(val)
    return val & 0xfffffffe
end

local function get_addr_rd(val)
    return val | 1
end

-- chip_write 写入指定长的数据
function pmbus:chip_write(cmd, data)
    local check_buf = table.concat({ string.char(self.slot_i2c_addr), string.char(cmd), data })
    local crc = crc8(check_buf)
    self.psu_chip:Write(ctx.new(), cmd, data .. string.pack('B', crc))
end

-- chip_bytewrite 从Chip对象中写一个Byte的信息
function pmbus:chip_bytewrite(cmd, data)
    self:chip_write(cmd, string.pack('B', data))
end

-- chip_wordwrite 从Chip对象中写一个Word的信息
function pmbus:chip_wordwrite(cmd, data)
    self:chip_write(cmd, string.pack('H', data))
end

-- chip_blkwrite 从Chip对象中写一个块的信息，第一位为数据长度
function pmbus:chip_blkwrite(cmd, data)
    self:chip_write(cmd, string.pack('B', #data) .. data)
end

-- chip_read 读取指定长的数据
function pmbus:chip_read(cmd, len)
    local err_str = ''
    for _ = 1, RETRY_TIMES, 1 do
        local value = self.psu_chip:Read(ctx.new(), cmd, len)
        local check_buf = { string.char(self.slot_i2c_addr), string.char(cmd), string.char(self.slot_i2c_addr | 0x01),
            value:sub(1, #value - 1) }
        if value and check_data(value:sub(#value, #value):byte(), table.concat(check_buf)) == E_OK then
            return value
        end
        err_str = table.concat({ '[power_mgmt][pmbus]read commad(0x', string.format('%02x', cmd), ') failed' })
    end
    error(err_str)
end

-- chip_wordread 读取一个16位的数据
function pmbus:chip_wordread(cmd)
    return string.unpack('H', self:chip_read(cmd, 3):sub(1, 2))
end

-- chip_byteread 读取一个8位的数据
function pmbus:chip_byteread(cmd)
    return string.unpack('B', self:chip_read(cmd, 2):sub(1, 1))
end

-- chip_blkread 读取一个块的信息
function pmbus:chip_blkread(cmd, len)
    local value = self:chip_read(cmd, len + 2)
    return value:sub(2, #value - 1)
end

-- 根据数据的实际长度，读取pmbus块数据
function pmbus:block_read(cmd)
    local val = self.psu_chip:Read(ctx.new(), cmd, BYTE_CNT_LEN):byte()
    if val == nil then
        error('[power_mgmt][pmbus] invalid read_len(nil)')
    end
    local read_len = val + BYTE_CNT_LEN + CS_LEN
    -- 读len至checksum的数据
    if read_len > BUFFER_MAX_LEN - RESP_DATA_OFF then
        error(table.concat({ 'invalid read len:', read_len, 'buffer len:', BUFFER_MAX_LEN - RESP_DATA_OFF }))
    end
    return self.psu_chip:Read(ctx.new(), cmd, read_len)
end

function pmbus:is_pmbus_cmd_read_i(cmd)
    return cmd == self.cmd.READ_IIN or cmd == self.cmd.READ_12VIOUT
end

-- get_dynamic_block 读取不定长的数据
function pmbus:get_dynamic_block(cmd)
    local value = self:block_read(cmd)
    local check_buf = table.concat({ string.char(get_addr_wr(self.slot_i2c_addr)),
        string.char(cmd),
        string.char(get_addr_rd(self.slot_i2c_addr)),
        value:sub(1, #value - 1) })
    -- value返回类型由pmbus.block_read保证
    if check_data(value:sub(-1, -1):byte(), check_buf) == E_OK then
        return value:sub(2, #value - 1)
    end
    error(table.concat({ '[power_mgmt][pmbus]read commad(0x', string.format('%02x', cmd), ') failed' }))
end

function pmbus:get_linear_11(cmd)
    local raw_word = self:chip_wordread(cmd)
    local value = raw_word & 0x7FF
    local bit = ((raw_word >> 11) & 0x1f)
    if raw_word & 0x8000 > 0 then
        bit = (255 - (bit | 0xE0)) + 1
        --  电流较小（0~1 A）时，以A为单位传值会导致精度丢失；
        --  计算电流时，将读数先放大，以10mA为单位传值以保留精度；
        return self:is_pmbus_cmd_read_i(cmd) and ((value * PS_I_SCALING_FACTOR) >> bit) or (value >> bit)
    end
    return value << bit
end

-- get_output_current 输出电流
function pmbus:get_output_current_amps()
    return self:get_linear_11(self.cmd.READ_12VIOUT) / 100
end

-- get_input_voltage 输入电压
function pmbus:get_input_voltage()
    return self:get_linear_11(self.cmd.READ_VIN)
end

-- get_output_power_watts 单电源输出功率
function pmbus:get_output_power_watts()
    return self:get_linear_11(self.cmd.READ_POUT)
end

-- get_power_supply_type 输入类型 交直流模式
function pmbus:get_power_supply_type()
    local ok, input_type = pcall(function()
        return self:chip_wordread(self.cmd.INPUT_TYPE)
    end)
    if not ok then
        return INPUT_TYPE_NONE
    end
    local power_supply_type = ({
        [0] = INPUT_TYPE_NONE,
        [1] = INPUT_TYPE_AC,
        [2] = INPUT_TYPE_DC,
        [3] = INPUT_TYPE_DC
    })[input_type]
    return power_supply_type
end

-- get_manufacturer 厂商
function pmbus:get_manufacturer()
    return self:get_dynamic_block(self.cmd.MFR_ID)
end

-- get_model 电源型号
function pmbus:get_model()
    return self:get_dynamic_block(self.cmd.MFR_MODEL)
end

-- get_product_version 电源版本
function pmbus:get_product_version()
    return self:get_dynamic_block(self.cmd.MFR_REVISION)
end

-- get_serial_number 电源序列号，去除电源字符串末尾读出的\r\n
function pmbus:get_serial_number()
    return self:get_dynamic_block(self.cmd.MFR_SERIAL):gsub("[\\r\\n]", "")
end

-- get_status_input 输入状态信息
function pmbus:get_status_input()
    return self:chip_byteread(self.cmd.STATUS_INPUT)
end

-- get_status_temperature 温度状态信息
function pmbus:get_status_temperature()
    return self:chip_byteread(self.cmd.STATUS_TEMPERATURE)
end

-- get_status_vout 输出电压状态
function pmbus:get_status_vout()
    return self:chip_byteread(self.cmd.STATUS_VOUT)
end

-- get_status_iout 输出电流状态
function pmbus:get_status_iout()
    return self:chip_byteread(self.cmd.STATUS_IOUT)
end

-- get_status_cml 逻辑、通信、存储器状态信息
function pmbus:get_status_cml()
    return self:chip_byteread(self.cmd.STATUS_CML)
end

-- get_status_vin 输入电压状态
function pmbus.get_status_vin()
    return pmbus:get_status_input()
end

-- check_input_loss 检查输入状态
function pmbus:check_input_loss(event)
    if event & get_pbit(4) > 0 then
        return 1
    else
        return 0
    end
end

-- get_specific 厂商自定义状态
function pmbus:get_specific()
    return self:chip_byteread(self.cmd.STATUS_MFR_SPECIFIC)
end

-- get_env_temperature_celsius 环境温度
function pmbus:get_env_temperature_celsius()
    return self:get_linear_11(self.cmd.READ_TEMPERATURE_1)
end

-- get_primary_chip_temperature_celsius 电源内部器件温度（原边）
function pmbus:get_primary_chip_temperature_celsius()
    return self:get_linear_11(self.cmd.READ_TEMPERATURE_2)
end

-- get_soft_load_info 获取固件参数
function pmbus:get_soft_load_info()
    local soft_load_info =  bs.new([[<<
        power_type:8/unit:8,
        soft_cnt:8,
        protocol_ver:8,
        frame_len:8,
        resv:8,
        soft_info/string
    >>]]):unpack(self:chip_blkread(self.cmd.LOAD_INFO_INQUIRY, LOAD_INFO_RESP_LEN), true)

    local soft_info = {}
    for i = 1, 3 do
        -- 软件信息大小为8字节
        table.insert(
            soft_info,
            bs.new([[<<
                soft_id:8,
                erase_time:16,
                write_waiting_time:16,
                restart_time:8,
                module_num:8,
            resv:8>>]]):unpack(soft_load_info.soft_info:sub((i - 1) * SOFT_INFO_SIZE + 1, i * SOFT_INFO_SIZE), true)
        )
    end
    return {
        power_type = soft_load_info.power_type,
        soft_cnt = soft_load_info.soft_cnt,
        protocol_ver = soft_load_info.protocol_ver,
        frame_len = soft_load_info.frame_len,
        recv = soft_load_info.recv,
        soft_info = soft_info,
    }
end

-- load_app_ctrl_cmd 下发控制命令
function pmbus:load_app_ctrl_cmd(cmd, data)
    local check_buf = table.concat({ string.char((self.slot_i2c_addr)),
        string.char(self.cmd.LOAD_CTRL),
        string.char(data & 0xff),
        string.char(data >> 8) })
    local crc = crc8(check_buf)
    check_buf = check_buf .. string.char(crc)
    self.psu_chip:Write(ctx.new(), cmd, check_buf:sub(3, -1))
end

function pmbus:load_app_ctrl_cmd_read(cmd)
    return self:chip_wordread(cmd)
end

function pmbus:load_soft_bin_data_to_chip(soft_info_from_bin, frame_len, block_num, wait_time)
    local context = ctx.new()
    -- 设置超时时间600s
    context.Timeout = 600
    self.psu_chip:PluginRequest(context, 'power_mgmt', 'load_soft_bin_data_by_plugins',
        skynet.packstring(self.slot_i2c_addr, frame_len, block_num, wait_time, soft_info_from_bin))
end

-- 以下函数QB900和通用pmbus电源有差异

function pmbus:get_fru_data(fru_config)
    local version_func
    
    if fru_config == 1 then
        version_func = self:get_firmware_version()
    else
        version_func = self:get_product_version()
    end
    return {
        ManufacturerName = self:get_manufacturer(),
        ProductName = self:get_model(),
        ProductVersion = version_func,
        ProductSerialNumber = self:get_serial_number(),
    }
end

-- get_health_event 获取健康状态
function pmbus:get_health_event()
    local event = 0
    local failed_flag = false
    local health_event = {}

    -- 输入状态信息 7C
    local ok, vin_status = pcall(function()
        return self:get_status_input()
    end)
    if not ok then
        failed_flag = true
    else
        health_event.input_voltage_fault = vin_status
        if vin_status & 0x90 > 0 then
            event = event | get_pbit(4)
        end
    end
    -- 温度状态信息 7D
    local ok, temper_status = pcall(function()
        return self:get_status_temperature()
    end)
    if not ok then
        failed_flag = true
    else
        health_event.temper_fault = temper_status
        if temper_status & 0xf0 > 0 then
            event = event | get_pbit(3)
        end
    end
    -- 无风扇
    local ok, vout_status = pcall(function()
        return self:get_status_vout()
    end)
    if not ok then
        failed_flag = true
    else
        health_event.output_voltage_fault = vout_status
    end
    local ok, iout_status = pcall(function()
        return self:get_status_iout()
    end)
    if not ok then
        failed_flag = true
    else
        health_event.output_current_fault = iout_status
    end
    health_event.event = event
    return health_event, failed_flag
end

function pmbus:get_soft_version(soft_id)
    local cmd = ({
        [self.firmware.QB_SOFT] = self.cmd.QB_VERSION
    })[soft_id]
    for _ = 1, RETRY_TIMES, 1 do
        local ok, ver = pcall(function()
            return self:chip_wordread(cmd)
        end)
        if ok then
            return ver
        else
            self.utils:frequency_limit_log(enums.LOG_LEVEL.ERROR, 60,
                'get_soft_version failed, ps id: %d, soft id: %s, error info: %s', self.ps_id, soft_id, ver)
        end
    end
    return nil
end

function pmbus:get_firmware_version()
    local fw_version = self:get_soft_version(self.firmware.QB_SOFT)
    if not fw_version then
        return 'N/A'
    end
    return string.format('QB:%x%02x', ((fw_version >> 8) & 0xff), fw_version & 0xff)
end

function pmbus:get_linear_16(cmd, vout_mode)
    local exponent = (1 << VOUT_EXPONENT) - vout_mode
    -- bit0-4为exponent，需要判断正负数
    if (vout_mode & 0x10) ~= 0 then
        return tonumber(string.format("%.2f", self:chip_wordread(cmd) / (2 ^ exponent)))
    end

    return self:chip_wordread(cmd) << exponent
end

-- get_output_voltage 输出电压
function pmbus:get_output_voltage()
    local ok, vout_mode = pcall(function ()
        return self:chip_byteread(self.cmd.VOUT_MODE)
    end)

    -- 不支持获取vout_mode时，与原先逻辑保持一致
    if not ok then
        return self:chip_wordread(self.cmd.READ_12VOUT) >> 10
    end

    -- bit 7:5 为mode
    local mode = ((vout_mode >> VOUT_EXPONENT) & 0x07)
    -- 000 linear 001 VID 010 Direct
    if mode == 0 then
        -- 先转换为整数与原先保持一致
        return tonumber(string.format("%d", self:get_linear_16(self.cmd.READ_12VOUT, vout_mode)))
    end
    return nil
end

function pmbus:get_event_log()
    local chip = self.psu_chip
    local args = skynet.packstring(self.slot_i2c_addr, self.ps_id)
    local ok, data = skynet.unpack(chip:PluginRequest(ctx.new(), 'power_mgmt', "start_powerconverter_blackbox", args))
    if ok == E_FAILED then
        return BLACK_BOX_MAX_LEN_POWERCONVERTER_PMBUS, ''
    else
        return BLACK_BOX_MAX_LEN_POWERCONVERTER_PMBUS, data
    end
end

function pmbus:time_service()
    local sys_time_second = utils_vos.vos_get_cur_time_stamp()
    if sys_time_second >= 0xffffffff then
        self.utils:frequency_limit_log(enums.LOG_LEVEL.ERROR, 60, 'sys_time_second is error, psid %s', self.ps_id)
        return
    end
    -- 这里使用小端序
    local current_time = string.pack('<I4', sys_time_second)
    local ok, resp = pcall(self.chip_blkwrite, self, self.cmd.TIME_SERVICE, current_time)
    if not ok then
        self.utils:frequency_limit_log(enums.LOG_LEVEL.ERROR, 60,
            'time service failed, psid: %s, error info: %s', self.ps_id, resp)
    end
end

function pmbus:upgrade(upgrade_path, upgrade_process, para_tab)
    local obj = upgrade(self).new(upgrade_process, para_tab)
    local upgrade_ret = obj:process(upgrade_path)

    if upgrade_ret ~= fw_def.SUPPLY_0 then
        log:error('upgrade ps%u failed', self.ps_id)
    else
        log:notice('upgrade ps%u successfully', self.ps_id)
    end
    return  upgrade_ret
end

-- 查询软件版本
function pmbus:inquiry_psu_version(soft_info)
    -- QB900等电源砖，soft id是0x80，但是查询固件版本需要使用命令字0xf3
    local cmd = ({
        [fw_def.FIRMWARE.DCDC_SOFT] = PS_STANDARD_READ.DC_VERSION_OFFSET
    })[soft_info.soft_id]
    for _ = 1, fw_def.GET_RESULT_RETRY do
        local ok, rsp = pcall(function()
            return self:chip_wordread(cmd)
        end)
        log:notice("qurey softid:%s version ok, version num is %s", soft_info.soft_id, rsp)
        if ok then
            return nil
        end
        c_tasks.get_instance():sleep_ms(100)
    end
    return ''
end

function pmbus:time_service()
    local sys_time_second = utils_vos.vos_get_cur_time_stamp()
    if sys_time_second >= 0xffffffff then
        self.utils:frequency_limit_log(enums.LOG_LEVEL.ERROR, 60, 'sys_time_second is error, psid %d', self.ps_id)
        return
    end
    -- 这里使用小端序
    local current_time = string.pack('<I4', sys_time_second)
    local ok, resp = pcall(self.chip_blkwrite, self, self.cmd.TIME_SERVICE, current_time)
    if not ok then
        self.utils:frequency_limit_log(enums.LOG_LEVEL.ERROR, 60,
            'time service failed, psid: %d, error info: %s', self.ps_id, resp)
    end
end

return pmbus
