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

#include "opae_intel_max10.h"
#include <libfdt.h>

int max10_reg_read(struct intel_max10_device *dev,
	unsigned int reg, unsigned int *val)
{
	if (!dev)
		return -ENODEV;

	dev_debug(dev, "%s: bus:0x%x, reg:0x%x\n", __func__, dev->bus, reg);

	return spi_transaction_read(dev->spi_tran_dev,
			reg, 4, (unsigned char *)val);
}

int max10_reg_write(struct intel_max10_device *dev,
	unsigned int reg, unsigned int val)
{
	unsigned int tmp = val;

	if (!dev)
		return -ENODEV;

	dev_debug(dev, "%s: bus:0x%x, reg:0x%x, val:0x%x\n", __func__,
			dev->bus, reg, val);

	return spi_transaction_write(dev->spi_tran_dev,
			reg, 4, (unsigned char *)&tmp);
}

int max10_sys_read(struct intel_max10_device *dev,
	unsigned int offset, unsigned int *val)
{
	if (!dev)
		return -ENODEV;


	return max10_reg_read(dev, dev->base + offset, val);
}

int max10_sys_write(struct intel_max10_device *dev,
	unsigned int offset, unsigned int val)
{
	if (!dev)
		return -ENODEV;

	return max10_reg_write(dev, dev->base + offset, val);
}

static struct max10_compatible_id max10_id_table[] = {
	{.compatible = MAX10_PAC,},
	{.compatible = MAX10_PAC_N3000,},
	{.compatible = MAX10_PAC_END,}
};

static struct max10_compatible_id *max10_match_compatible(const char *fdt_root)
{
	struct max10_compatible_id *id = max10_id_table;

	for (; strcmp(id->compatible, MAX10_PAC_END); id++) {
		if (fdt_node_check_compatible(fdt_root, 0, id->compatible))
			continue;

		return id;
	}

	return NULL;
}

static inline bool
is_max10_pac_n3000(struct intel_max10_device *max10)
{
	return max10->id && !strcmp(max10->id->compatible,
			MAX10_PAC_N3000);
}

static void max10_check_capability(struct intel_max10_device *max10)
{
	if (!max10->fdt_root)
		return;

	if (is_max10_pac_n3000(max10)) {
		max10->flags |= MAX10_FLAGS_NO_I2C2 |
				MAX10_FLAGS_NO_BMCIMG_FLASH;
		dev_info(max10, "found %s card\n", max10->id->compatible);
	} else
		max10->flags |= MAX10_FLAGS_MAC_CACHE;
}

static int altera_nor_flash_read(struct intel_max10_device *dev,
	u32 offset, void *buffer, u32 len)
{
	int word_len;
	int i;
	unsigned int *buf = (unsigned int *)buffer;
	unsigned int value;
	int ret;

	if (!dev || !buffer || len <= 0)
		return -ENODEV;

	word_len = len/4;

	for (i = 0; i < word_len; i++) {
		ret = max10_reg_read(dev, offset + i*4,
				&value);
		if (ret)
			return -EBUSY;

		*buf++ = value;
	}

	return 0;
}

static int enable_nor_flash(struct intel_max10_device *dev, bool on)
{
	unsigned int val = 0;
	int ret;

	ret = max10_sys_read(dev, RSU_REG, &val);
	if (ret) {
		dev_err(NULL "enabling flash error\n");
		return ret;
	}

	if (on)
		val |= RSU_ENABLE;
	else
		val &= ~RSU_ENABLE;

	return max10_sys_write(dev, RSU_REG, val);
}

static int init_max10_device_table(struct intel_max10_device *max10)
{
	struct altera_spi_device *spi = NULL;
	struct max10_compatible_id *id;
	struct fdt_header hdr;
	char *fdt_root = NULL;
	u32 dtb_magic = 0;
	u32 dt_size, dt_addr, val;
	int ret = 0;

	spi = (struct altera_spi_device *)max10->spi_master;
	if (!spi) {
		dev_err(max10, "spi master is not set\n");
		return -EINVAL;
	}
	if (spi->dtb)
		dtb_magic = *(u32 *)spi->dtb;

	if (dtb_magic != 0xEDFE0DD0) {
		dev_info(max10, "read DTB from NOR flash\n");
		ret = max10_sys_read(max10, DT_AVAIL_REG, &val);
		if (ret) {
			dev_err(max10 "cannot read DT_AVAIL_REG\n");
			return ret;
		}

		if (!(val & DT_AVAIL)) {
			dev_err(max10 "DT not available\n");
			return -EINVAL;
		}

		ret = max10_sys_read(max10, DT_BASE_ADDR_REG, &dt_addr);
		if (ret) {
			dev_info(max10 "cannot get base addr of device table\n");
			return ret;
		}

		ret = enable_nor_flash(max10, true);
		if (ret) {
			dev_err(max10 "fail to enable flash\n");
			return ret;
		}

		ret = altera_nor_flash_read(max10, dt_addr, &hdr, sizeof(hdr));
		if (ret) {
			dev_err(max10 "read fdt header fail\n");
			goto disable_nor_flash;
		}

		ret = fdt_check_header(&hdr);
		if (ret) {
			dev_err(max10 "check fdt header fail\n");
			goto disable_nor_flash;
		}

		dt_size = fdt_totalsize(&hdr);
		if (dt_size > DFT_MAX_SIZE) {
			dev_err(max10 "invalid device table size\n");
			ret = -EINVAL;
			goto disable_nor_flash;
		}

		fdt_root = opae_malloc(dt_size);
		if (!fdt_root) {
			ret = -ENOMEM;
			goto disable_nor_flash;
		}

		ret = altera_nor_flash_read(max10, dt_addr, fdt_root, dt_size);
		if (ret) {
			opae_free(fdt_root);
			fdt_root = NULL;
			dev_err(max10 "cannot read device table\n");
			goto disable_nor_flash;
		}

		if (spi->dtb) {
			if (*spi->dtb_sz_ptr < dt_size) {
				dev_warn(max10,
						 "share memory for dtb is smaller than required %u\n",
						 dt_size);
			} else {
				*spi->dtb_sz_ptr = dt_size;
			}
			/* store dtb data into share memory  */
			memcpy(spi->dtb, fdt_root, *spi->dtb_sz_ptr);
		}

disable_nor_flash:
		enable_nor_flash(max10, false);
	} else {
		if (*spi->dtb_sz_ptr > 0) {
			dev_info(max10, "read DTB from shared memory\n");
			fdt_root = opae_malloc(*spi->dtb_sz_ptr);
			if (fdt_root)
				memcpy(fdt_root, spi->dtb, *spi->dtb_sz_ptr);
			else
				ret = -ENOMEM;
		}
	}

	if (fdt_root) {
		id = max10_match_compatible(fdt_root);
		if (!id) {
			dev_err(max10 "max10 compatible not found\n");
			ret = -ENODEV;
		} else {
			max10->flags |= MAX10_FLAGS_DEVICE_TABLE;
			max10->id = id;
			max10->fdt_root = fdt_root;
		}
	}

	return ret;
}

static u64 fdt_get_number(const fdt32_t *cell, int size)
{
	u64 r = 0;

	while (size--)
		r = (r << 32) | fdt32_to_cpu(*cell++);

	return r;
}

static int fdt_get_reg(const void *fdt, int node, unsigned int idx,
		u64 *start, u64 *size)
{
	const fdt32_t *prop, *end;
	int na = 0, ns = 0, len = 0, parent;

	parent = fdt_parent_offset(fdt, node);
	if (parent < 0)
		return parent;

	prop = fdt_getprop(fdt, parent, "#address-cells", NULL);
	na = prop ? fdt32_to_cpu(*prop) : 2;

	prop = fdt_getprop(fdt, parent, "#size-cells", NULL);
	ns = prop ? fdt32_to_cpu(*prop) : 2;

	prop = fdt_getprop(fdt, node, "reg", &len);
	if (!prop)
		return -FDT_ERR_NOTFOUND;

	end = prop + len/sizeof(*prop);
	prop = prop + (na + ns) * idx;

	if (prop + na + ns > end)
		return -FDT_ERR_NOTFOUND;

	*start = fdt_get_number(prop, na);
	*size = fdt_get_number(prop + na, ns);

	return 0;
}

static int __fdt_stringlist_search(const void *fdt, int offset,
		const char *prop, const char *string)
{
	int length, len, index = 0;
	const char *list, *end;

	list = fdt_getprop(fdt, offset, prop, &length);
	if (!list)
		return length;

	len = strlen(string) + 1;
	end = list + length;

	while (list < end) {
		length = strnlen(list, end - list) + 1;

		if (list + length > end)
			return -FDT_ERR_BADVALUE;

		if (length == len && memcmp(list, string, length) == 0)
			return index;

		list += length;
		index++;
	}

	return -FDT_ERR_NOTFOUND;
}

static int fdt_get_named_reg(const void *fdt, int node, const char *name,
		u64 *start, u64 *size)
{
	int idx;

	idx = __fdt_stringlist_search(fdt, node, "reg-names", name);
	if (idx < 0)
		return idx;

	return fdt_get_reg(fdt, node, idx, start, size);
}

static void max10_sensor_uinit(struct intel_max10_device *dev)
{
	struct opae_sensor_info *info;

	TAILQ_FOREACH(info, &dev->opae_sensor_list, node) {
		TAILQ_REMOVE(&dev->opae_sensor_list, info, node);
		opae_free(info);
	}
}

static bool sensor_reg_valid(struct sensor_reg *reg)
{
	return !!reg->size;
}

static int max10_add_sensor(struct intel_max10_device *dev,
	struct raw_sensor_info *info, struct opae_sensor_info *sensor)
{
	int i;
	int ret = 0;
	unsigned int val;

	if (!info || !sensor)
		return -ENODEV;

	sensor->id = info->id;
	sensor->name = info->name;
	sensor->type = info->type;
	sensor->multiplier = info->multiplier;

	for (i = SENSOR_REG_VALUE; i < SENSOR_REG_MAX; i++) {
		if (!sensor_reg_valid(&info->regs[i]))
			continue;

		ret = max10_sys_read(dev, info->regs[i].regoff, &val);
		if (ret)
			break;

		if (val == 0xdeadbeef) {
			dev_debug(dev, "%s: sensor:%s invalid 0x%x at:%d\n",
				__func__, sensor->name, val, i);
			continue;
		}

		val *= info->multiplier;

		switch (i) {
		case SENSOR_REG_VALUE:
			sensor->value_reg = info->regs[i].regoff;
			sensor->flags |= OPAE_SENSOR_VALID;
			break;
		case SENSOR_REG_HIGH_WARN:
			sensor->high_warn = val;
			sensor->flags |= OPAE_SENSOR_HIGH_WARN_VALID;
			break;
		case SENSOR_REG_HIGH_FATAL:
			sensor->high_fatal = val;
			sensor->flags |= OPAE_SENSOR_HIGH_FATAL_VALID;
			break;
		case SENSOR_REG_LOW_WARN:
			sensor->low_warn = val;
			sensor->flags |= OPAE_SENSOR_LOW_WARN_VALID;
			break;
		case SENSOR_REG_LOW_FATAL:
			sensor->low_fatal = val;
			sensor->flags |= OPAE_SENSOR_LOW_FATAL_VALID;
			break;
		case SENSOR_REG_HYSTERESIS:
			sensor->hysteresis = val;
			sensor->flags |= OPAE_SENSOR_HYSTERESIS_VALID;
			break;
		}
	}

	return ret;
}

static int
max10_sensor_init(struct intel_max10_device *dev, int parent)
{
	int i, ret = 0, offset = 0;
	const fdt32_t *num;
	const char *ptr;
	u64 start, size;
	struct raw_sensor_info *raw;
	struct opae_sensor_info *sensor;
	char *fdt_root = dev->fdt_root;

	if (!fdt_root) {
		dev_debug(dev, "skip sensor init as not find Device Tree\n");
		return 0;
	}

	fdt_for_each_subnode(offset, fdt_root, parent) {
		ptr = fdt_get_name(fdt_root, offset, NULL);
		if (!ptr) {
			dev_err(dev, "failed to fdt get name\n");
			continue;
		}

		if (!strstr(ptr, "sensor")) {
			dev_debug(dev, "%s is not a sensor node\n", ptr);
			continue;
		}

		dev_debug(dev, "found sensor node %s\n", ptr);

		raw = (struct raw_sensor_info *)opae_zmalloc(sizeof(*raw));
		if (!raw) {
			ret = -ENOMEM;
			goto free_sensor;
		}

		raw->name = fdt_getprop(fdt_root, offset, "sensor_name", NULL);
		if (!raw->name) {
			ret = -EINVAL;
			goto free_sensor;
		}

		raw->type = fdt_getprop(fdt_root, offset, "type", NULL);
		if (!raw->type) {
			ret = -EINVAL;
			goto free_sensor;
		}

		for (i = SENSOR_REG_VALUE; i < SENSOR_REG_MAX; i++) {
			ret = fdt_get_named_reg(fdt_root, offset,
					sensor_reg_name[i], &start,
					&size);
			if (ret) {
				dev_debug(dev, "no found %d: sensor node %s, %s\n",
						ret, ptr, sensor_reg_name[i]);
				if (i == SENSOR_REG_VALUE) {
					ret = -EINVAL;
					goto free_sensor;
				}

				continue;
			}

			/* This is a hack to compatible with non-secure
			 * solution. If sensors are included in root node,
			 * then it's non-secure dtb, which use absolute addr
			 * of non-secure solution.
			 */
			if (parent)
				raw->regs[i].regoff = start;
			else
				raw->regs[i].regoff = start -
					MAX10_BASE_ADDR;
			raw->regs[i].size = size;
		}

		num = fdt_getprop(fdt_root, offset, "id", NULL);
		if (!num) {
			ret = -EINVAL;
			goto free_sensor;
		}

		raw->id = fdt32_to_cpu(*num);
		num = fdt_getprop(fdt_root, offset, "multiplier", NULL);
		raw->multiplier = num ? fdt32_to_cpu(*num) : 1;

		dev_debug(dev, "found sensor from DTB: %s: %s: %u: %u\n",
				raw->name, raw->type,
				raw->id, raw->multiplier);

		for (i = SENSOR_REG_VALUE; i < SENSOR_REG_MAX; i++)
			dev_debug(dev, "sensor reg[%d]: %x: %zu\n",
					i, raw->regs[i].regoff,
					raw->regs[i].size);

		sensor = opae_zmalloc(sizeof(*sensor));
		if (!sensor) {
			ret = -EINVAL;
			goto free_sensor;
		}

		if (max10_add_sensor(dev, raw, sensor)) {
			ret = -EINVAL;
			opae_free(sensor);
			goto free_sensor;
		}

		if (sensor->flags & OPAE_SENSOR_VALID) {
			TAILQ_INSERT_TAIL(&dev->opae_sensor_list, sensor, node);
			dev_info(dev, "found valid sensor: %s\n", sensor->name);
		} else
			opae_free(sensor);

		opae_free(raw);
	}

	return 0;

free_sensor:
	if (raw)
		opae_free(raw);
	max10_sensor_uinit(dev);
	return ret;
}

static int check_max10_version(struct intel_max10_device *dev)
{
	unsigned int v;

	if (!max10_reg_read(dev, MAX10_SEC_BASE_ADDR + MAX10_BUILD_VER,
				&v)) {
		if (v != 0xffffffff) {
			dev_info(dev, "secure MAX10 detected\n");
			dev->base = MAX10_SEC_BASE_ADDR;
			dev->flags |= MAX10_FLAGS_SECURE;
		} else {
			dev_info(dev, "non-secure MAX10 detected\n");
			dev->base = MAX10_BASE_ADDR;
		}
		return 0;
	}

	return -ENODEV;
}

static int
max10_secure_hw_init(struct intel_max10_device *dev)
{
	int offset, sysmgr_offset = 0;
	char *fdt_root;

	fdt_root = dev->fdt_root;
	if (!fdt_root) {
		dev_debug(dev, "skip init as not find Device Tree\n");
		return 0;
	}

	fdt_for_each_subnode(offset, fdt_root, 0) {
		if (!fdt_node_check_compatible(fdt_root, offset,
					"intel-max10,system-manager")) {
			sysmgr_offset = offset;
			break;
		}
	}

	max10_check_capability(dev);

	max10_sensor_init(dev, sysmgr_offset);

	return 0;
}

static int
max10_non_secure_hw_init(struct intel_max10_device *dev)
{
	max10_check_capability(dev);

	max10_sensor_init(dev, 0);

	return 0;
}

struct intel_max10_device *
intel_max10_device_probe(struct altera_spi_device *spi,
		int chipselect)
{
	struct intel_max10_device *dev;
	int ret;
	unsigned int val;

	dev = opae_malloc(sizeof(*dev));
	if (!dev)
		return NULL;

	TAILQ_INIT(&dev->opae_sensor_list);

	dev->spi_master = spi;

	dev->spi_tran_dev = spi_transaction_init(spi, chipselect);
	if (!dev->spi_tran_dev) {
		dev_err(dev, "%s spi tran init fail\n", __func__);
		goto free_dev;
	}

	/* check the max10 version */
	ret = check_max10_version(dev);
	if (ret) {
		dev_err(dev, "Failed to find max10 hardware!\n");
		goto free_dev;
	}

	/* load the MAX10 device table */
	ret = init_max10_device_table(dev);
	if (ret) {
		dev_err(dev, "Init max10 device table fail\n");
		goto free_dev;
	}

	/* init max10 devices, like sensor*/
	if (dev->flags & MAX10_FLAGS_SECURE)
		ret = max10_secure_hw_init(dev);
	else
		ret = max10_non_secure_hw_init(dev);
	if (ret) {
		dev_err(dev, "Failed to init max10 hardware!\n");
		goto free_dtb;
	}

	/* read FPGA loading information */
	ret = max10_sys_read(dev, FPGA_PAGE_INFO, &val);
	if (ret) {
		dev_err(dev, "fail to get FPGA loading info\n");
		goto release_max10_hw;
	}
	dev_info(dev, "FPGA loaded from %s Image\n", val ? "User" : "Factory");

	return dev;

release_max10_hw:
	max10_sensor_uinit(dev);
free_dtb:
	if (dev->fdt_root)
		opae_free(dev->fdt_root);
	if (dev->spi_tran_dev)
		spi_transaction_remove(dev->spi_tran_dev);
free_dev:
	opae_free(dev);

	return NULL;
}

int intel_max10_device_remove(struct intel_max10_device *dev)
{
	if (!dev)
		return 0;

	max10_sensor_uinit(dev);

	if (dev->spi_tran_dev)
		spi_transaction_remove(dev->spi_tran_dev);

	if (dev->fdt_root)
		opae_free(dev->fdt_root);

	opae_free(dev);

	return 0;
}
