// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright 2022 HabanaLabs, Ltd.
 * All Rights Reserved.
 */

#include "habanalabs.h"

#define VCMD_CONTROL_OFFSET			0x40	/* SWREG16 */
#define VCMD_IRQ_STATUS_OFFSET			0x44	/* SWREG17 */

#define VCMD_IRQ_STATUS_ENDCMD_MASK		0x1
#define VCMD_IRQ_STATUS_BUSERR_MASK		0x2
#define VCMD_IRQ_STATUS_TIMEOUT_MASK		0x4
#define VCMD_IRQ_STATUS_CMDERR_MASK		0x8
#define VCMD_IRQ_STATUS_ABORT_MASK		0x10
#define VCMD_IRQ_STATUS_RESET_MASK		0x20

static void dec_print_abnrm_intr_source(struct hl_device *hdev, u32 irq_status)
{
	const char *format = "abnormal interrupt source:%s%s%s%s%s%s\n";
	char *intr_source[6] = {"Unknown", "", "", "", "", ""};
	int i = 0;

	if (!irq_status)
		return;

	if (irq_status & VCMD_IRQ_STATUS_ENDCMD_MASK)
		intr_source[i++] = " ENDCMD";
	if (irq_status & VCMD_IRQ_STATUS_BUSERR_MASK)
		intr_source[i++] = " BUSERR";
	if (irq_status & VCMD_IRQ_STATUS_TIMEOUT_MASK)
		intr_source[i++] = " TIMEOUT";
	if (irq_status & VCMD_IRQ_STATUS_CMDERR_MASK)
		intr_source[i++] = " CMDERR";
	if (irq_status & VCMD_IRQ_STATUS_ABORT_MASK)
		intr_source[i++] = " ABORT";
	if (irq_status & VCMD_IRQ_STATUS_RESET_MASK)
		intr_source[i++] = " RESET";

	dev_err(hdev->dev, format, intr_source[0], intr_source[1],
		intr_source[2], intr_source[3], intr_source[4], intr_source[5]);
}

static void dec_abnrm_intr_work(struct work_struct *work)
{
	struct hl_dec *dec = container_of(work, struct hl_dec, abnrm_intr_work);
	struct hl_device *hdev = dec->hdev;
	u32 irq_status, event_mask = 0;
	bool reset_required = false;

	irq_status = RREG32(dec->base_addr + VCMD_IRQ_STATUS_OFFSET);

	dev_err(hdev->dev, "Decoder abnormal interrupt %#x, core %d\n", irq_status, dec->core_id);

	dec_print_abnrm_intr_source(hdev, irq_status);

	/* Clear the interrupt */
	WREG32(dec->base_addr + VCMD_IRQ_STATUS_OFFSET, irq_status);

	/* Flush the interrupt clear */
	RREG32(dec->base_addr + VCMD_IRQ_STATUS_OFFSET);

	if (irq_status & VCMD_IRQ_STATUS_TIMEOUT_MASK) {
		reset_required = true;
		event_mask |= HL_NOTIFIER_EVENT_GENERAL_HW_ERR;
	}

	if (irq_status & VCMD_IRQ_STATUS_CMDERR_MASK)
		event_mask |= HL_NOTIFIER_EVENT_UNDEFINED_OPCODE;

	if (irq_status & (VCMD_IRQ_STATUS_ENDCMD_MASK |
				VCMD_IRQ_STATUS_BUSERR_MASK |
				VCMD_IRQ_STATUS_ABORT_MASK))
		event_mask |= HL_NOTIFIER_EVENT_USER_ENGINE_ERR;

	if (reset_required) {
		event_mask |= HL_NOTIFIER_EVENT_DEVICE_RESET;
		hl_device_cond_reset(hdev, 0, event_mask);
	} else if (event_mask) {
		hl_notifier_event_send_all(hdev, event_mask);
	}
}

void hl_dec_fini(struct hl_device *hdev)
{
	kfree(hdev->dec);
}

int hl_dec_init(struct hl_device *hdev)
{
	struct asic_fixed_properties *prop = &hdev->asic_prop;
	struct hl_dec *dec;
	int rc, j;

	/* if max core is 0, nothing to do*/
	if (!prop->max_dec)
		return 0;

	hdev->dec = kcalloc(prop->max_dec, sizeof(struct hl_dec), GFP_KERNEL);
	if (!hdev->dec)
		return -ENOMEM;

	for (j = 0 ; j < prop->max_dec ; j++) {
		dec = hdev->dec + j;

		dec->hdev = hdev;
		INIT_WORK(&dec->abnrm_intr_work, dec_abnrm_intr_work);
		dec->core_id = j;
		dec->base_addr = hdev->asic_funcs->get_dec_base_addr(hdev, j);
		if (!dec->base_addr) {
			dev_err(hdev->dev, "Invalid base address of decoder %d\n", j);
			rc = -EINVAL;
			goto err_dec_fini;
		}
	}

	return 0;

err_dec_fini:
	hl_dec_fini(hdev);

	return rc;
}

void hl_dec_ctx_fini(struct hl_ctx *ctx)
{
	struct hl_device *hdev = ctx->hdev;
	struct asic_fixed_properties *prop = &hdev->asic_prop;
	struct hl_dec *dec;
	int j;

	for (j = 0 ; j < prop->max_dec ; j++) {
		if (!!(prop->decoder_enabled_mask & BIT(j))) {
			dec = hdev->dec + j;
			/* Stop the decoder */
			WREG32(dec->base_addr + VCMD_CONTROL_OFFSET, 0);
		}
	}
}