// SPDX-License-Identifier: GPL-2.0+
/*
 * V3MSK board CPLD access support
 *
 * Copyright (C) 2019 Renesas Electronics Corporation
 * Copyright (C) 2019 Cogent Embedded, Inc.
 *
 */

#include <common.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <dm.h>
#include <errno.h>
#include <linux/err.h>
#include <sysreset.h>
#include <linux/delay.h>
#include <command.h>

#define CPLD_ADDR_PRODUCT_L		0x000 /* R */
#define CPLD_ADDR_PRODUCT_H		0x001 /* R */
#define CPLD_ADDR_CPLD_VERSION_D	0x002 /* R */
#define CPLD_ADDR_CPLD_VERSION_Y	0x003 /* R */
#define CPLD_ADDR_MODE_SET_L		0x004 /* R/W */
#define CPLD_ADDR_MODE_SET_H		0x005 /* R/W */
#define CPLD_ADDR_MODE_APPLIED_L	0x006 /* R */
#define CPLD_ADDR_MODE_APPLIED_H	0x007 /* R */
#define CPLD_ADDR_DIPSW			0x008 /* R */
#define CPLD_ADDR_RESET			0x00A /* R/W */
#define CPLD_ADDR_POWER_CFG		0x00B /* R/W */
#define CPLD_ADDR_PERI_CFG1		0x00C /* R/W */
#define CPLD_ADDR_PERI_CFG2		0x00D /* R/W */
#define CPLD_ADDR_LEDS			0x00E /* R/W */
#define CPLD_ADDR_PCB_VERSION		0x300 /* R */
#define CPLD_ADDR_SOC_VERSION		0x301 /* R */
#define CPLD_ADDR_PCB_SN_L		0x302 /* R */
#define CPLD_ADDR_PCB_SN_H		0x303 /* R */

#define MDIO_DELAY			10 /* microseconds */

#define CPLD_MAX_GPIOS			2

struct renesas_v3msk_sysreset_priv {
	struct gpio_desc	miso;
	struct gpio_desc	mosi;
	struct gpio_desc	mdc;
	struct gpio_desc	enablez;
	/*
	 * V3MSK Videobox Mini board has CANFD PHY connected
	 * we must shutdown this chip to use bb pins
	 */
	struct gpio_desc	gpios[CPLD_MAX_GPIOS];
};

static void mdio_bb_active_mdio(struct renesas_v3msk_sysreset_priv *priv)
{
	dm_gpio_set_dir_flags(&priv->mosi, GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);
}

static void mdio_bb_tristate_mdio(struct renesas_v3msk_sysreset_priv *priv)
{
	dm_gpio_set_dir_flags(&priv->mosi, GPIOD_IS_IN);
}

static void mdio_bb_set_mdio(struct renesas_v3msk_sysreset_priv *priv, int val)
{
	dm_gpio_set_value(&priv->mosi, val);
}

static int mdio_bb_get_mdio(struct renesas_v3msk_sysreset_priv *priv)
{
	return dm_gpio_get_value(&priv->miso);
}

static void mdio_bb_set_mdc(struct renesas_v3msk_sysreset_priv *priv, int val)
{
	dm_gpio_set_value(&priv->mdc, val);
}

static void mdio_bb_delay(void)
{
	udelay(MDIO_DELAY);
}

/* Send the preamble, address, and register (common to read and write) */
static void mdio_bb_pre(struct renesas_v3msk_sysreset_priv *priv,
			u8 op, u8 addr, u8 reg)
{
	int i;

	/* 32-bit preamble */
	mdio_bb_active_mdio(priv);
	mdio_bb_set_mdio(priv, 1);
	for (i = 0; i < 32; i++) {
		mdio_bb_set_mdc(priv, 0);
		mdio_bb_delay();
		mdio_bb_set_mdc(priv, 1);
		mdio_bb_delay();
	}
	/* send the ST (2-bits of '01') */
	mdio_bb_set_mdio(priv, 0);
	mdio_bb_set_mdc(priv, 0);
	mdio_bb_delay();
	mdio_bb_set_mdc(priv, 1);
	mdio_bb_delay();
	mdio_bb_set_mdio(priv, 1);
	mdio_bb_set_mdc(priv, 0);
	mdio_bb_delay();
	mdio_bb_set_mdc(priv, 1);
	mdio_bb_delay();
	/* send the OP (2-bits of Opcode: '10'-read, '01'-write) */
	mdio_bb_set_mdio(priv, op >> 1);
	mdio_bb_set_mdc(priv, 0);
	mdio_bb_delay();
	mdio_bb_set_mdc(priv, 1);
	mdio_bb_delay();
	mdio_bb_set_mdio(priv, op & 1);
	mdio_bb_set_mdc(priv, 0);
	mdio_bb_delay();
	mdio_bb_set_mdc(priv, 1);
	mdio_bb_delay();
	/* send the PA5 (5-bits of PHY address) */
	for (i = 0; i < 5; i++) {
		mdio_bb_set_mdio(priv, addr & 0x10); /* MSB first */
		mdio_bb_set_mdc(priv, 0);
		mdio_bb_delay();
		mdio_bb_set_mdc(priv, 1);
		mdio_bb_delay();
		addr <<= 1;
	}
	/* send the RA5 (5-bits of register address) */
	for (i = 0; i < 5; i++) {
		mdio_bb_set_mdio(priv, reg & 0x10); /* MSB first */
		mdio_bb_set_mdc(priv, 0);
		mdio_bb_delay();
		mdio_bb_set_mdc(priv, 1);
		mdio_bb_delay();
		reg <<= 1;
	}
}

static int mdio_bb_read(struct renesas_v3msk_sysreset_priv *priv,
			u8 addr, u8 reg)
{
	int i;
	u16 data = 0;

	mdio_bb_pre(priv, 2, addr, reg);
	/* tri-state MDIO */
	mdio_bb_tristate_mdio(priv);
	/* read TA (2-bits of turn-around, last bit must be '0') */
	mdio_bb_set_mdc(priv, 0);
	mdio_bb_delay();
	mdio_bb_set_mdc(priv, 1);
	mdio_bb_delay();
	mdio_bb_set_mdc(priv, 0);
	mdio_bb_delay();
	mdio_bb_set_mdc(priv, 1);
	mdio_bb_delay();
	/* check the turnaround bit: the PHY should drive line to zero */
	if (mdio_bb_get_mdio(priv) != 0) {
		printf("PHY didn't drive TA low\n");
		for (i = 0; i < 32; i++) {
			mdio_bb_set_mdc(priv, 0);
			mdio_bb_delay();
			mdio_bb_set_mdc(priv, 1);
			mdio_bb_delay();
		}
		/* There is no PHY, set value to 0xFFFF */
		return 0xFFFF;
	}
	mdio_bb_set_mdc(priv, 0);
	mdio_bb_delay();
	/* read 16-bits of data */
	for (i = 0; i < 16; i++) {
		mdio_bb_set_mdc(priv, 1);
		mdio_bb_delay();
		data <<= 1;
		data |= mdio_bb_get_mdio(priv);
		mdio_bb_set_mdc(priv, 0);
		mdio_bb_delay();
	}

	mdio_bb_set_mdc(priv, 1);
	mdio_bb_delay();
	mdio_bb_set_mdc(priv, 0);
	mdio_bb_delay();
	mdio_bb_set_mdc(priv, 1);
	mdio_bb_delay();

	debug("cpld_read(0x%x) @ 0x%x = 0x%04x\n", reg, addr, data);

	return data;
}

static void mdio_bb_write(struct renesas_v3msk_sysreset_priv *priv,
			  u8 addr, u8 reg, u16 val)
{
	int i;

	mdio_bb_pre(priv, 1, addr, reg);
	/* send the TA (2-bits of turn-around '10') */
	mdio_bb_set_mdio(priv, 1);
	mdio_bb_set_mdc(priv, 0);
	mdio_bb_delay();
	mdio_bb_set_mdc(priv, 1);
	mdio_bb_delay();
	mdio_bb_set_mdio(priv, 0);
	mdio_bb_set_mdc(priv, 0);
	mdio_bb_delay();
	mdio_bb_set_mdc(priv, 1);
	mdio_bb_delay();
	/* write 16-bits of data */
	for (i = 0; i < 16; i++) {
		mdio_bb_set_mdio(priv, val & 0x8000); /* MSB first */
		mdio_bb_set_mdc(priv, 0);
		mdio_bb_delay();
		mdio_bb_set_mdc(priv, 1);
		mdio_bb_delay();
		val <<= 1;
	}
	/* tri-state MDIO */
	mdio_bb_tristate_mdio(priv);
	mdio_bb_set_mdc(priv, 0);
	mdio_bb_delay();
	mdio_bb_set_mdc(priv, 1);
	mdio_bb_delay();
}

static u16 cpld_read(struct udevice *dev, u16 addr)
{
	struct renesas_v3msk_sysreset_priv *priv = dev_get_priv(dev);

	/* random flash reads require 2 reads: first read is unreliable */
	if (addr >= CPLD_ADDR_PCB_VERSION)
		mdio_bb_read(priv, addr >> 5, addr & 0x1f);

	return mdio_bb_read(priv, addr >> 5, addr & 0x1f);
}

static void cpld_write(struct udevice *dev, u16 addr, u16 data)
{
	struct renesas_v3msk_sysreset_priv *priv = dev_get_priv(dev);

	mdio_bb_write(priv, addr >> 5, addr & 0x1f, data);
}

static int do_cpld(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
{
	struct udevice *dev;
	u16 addr, val;
	int ret;

	ret = uclass_get_device_by_driver(UCLASS_SYSRESET,
					  DM_DRIVER_GET(sysreset_renesas_v3msk),
					  &dev);
	if (ret)
		return ret;

	if (argc == 2 && strcmp(argv[1], "info") == 0) {
		printf("Product:                0x%08x\n",
		       (cpld_read(dev, CPLD_ADDR_PRODUCT_H) << 16) |
			cpld_read(dev, CPLD_ADDR_PRODUCT_L));
		printf("CPLD version:           0x%08x\n",
		       (cpld_read(dev, CPLD_ADDR_CPLD_VERSION_Y) << 16) |
			cpld_read(dev, CPLD_ADDR_CPLD_VERSION_D));
		printf("Mode setting (MD0..26): 0x%08x\n",
		       (cpld_read(dev, CPLD_ADDR_MODE_APPLIED_H) << 16) |
			cpld_read(dev, CPLD_ADDR_MODE_APPLIED_L));
		printf("DIPSW (SW4, SW5):       0x%02x, 0x%x\n",
		       (cpld_read(dev, CPLD_ADDR_DIPSW) & 0xff) ^ 0xff,
		       (cpld_read(dev, CPLD_ADDR_DIPSW) >> 8) ^ 0xf);
		printf("Power config:           0x%08x\n",
		       cpld_read(dev, CPLD_ADDR_POWER_CFG));
		printf("Periferals config:      0x%08x\n",
		       (cpld_read(dev, CPLD_ADDR_PERI_CFG2) << 16) |
			cpld_read(dev, CPLD_ADDR_PERI_CFG1));
		printf("PCB version:            %d.%d\n",
		       cpld_read(dev, CPLD_ADDR_PCB_VERSION) >> 8,
		       cpld_read(dev, CPLD_ADDR_PCB_VERSION) & 0xff);
		printf("SOC version:            %d.%d\n",
		       cpld_read(dev, CPLD_ADDR_SOC_VERSION) >> 8,
		       cpld_read(dev, CPLD_ADDR_SOC_VERSION) & 0xff);
		printf("PCB S/N:                %d\n",
		       (cpld_read(dev, CPLD_ADDR_PCB_SN_H) << 16) |
			cpld_read(dev, CPLD_ADDR_PCB_SN_L));
		return 0;
	}

	if (argc < 3)
		return CMD_RET_USAGE;

	addr = simple_strtoul(argv[2], NULL, 16);
	if (!(addr >= CPLD_ADDR_PRODUCT_L && addr <= CPLD_ADDR_LEDS)) {
		printf("cpld invalid addr\n");
		return CMD_RET_USAGE;
	}

	if (argc == 3 && strcmp(argv[1], "read") == 0) {
		printf("0x%x\n", cpld_read(dev, addr));
	} else if (argc == 4 && strcmp(argv[1], "write") == 0) {
		val = simple_strtoul(argv[3], NULL, 16);
		cpld_write(dev, addr, val);
	}

	return 0;
}

U_BOOT_CMD(cpld, 4, 1, do_cpld,
	   "CPLD access",
	   "info\n"
	   "cpld read addr\n"
	   "cpld write addr val\n"
);

static int renesas_v3msk_sysreset_request(struct udevice *dev, enum sysreset_t type)
{
	cpld_write(dev, CPLD_ADDR_RESET, 1);

	return -EINPROGRESS;
}

static int renesas_v3msk_sysreset_probe(struct udevice *dev)
{
	struct renesas_v3msk_sysreset_priv *priv = dev_get_priv(dev);

	if (gpio_request_by_name(dev, "gpio-miso", 0, &priv->miso,
				 GPIOD_IS_IN))
		return -EINVAL;

	if (gpio_request_by_name(dev, "gpio-mosi", 0, &priv->mosi,
				 GPIOD_IS_OUT))
		return -EINVAL;

	if (gpio_request_by_name(dev, "gpio-mdc", 0, &priv->mdc,
				 GPIOD_IS_OUT))
		return -EINVAL;

	if (gpio_request_by_name(dev, "gpio-enablez", 0, &priv->enablez,
				 GPIOD_IS_OUT))
		return -EINVAL;

	/* V3MSK Videobox Mini board has CANFD PHY connected
	 * we must shutdown this chip to use bb pins
	 */
	gpio_request_list_by_name(dev, "gpios", priv->gpios, CPLD_MAX_GPIOS,
				  GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE);

	return 0;
}

static struct sysreset_ops renesas_v3msk_sysreset = {
	.request	= renesas_v3msk_sysreset_request,
};

static const struct udevice_id renesas_v3msk_sysreset_ids[] = {
	{ .compatible = "renesas,v3msk-cpld" },
	{ /* sentinel */ }
};

U_BOOT_DRIVER(sysreset_renesas_v3msk) = {
	.name		= "renesas_v3msk_sysreset",
	.id		= UCLASS_SYSRESET,
	.ops		= &renesas_v3msk_sysreset,
	.probe		= renesas_v3msk_sysreset_probe,
	.of_match	= renesas_v3msk_sysreset_ids,
	.priv_auto	= sizeof(struct renesas_v3msk_sysreset_priv),
};
