-- 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 class = require 'mc.class'
local bs = require 'mc.bitstring'
local context = require 'mc.context'
local log = require 'mc.logging'
local utils = require 'mc.utils'
local cmn = require 'common'

local HEADER_LEN<const> = 12
local RETIMER_MSG_RETRY_TIMES<const> = 3
local RETIMER_CMD_REQUEST<const> = 0x20
local RETIMER_CMD_RESPONSE<const> = 0x21
local RETIMER_CMD_GET_STATUS<const> = 0x22
local RETIMER_IDLE_STATUS<const> = 0x10

local FRAME_DATA<const> = [[<<
    count:8,
    data/string
>>]]

local RESPONSE_BODY<const> = [[<<
    error_code:16,
    opcode:16,
    total_length:32,
    length:32,
    data:length/string
>>]]

local REQUEST_BODY<const> = [[<<
    lun:8,
    arg:8,
    opcode:16,
    offset:32,
    length:32,
    data:length/string
>>]]

local ERROR_CODE<const> = {
    SUCCESS = 0,
    COMMAND_ERROR = 0x1,
    PARAMETER_ERROR = 0x2,
    INTERNAL_ERROR = 0x3
}

local smbus = class()

function smbus:read(data_len)
    if not self:wait_for_idle() then
        return false, 'fail to wait chip idle'
    end
    local ok, data = pcall(self.ref_chip.Read, self.ref_chip, context.get_context_or_default(), RETIMER_CMD_RESPONSE,
        HEADER_LEN + data_len + 1) -- 包头+数据以及1字节帧头的长度字段
    if ok then
        if log:getLevel() >= log.DEBUG then
            log:debug('smbus read: ' .. utils.to_hex(data))
        end
        return self:parse_response(data)
    end
    return false, 'fail to read data'
end

function smbus:write(args)
    if not self:wait_for_idle() then
        return false, 'fail to wait chip idle'
    end

    local data = self:create_request(args)
    if log:getLevel() >= log.DEBUG then
        log:debug('smbus write: ' .. utils.to_hex(data))
    end
    local ok, msg = pcall(self.ref_chip.Write, self.ref_chip, context.new(), RETIMER_CMD_REQUEST, data)
    if ok then
        return true, msg
    end
    return false, 'fail to write data'
end

function smbus:wait_for_idle()
    for _ = 1, RETIMER_MSG_RETRY_TIMES do
        local ok, value = pcall(self.ref_chip.Read, self.ref_chip,
            context.get_context_or_default(), RETIMER_CMD_GET_STATUS, 1)
        if ok then
            local stat = string.byte(value)
            if stat == RETIMER_IDLE_STATUS then
                return true
            end
        end
        cmn.skynet.sleep(10) -- 等待100毫秒
    end
    return false
end

function smbus:create_request(args)
    -- 拼接负载数据
    local pattern = bs.new(REQUEST_BODY)
    local length = HEADER_LEN + (args['data'] and string.len(args['data']) or 0)
    local request = pattern:unpack(string.rep('\x00', length), true)
    request.lun = args['lun'] or 0x80 -- 默认0x80表示最后一次数据
    request.arg = args['arg'] or 0x0 -- 默认为0x0，其他特殊场景使用
    request.opcode = args['opcode'] or 0x0
    request.offset = args['offset'] or 0x0 -- 默认为0x0，其他特殊场景使用
    request.length = args['length'] or 0x0
    request.data = args['data'] or ''
    local payload = pattern:pack(request)
    payload = string.sub(payload, 1, length) -- 数据为空的场景，长度超长需要截断

    -- 拼接帧数据
    pattern = bs.new(FRAME_DATA)
    local rawdata = pattern:pack({
        count = length,
        data = payload
    })
    if log:getLevel() >= log.DEBUG then
        log:debug('create request, length: %s, data: %s', string.len(rawdata), utils.to_hex(rawdata))
    end
    return rawdata
end

function smbus:parse_response(rawdata)
    -- 解析帧数据，校验数据长度
    local frame_info = bs.new(FRAME_DATA):unpack(rawdata, true)
    if frame_info == nil then
        return false, 'unable to parse smbus frame data'
    end
    if frame_info.count ~= string.len(frame_info.data) then -- 字节数等于数据长度 + 1字节字节数
        return false, 'frame data is invalid, byte count is not equal to data length'
    end

    -- 解析负载数据，判断错误码
    local result = bs.new(RESPONSE_BODY):unpack(frame_info.data, true)
    if result == nil then
        return false, 'unable to parse smbus payload data'
    end
    if result.error_code ~= ERROR_CODE.SUCCESS then
        return false, 'error response, error code: ' .. result.error_code
    end
    return true, result.data
end

function smbus:ctor(ref_chip)
    self.ref_chip = ref_chip
end

return smbus
