/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(c) 2010-2018 Intel Corporation
 */

#include "ifpga_feature_dev.h"

static int fme_err_get_errors(struct ifpga_fme_hw *fme, u64 *val)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
						  FME_FEATURE_ID_GLOBAL_ERR);
	struct feature_fme_error0 fme_error0;

	fme_error0.csr = readq(&fme_err->fme_err);
	*val = fme_error0.csr;

	return 0;
}

static int fme_err_get_first_error(struct ifpga_fme_hw *fme, u64 *val)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
						  FME_FEATURE_ID_GLOBAL_ERR);
	struct feature_fme_first_error fme_first_err;

	fme_first_err.csr = readq(&fme_err->fme_first_err);
	*val = fme_first_err.err_reg_status;

	return 0;
}

static int fme_err_get_next_error(struct ifpga_fme_hw *fme, u64 *val)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
						  FME_FEATURE_ID_GLOBAL_ERR);
	struct feature_fme_next_error fme_next_err;

	fme_next_err.csr = readq(&fme_err->fme_next_err);
	*val = fme_next_err.err_reg_status;

	return 0;
}

static int fme_err_set_clear(struct ifpga_fme_hw *fme, u64 val)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
						  FME_FEATURE_ID_GLOBAL_ERR);

	spinlock_lock(&fme->lock);

	writeq(val, &fme_err->fme_err);

	spinlock_unlock(&fme->lock);

	return 0;
}

static int fme_err_get_revision(struct ifpga_fme_hw *fme, u64 *val)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
						  FME_FEATURE_ID_GLOBAL_ERR);
	struct feature_header header;

	header.csr = readq(&fme_err->header);
	*val = header.revision;

	return 0;
}

static int fme_err_get_pcie0_errors(struct ifpga_fme_hw *fme, u64 *val)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
						  FME_FEATURE_ID_GLOBAL_ERR);
	struct feature_fme_pcie0_error pcie0_err;

	pcie0_err.csr = readq(&fme_err->pcie0_err);
	*val = pcie0_err.csr;

	return 0;
}

static int fme_err_set_pcie0_errors(struct ifpga_fme_hw *fme, u64 val)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
						  FME_FEATURE_ID_GLOBAL_ERR);
	struct feature_fme_pcie0_error pcie0_err;
	int ret = 0;

	spinlock_lock(&fme->lock);
	writeq(FME_PCIE0_ERROR_MASK, &fme_err->pcie0_err_mask);

	pcie0_err.csr = readq(&fme_err->pcie0_err);
	if (val != pcie0_err.csr)
		ret = -EBUSY;
	else
		writeq(pcie0_err.csr & FME_PCIE0_ERROR_MASK,
		       &fme_err->pcie0_err);

	writeq(0UL, &fme_err->pcie0_err_mask);
	spinlock_unlock(&fme->lock);

	return ret;
}

static int fme_err_get_pcie1_errors(struct ifpga_fme_hw *fme, u64 *val)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
						  FME_FEATURE_ID_GLOBAL_ERR);
	struct feature_fme_pcie1_error pcie1_err;

	pcie1_err.csr = readq(&fme_err->pcie1_err);
	*val = pcie1_err.csr;

	return 0;
}

static int fme_err_set_pcie1_errors(struct ifpga_fme_hw *fme, u64 val)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
						  FME_FEATURE_ID_GLOBAL_ERR);
	struct feature_fme_pcie1_error pcie1_err;
	int ret = 0;

	spinlock_lock(&fme->lock);
	writeq(FME_PCIE1_ERROR_MASK, &fme_err->pcie1_err_mask);

	pcie1_err.csr = readq(&fme_err->pcie1_err);
	if (val != pcie1_err.csr)
		ret = -EBUSY;
	else
		writeq(pcie1_err.csr & FME_PCIE1_ERROR_MASK,
		       &fme_err->pcie1_err);

	writeq(0UL, &fme_err->pcie1_err_mask);
	spinlock_unlock(&fme->lock);

	return ret;
}

static int fme_err_get_nonfatal_errors(struct ifpga_fme_hw *fme, u64 *val)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
						  FME_FEATURE_ID_GLOBAL_ERR);
	struct feature_fme_ras_nonfaterror ras_nonfaterr;

	ras_nonfaterr.csr = readq(&fme_err->ras_nonfaterr);
	*val = ras_nonfaterr.csr;

	return 0;
}

static int fme_err_get_catfatal_errors(struct ifpga_fme_hw *fme, u64 *val)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
						  FME_FEATURE_ID_GLOBAL_ERR);
	struct feature_fme_ras_catfaterror ras_catfaterr;

	ras_catfaterr.csr = readq(&fme_err->ras_catfaterr);
	*val = ras_catfaterr.csr;

	return 0;
}

static int fme_err_get_inject_errors(struct ifpga_fme_hw *fme, u64 *val)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
						  FME_FEATURE_ID_GLOBAL_ERR);
	struct feature_fme_ras_error_inj ras_error_inj;

	ras_error_inj.csr = readq(&fme_err->ras_error_inj);
	*val = ras_error_inj.csr & FME_RAS_ERROR_INJ_MASK;

	return 0;
}

static int fme_err_set_inject_errors(struct ifpga_fme_hw *fme, u64 val)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
					      FME_FEATURE_ID_GLOBAL_ERR);
	struct feature_fme_ras_error_inj ras_error_inj;

	spinlock_lock(&fme->lock);
	ras_error_inj.csr = readq(&fme_err->ras_error_inj);

	if (val <= FME_RAS_ERROR_INJ_MASK) {
		ras_error_inj.csr = val;
	} else {
		spinlock_unlock(&fme->lock);
		return -EINVAL;
	}

	writeq(ras_error_inj.csr, &fme_err->ras_error_inj);
	spinlock_unlock(&fme->lock);

	return 0;
}

static void fme_error_enable(struct ifpga_fme_hw *fme)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
						  FME_FEATURE_ID_GLOBAL_ERR);

	writeq(FME_ERROR0_MASK_DEFAULT, &fme_err->fme_err_mask);
	writeq(0UL, &fme_err->pcie0_err_mask);
	writeq(0UL, &fme_err->pcie1_err_mask);
	writeq(0UL, &fme_err->ras_nonfat_mask);
	writeq(0UL, &fme_err->ras_catfat_mask);
}

static int fme_global_error_init(struct ifpga_feature *feature)
{
	struct ifpga_fme_hw *fme = feature->parent;

	fme_error_enable(fme);

	if (feature->ctx_num)
		fme->capability |= FPGA_FME_CAP_ERR_IRQ;

	return 0;
}

static void fme_global_error_uinit(struct ifpga_feature *feature)
{
	UNUSED(feature);
}

static int fme_err_check_seu(struct feature_fme_err *fme_err)
{
	struct feature_fme_error_capability error_cap;

	error_cap.csr = readq(&fme_err->fme_err_capability);

	return error_cap.seu_support ? 1 : 0;
}

static int fme_err_get_seu_emr(struct ifpga_fme_hw *fme,
		u64 *val, bool high)
{
	struct feature_fme_err *fme_err
		= get_fme_feature_ioaddr_by_index(fme,
				FME_FEATURE_ID_GLOBAL_ERR);

	if (!fme_err_check_seu(fme_err))
		return -ENODEV;

	if (high)
		*val = readq(&fme_err->seu_emr_h);
	else
		*val = readq(&fme_err->seu_emr_l);

	return 0;
}

static int fme_err_fme_err_get_prop(struct ifpga_feature *feature,
				    struct feature_prop *prop)
{
	struct ifpga_fme_hw *fme = feature->parent;
	u16 id = GET_FIELD(PROP_ID, prop->prop_id);

	switch (id) {
	case 0x1: /* ERRORS */
		return fme_err_get_errors(fme, &prop->data);
	case 0x2: /* FIRST_ERROR */
		return fme_err_get_first_error(fme, &prop->data);
	case 0x3: /* NEXT_ERROR */
		return fme_err_get_next_error(fme, &prop->data);
	case 0x5: /* SEU EMR LOW */
		return fme_err_get_seu_emr(fme, &prop->data, 0);
	case 0x6: /* SEU EMR HIGH */
		return fme_err_get_seu_emr(fme, &prop->data, 1);
	}

	return -ENOENT;
}

static int fme_err_root_get_prop(struct ifpga_feature *feature,
				 struct feature_prop *prop)
{
	struct ifpga_fme_hw *fme = feature->parent;
	u16 id = GET_FIELD(PROP_ID, prop->prop_id);

	switch (id) {
	case 0x5: /* REVISION */
		return fme_err_get_revision(fme, &prop->data);
	case 0x6: /* PCIE0_ERRORS */
		return fme_err_get_pcie0_errors(fme, &prop->data);
	case 0x7: /* PCIE1_ERRORS */
		return fme_err_get_pcie1_errors(fme, &prop->data);
	case 0x8: /* NONFATAL_ERRORS */
		return fme_err_get_nonfatal_errors(fme, &prop->data);
	case 0x9: /* CATFATAL_ERRORS */
		return fme_err_get_catfatal_errors(fme, &prop->data);
	case 0xa: /* INJECT_ERRORS */
		return fme_err_get_inject_errors(fme, &prop->data);
	case 0xb: /* REVISION*/
		return fme_err_get_revision(fme, &prop->data);
	}

	return -ENOENT;
}

static int fme_global_error_get_prop(struct ifpga_feature *feature,
				     struct feature_prop *prop)
{
	u8 top = GET_FIELD(PROP_TOP, prop->prop_id);
	u8 sub = GET_FIELD(PROP_SUB, prop->prop_id);

	/* PROP_SUB is never used */
	if (sub != PROP_SUB_UNUSED)
		return -ENOENT;

	switch (top) {
	case ERR_PROP_TOP_FME_ERR:
		return fme_err_fme_err_get_prop(feature, prop);
	case ERR_PROP_TOP_UNUSED:
		return fme_err_root_get_prop(feature, prop);
	}

	return -ENOENT;
}

static int fme_err_fme_err_set_prop(struct ifpga_feature *feature,
				    struct feature_prop *prop)
{
	struct ifpga_fme_hw *fme = feature->parent;
	u16 id = GET_FIELD(PROP_ID, prop->prop_id);

	switch (id) {
	case 0x4: /* CLEAR */
		return fme_err_set_clear(fme, prop->data);
	}

	return -ENOENT;
}

static int fme_err_root_set_prop(struct ifpga_feature *feature,
				 struct feature_prop *prop)
{
	struct ifpga_fme_hw *fme = feature->parent;
	u16 id = GET_FIELD(PROP_ID, prop->prop_id);

	switch (id) {
	case 0x6: /* PCIE0_ERRORS */
		return fme_err_set_pcie0_errors(fme, prop->data);
	case 0x7: /* PCIE1_ERRORS */
		return fme_err_set_pcie1_errors(fme, prop->data);
	case 0xa: /* INJECT_ERRORS */
		return fme_err_set_inject_errors(fme, prop->data);
	}

	return -ENOENT;
}

static int fme_global_error_set_prop(struct ifpga_feature *feature,
				     struct feature_prop *prop)
{
	u8 top = GET_FIELD(PROP_TOP, prop->prop_id);
	u8 sub = GET_FIELD(PROP_SUB, prop->prop_id);

	/* PROP_SUB is never used */
	if (sub != PROP_SUB_UNUSED)
		return -ENOENT;

	switch (top) {
	case ERR_PROP_TOP_FME_ERR:
		return fme_err_fme_err_set_prop(feature, prop);
	case ERR_PROP_TOP_UNUSED:
		return fme_err_root_set_prop(feature, prop);
	}

	return -ENOENT;
}

static int fme_global_err_set_irq(struct ifpga_feature *feature, void *irq_set)
{
	struct fpga_fme_err_irq_set *err_irq_set = irq_set;
	struct ifpga_fme_hw *fme;
	int ret;

	fme = (struct ifpga_fme_hw *)feature->parent;

	if (!(fme->capability & FPGA_FME_CAP_ERR_IRQ))
		return -ENODEV;

	spinlock_lock(&fme->lock);
	ret = fpga_msix_set_block(feature, 0, 1, &err_irq_set->evtfd);
	spinlock_unlock(&fme->lock);

	return ret;
}

struct ifpga_feature_ops fme_global_err_ops = {
	.init = fme_global_error_init,
	.uinit = fme_global_error_uinit,
	.get_prop = fme_global_error_get_prop,
	.set_prop = fme_global_error_set_prop,
	.set_irq = fme_global_err_set_irq,
};
