// SPDX-License-Identifier: GPL-2.0
//
// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
// Author: Vignesh Raghavendra <vigneshr@ti.com>

#include <common.h>
#include <asm/io.h>
#include <dm.h>
#include <regmap.h>
#include <syscon.h>

#define FSS_SYSC_REG	0x4

#define HYPERBUS_CALIB_COUNT 25

struct am654_hbmc_priv {
	void __iomem *mmiobase;
	bool calibrated;
};

/* Calibrate by looking for "QRY" string within the CFI space */
static int am654_hyperbus_calibrate(struct udevice *dev)
{
	struct am654_hbmc_priv *priv = dev_get_priv(dev);
	int count = HYPERBUS_CALIB_COUNT;
	int pass_count = 0;
	u16 qry[3];

	if (priv->calibrated)
		return 0;

	writew(0xF0, priv->mmiobase);
	writew(0x98, priv->mmiobase + 0xaa);

	while (count--) {
		qry[0] = readw(priv->mmiobase + 0x20);
		qry[1] = readw(priv->mmiobase + 0x22);
		qry[2] = readw(priv->mmiobase + 0x24);

		if (qry[0] == 'Q' && qry[1] == 'R' && qry[2] == 'Y')
			pass_count++;
		else
			pass_count = 0;
		if (pass_count == 5)
			break;
	}
	writew(0xF0, priv->mmiobase);
	writew(0xFF, priv->mmiobase);

	return pass_count == 5;
}

static int am654_select_hbmc(struct udevice *dev)
{
	struct regmap *regmap = syscon_get_regmap(dev_get_parent(dev));

	return regmap_update_bits(regmap, FSS_SYSC_REG, 0x2, 0x2);
}

static int am654_hbmc_bind(struct udevice *dev)
{
	return dm_scan_fdt_dev(dev);
}

static int am654_hbmc_probe(struct udevice *dev)
{
	struct am654_hbmc_priv *priv = dev_get_priv(dev);
	int ret;

	priv->mmiobase = devfdt_remap_addr_index(dev, 1);
	if (dev_read_bool(dev, "mux-controls")) {
		ret = am654_select_hbmc(dev);
		if (ret) {
			dev_err(dev, "Failed to select HBMC mux\n");
			return ret;
		}
	}

	if (!priv->calibrated) {
		ret = am654_hyperbus_calibrate(dev);
		if (!ret) {
			dev_err(dev, "Calibration Failed\n");
			return -EIO;
		}
	}
	priv->calibrated = true;

	return 0;
}

static const struct udevice_id am654_hbmc_dt_ids[] = {
	{
		.compatible = "ti,am654-hbmc",
	},
	{ /* end of table */ }
};

U_BOOT_DRIVER(hbmc_am654) = {
	.name	= "hbmc-am654",
	.id	= UCLASS_MTD,
	.of_match = am654_hbmc_dt_ids,
	.probe = am654_hbmc_probe,
	.bind = am654_hbmc_bind,
	.priv_auto_alloc_size = sizeof(struct am654_hbmc_priv),
};
