/*
 * harness_mask.c — unit-level proof of the DF-4947 mask-vs-index defect in
 * hw_translate_dcn10.c id_to_offset (sys/dev/drm/amd/display/dc/gpio/dcn10/).
 *
 * id_to_offset() below is a VERBATIM copy of the GPIO_ID_DDC_DATA and
 * GPIO_ID_DDC_CLOCK branches (hw_translate_dcn10.c:210-274). For
 * en = GPIO_DDC_LINE_I2C_PAD it pairs REG(DC_GPIO_I2CPAD_A) with the
 * DDC6DATA mask 0x100 — but DC_GPIO_I2CPAD_A's SDA field is bit 1 (0x2) per
 * dcn_1_0_sh_mask.h:41219, and the factory's own ddc_i2c_data_regs entry
 * (ddc_regs.h:69-71) uses that SDA_A_MASK for this very pin. The two tables
 * disagree: the translate table would drive the wrong GPIO bit.
 *
 * The sole in-tree consumer of id_to_offset is dal_gpio_get_pin_info
 * (gpio_base.c:140-147), which currently has no callers — the defect is
 * latent (Info severity), but deterministic to demonstrate.
 */
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

enum gpio_id {
	GPIO_ID_DDC_DATA, GPIO_ID_DDC_CLOCK,
};
struct gpio_pin_info {
	uint32_t offset, offset_y, offset_en, offset_mask;
	uint32_t mask, mask_y, mask_en, mask_mask;
};
enum gpio_ddc_line { GPIO_DDC_LINE_DDC1 = 0, GPIO_DDC_LINE_DDC2,
	GPIO_DDC_LINE_DDC3, GPIO_DDC_LINE_DDC4, GPIO_DDC_LINE_DDC5,
	GPIO_DDC_LINE_DDC6, GPIO_DDC_LINE_DDC_VGA,
	GPIO_DDC_LINE_VIP_PAD, GPIO_DDC_LINE_I2C_PAD = GPIO_DDC_LINE_VIP_PAD,
	GPIO_DDC_LINE_COUNT };

#define BASE_INNER(seg)	DCE_BASE__INST0_SEG ## seg
#define BASE(seg)	BASE_INNER(seg)
#define REG(reg_name)	BASE(mm ## reg_name ## _BASE_IDX) + mm ## reg_name
#define DCE_BASE__INST0_SEG2 0x34C0
#define mmDC_GPIO_DDC1_A	0x28d1
#define mmDC_GPIO_DDC1_A_BASE_IDX	2
#define mmDC_GPIO_DDC2_A	0x28d5
#define mmDC_GPIO_DDC2_A_BASE_IDX	2
#define mmDC_GPIO_DDC3_A	0x28d9
#define mmDC_GPIO_DDC3_A_BASE_IDX	2
#define mmDC_GPIO_DDC4_A	0x28dd
#define mmDC_GPIO_DDC4_A_BASE_IDX	2
#define mmDC_GPIO_DDC5_A	0x28e1
#define mmDC_GPIO_DDC5_A_BASE_IDX	2
#define mmDC_GPIO_DDC6_A	0x28e5
#define mmDC_GPIO_DDC6_A_BASE_IDX	2
#define mmDC_GPIO_DDCVGA_A	0x28e9
#define mmDC_GPIO_DDCVGA_A_BASE_IDX	2
#define mmDC_GPIO_I2CPAD_A	0x2901
#define mmDC_GPIO_I2CPAD_A_BASE_IDX	2
#define DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK 0x00000100L
#define DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK  0x00000001L

/* dcn_1_0_sh_mask.h:41216-41219 — the real I2C pad field positions */
#define DC_GPIO_I2CPAD_A__DC_GPIO_SCL_A_MASK  0x00000001L
#define DC_GPIO_I2CPAD_A__DC_GPIO_SDA_A_MASK  0x00000002L
/* dcn_1_0_sh_mask.h:40628 etc — DDC1-6/VGA DATA fields all sit at bit 8 */
#define DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK  0x00000100L
#define DC_GPIO_DDCVGA_A__DC_GPIO_DDCVGADATA_A_MASK 0x00000100L

static bool id_to_offset_ddc(enum gpio_id id, uint32_t en,
	struct gpio_pin_info *info)
{
	switch (id) {
	/* ---- VERBATIM hw_translate_dcn10.c:210-242 ---- */
	case GPIO_ID_DDC_DATA:
		info->mask = DC_GPIO_DDC6_A__DC_GPIO_DDC6DATA_A_MASK;
		switch (en) {
		case GPIO_DDC_LINE_DDC1: info->offset = REG(DC_GPIO_DDC1_A); break;
		case GPIO_DDC_LINE_DDC2: info->offset = REG(DC_GPIO_DDC2_A); break;
		case GPIO_DDC_LINE_DDC3: info->offset = REG(DC_GPIO_DDC3_A); break;
		case GPIO_DDC_LINE_DDC4: info->offset = REG(DC_GPIO_DDC4_A); break;
		case GPIO_DDC_LINE_DDC5: info->offset = REG(DC_GPIO_DDC5_A); break;
		case GPIO_DDC_LINE_DDC6: info->offset = REG(DC_GPIO_DDC6_A); break;
		case GPIO_DDC_LINE_DDC_VGA: info->offset = REG(DC_GPIO_DDCVGA_A); break;
		case GPIO_DDC_LINE_I2C_PAD: info->offset = REG(DC_GPIO_I2CPAD_A); break;
		default: return false;
		}
		return true;
	/* ---- VERBATIM hw_translate_dcn10.c:243-274 ---- */
	case GPIO_ID_DDC_CLOCK:
		info->mask = DC_GPIO_DDC6_A__DC_GPIO_DDC6CLK_A_MASK;
		switch (en) {
		case GPIO_DDC_LINE_DDC1: info->offset = REG(DC_GPIO_DDC1_A); break;
		case GPIO_DDC_LINE_DDC2: info->offset = REG(DC_GPIO_DDC2_A); break;
		case GPIO_DDC_LINE_DDC3: info->offset = REG(DC_GPIO_DDC3_A); break;
		case GPIO_DDC_LINE_DDC4: info->offset = REG(DC_GPIO_DDC4_A); break;
		case GPIO_DDC_LINE_DDC5: info->offset = REG(DC_GPIO_DDC5_A); break;
		case GPIO_DDC_LINE_DDC6: info->offset = REG(DC_GPIO_DDC6_A); break;
		case GPIO_DDC_LINE_DDC_VGA: info->offset = REG(DC_GPIO_DDCVGA_A); break;
		case GPIO_DDC_LINE_I2C_PAD: info->offset = REG(DC_GPIO_I2CPAD_A); break;
		default: return false;
		}
		return true;
	}
	return false;
}

static void show(const char *tag, enum gpio_id id, uint32_t en,
	uint32_t true_mask)
{
	struct gpio_pin_info info;
	bool r;
	memset(&info, 0, sizeof(info));
	r = id_to_offset_ddc(id, en, &info);
	printf("%-34s = %-5s offset=0x%04x mask=0x%-4x real field mask=0x%-4x %s\n",
		tag, r ? "true" : "false", info.offset, info.mask, true_mask,
		(r && info.mask == true_mask) ? "OK" : "WRONG BIT (DF-4947)");
}

int main(void)
{
	int bad = 0;
	printf("== id_to_offset DDC mask-vs-register-field audit (DF-4947) ==\n");
	printf("DDC1-6/VGA DATA/CLK fields are laid out identically to DDC6\n");
	printf("(bit8/bit0), so the hardwired DDC6 masks are correct there.\n");
	printf("DC_GPIO_I2CPAD_A is NOT: SDA=bit1(0x2), SCL=bit0(0x1).\n\n");
	show("id_to_offset(DDC_DATA, DDC1)", GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC1, DC_GPIO_DDC1_A__DC_GPIO_DDC1DATA_A_MASK);
	show("id_to_offset(DDC_DATA, DDC_VGA)", GPIO_ID_DDC_DATA, GPIO_DDC_LINE_DDC_VGA, DC_GPIO_DDCVGA_A__DC_GPIO_DDCVGADATA_A_MASK);
	show("id_to_offset(DDC_CLOCK, I2C_PAD)", GPIO_ID_DDC_CLOCK, GPIO_DDC_LINE_I2C_PAD, DC_GPIO_I2CPAD_A__DC_GPIO_SCL_A_MASK);
	show("id_to_offset(DDC_DATA, I2C_PAD)", GPIO_ID_DDC_DATA, GPIO_DDC_LINE_I2C_PAD, DC_GPIO_I2CPAD_A__DC_GPIO_SDA_A_MASK);

	/* count the wrong one */
	{
		struct gpio_pin_info info;
		memset(&info, 0, sizeof(info));
		if (id_to_offset_ddc(GPIO_ID_DDC_DATA, GPIO_DDC_LINE_I2C_PAD, &info)
		    && info.mask != DC_GPIO_I2CPAD_A__DC_GPIO_SDA_A_MASK)
			bad++;
	}
	printf("\n%d wrong-mask lines (DDC_DATA+I2C_PAD is the DF-4947 defect)\n", bad);
	return (bad == 1) ? 0 : 1;
}
