/*
 * emmc_adapter.c
 *
 * linux emmc driver implement.
 *
 * Copyright (c) 2020-2021 Huawei Device Co., Ltd.
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 */

#include <securec.h>
#include <linux/mmc/card.h>
#include <linux/mmc/host.h>
#include "device_resource_if.h"
#include "hdf_log.h"
#include "emmc_if.h"
#include "mmc_corex.h"
#include "mmc_emmc.h"

#define HDF_LOG_TAG emmc_adapter_c

struct mmc_host *GetMmcHost(int32_t slot);

int32_t LinuxEmmcGetCid(struct EmmcDevice *dev, uint8_t *cid, uint32_t size)
{
    struct mmc_host *mmcHost = NULL;
    struct MmcCntlr *cntlr = NULL;

    if (dev == NULL || dev->mmc.cntlr == NULL) {
        HDF_LOGE("LinuxEmmcGetCid: dev or cntlr is null.");
        return HDF_ERR_INVALID_OBJECT;
    }
    if (cid == NULL || size < EMMC_CID_LEN) {
        HDF_LOGE("LinuxEmmcGetCid: cid is null or size is invalid.");
        return HDF_ERR_INVALID_PARAM;
    }

    cntlr = dev->mmc.cntlr;
    mmcHost = (struct mmc_host *)cntlr->priv;
    if (mmcHost == NULL) {
        HDF_LOGE("LinuxEmmcGetCid: mmcHost is NULL!");
        return HDF_ERR_NOT_SUPPORT;
    }
    if (mmcHost->card == NULL) {
        HDF_LOGE("LinuxEmmcGetCid: card is null.");
        return HDF_ERR_NOT_SUPPORT;
    }
    if (memcpy_s(cid, sizeof(uint8_t) * size, (uint8_t *)(mmcHost->card->raw_cid),
        sizeof(mmcHost->card->raw_cid)) != EOK) {
        HDF_LOGE("LinuxEmmcGetCid: memcpy_s fail!");
        return HDF_FAILURE;
    }
    return HDF_SUCCESS;
}

static struct EmmcDeviceOps g_emmcMethod = {
    .getCid = LinuxEmmcGetCid,
};

static void LinuxEmmcDeleteCntlr(struct MmcCntlr *cntlr)
{
    if (cntlr == NULL) {
        return;
    }

    if (cntlr->curDev != NULL) {
        MmcDeviceRemove(cntlr->curDev);
        OsalMemFree(cntlr->curDev);
    }
    MmcCntlrRemove(cntlr);
    OsalMemFree(cntlr);
}

static int32_t LinuxEmmcCntlrParse(struct MmcCntlr *cntlr, struct HdfDeviceObject *obj)
{
    const struct DeviceResourceNode *node = NULL;
    struct DeviceResourceIface *drsOps = NULL;
    int32_t ret;

    if (obj == NULL || cntlr == NULL) {
        HDF_LOGE("LinuxEmmcCntlrParse: input para is NULL.");
        return HDF_FAILURE;
    }

    node = obj->property;
    if (node == NULL) {
        HDF_LOGE("LinuxEmmcCntlrParse: drs node is NULL.");
        return HDF_FAILURE;
    }
    drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
    if (drsOps == NULL || drsOps->GetUint16 == NULL || drsOps->GetUint32 == NULL) {
        HDF_LOGE("LinuxEmmcCntlrParse: invalid drs ops fail!");
        return HDF_FAILURE;
    }

    ret = drsOps->GetUint16(node, "hostId", &(cntlr->index), 0);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("LinuxEmmcCntlrParse: read hostIndex fail!");
        return ret;
    }
    ret = drsOps->GetUint32(node, "devType", &(cntlr->devType), 0);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("LinuxEmmcCntlrParse: read devType fail!");
        return ret;
    }
    return HDF_SUCCESS;
}

static int32_t LinuxEmmcBind(struct HdfDeviceObject *obj)
{
    struct MmcCntlr *cntlr = NULL;
    int32_t ret;

    if (obj == NULL) {
        HDF_LOGE("LinuxEmmcBind: Fail, obj is NULL.");
        return HDF_ERR_INVALID_OBJECT;
    }

    cntlr = (struct MmcCntlr *)OsalMemCalloc(sizeof(struct MmcCntlr));
    if (cntlr == NULL) {
        HDF_LOGE("LinuxEmmcBind: no mem for MmcCntlr.");
        return HDF_ERR_MALLOC_FAIL;
    }

    cntlr->ops = NULL;
    cntlr->hdfDevObj = obj;
    obj->service = &cntlr->service;
    ret = LinuxEmmcCntlrParse(cntlr, obj);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("LinuxEmmcBind: cntlr parse fail.");
        goto _ERR;
    }
    cntlr->priv = (void *)GetMmcHost((int32_t)cntlr->index);

    ret = MmcCntlrAdd(cntlr, false);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("LinuxEmmcBind: cntlr add fail.");
        goto _ERR;
    }

    ret = MmcCntlrAllocDev(cntlr, (enum MmcDevType)cntlr->devType);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("LinuxEmmcBind: alloc dev fail.");
        goto _ERR;
    }
    MmcDeviceAddOps(cntlr->curDev, &g_emmcMethod);
    HDF_LOGD("LinuxEmmcBind: Success!");
    return HDF_SUCCESS;

_ERR:
    LinuxEmmcDeleteCntlr(cntlr);
    HDF_LOGE("LinuxEmmcBind: Fail!");
    return HDF_FAILURE;
}

static int32_t LinuxEmmcInit(struct HdfDeviceObject *obj)
{
    (void)obj;

    HDF_LOGD("LinuxEmmcInit: Success!");
    return HDF_SUCCESS;
}

static void LinuxEmmcRelease(struct HdfDeviceObject *obj)
{
    if (obj == NULL) {
        return;
    }
    LinuxEmmcDeleteCntlr((struct MmcCntlr *)obj->service);
}

struct HdfDriverEntry g_emmcDriverEntry = {
    .moduleVersion = 1,
    .Bind = LinuxEmmcBind,
    .Init = LinuxEmmcInit,
    .Release = LinuxEmmcRelease,
    .moduleName = "HDF_PLATFORM_EMMC",
};
HDF_INIT(g_emmcDriverEntry);