// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2024 CERN (www.cern.ch)
 * Author: Vaibhav Gupta <vaibhav.gupta@cern.ch>
 */

#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/platform_data/i2c-ocores.h>
#include "fmc-adc-100m14b4cha-private.h"

#define SI570_I2C_MASTER_OFF 0x1600
#define SI570_I2C_MASTER_SIZE 0x100

#define SI570_I2C_OFF 0X55

static struct resource si570_i2c_master_res = {
	.name   = "si570_i2c_master_res",
	.start	= SI570_I2C_MASTER_OFF,
	.end	= SI570_I2C_MASTER_OFF + SI570_I2C_MASTER_SIZE - 1,
	.flags	= IORESOURCE_MEM,
};

#define SI570_I2C_MASTER_CLK_HZ 125000000
#define SI570_I2C_MASTER_CLK_KHZ (SI570_I2C_MASTER_CLK_HZ / 1000)

static struct ocores_i2c_platform_data si570_i2c_master_data = {
	.reg_shift	  = 2,		/* two bytes between registers */
	.reg_io_width     = 4,
	.clock_khz	  = SI570_I2C_MASTER_CLK_KHZ,
	.num_devices      = 0,
	.devices	  = NULL,
};

static int fa_si570_i2c_find_adapter(struct device *dev, void *data)
{
	struct i2c_adapter *adap;
	struct platform_device *pdev = data;

	if (dev->type != &i2c_adapter_type)
		return 0;

	adap = to_i2c_adapter(dev);

	if(adap->dev.parent != &pdev->dev)
		return 0;

	return adap->nr;
}


int fa_si570_i2c_init(struct fa_dev *fa) {
	int idr;
	struct platform_device *pdev;
	struct resource *rmem;

	rmem = platform_get_resource(fa->pdev, IORESOURCE_MEM,
				     ADC_MEM_BASE);
	if(!rmem) {
		dev_err(&fa->pdev->dev, "Missing memory resource\n");
		return -EINVAL;
	}

	si570_i2c_master_res.parent = rmem;
	si570_i2c_master_res.start  = rmem->start + SI570_I2C_MASTER_OFF;
	si570_i2c_master_res.end    = si570_i2c_master_res.start +
					SI570_I2C_MASTER_SIZE - 1;

	si570_i2c_master_data.big_endian = fa_is_flag_set(fa, FMC_ADC_BIG_ENDIAN);

	pdev = platform_device_alloc("ocores-i2c", PLATFORM_DEVID_AUTO);

	if (!pdev ||
	    platform_device_add_resources(pdev,
					  &si570_i2c_master_res, 1) ||
	    platform_device_add_data(pdev,
				     &si570_i2c_master_data,
				     sizeof(si570_i2c_master_data)) ||
	    platform_device_add(pdev)) {
		platform_device_put(pdev);
		return -1;
	}

	idr = i2c_for_each_dev(pdev, fa_si570_i2c_find_adapter);

	if(idr < 0){
		dev_err(&fa->pdev->dev, "Could not acquire i2C Adapter\n");
		return idr;
	}

	fa->si570_i2c_adapter_nr = idr;

	return 0;
}

void fa_si570_i2c_exit(struct fa_dev *fa) {
	struct i2c_adapter *adap;
	struct platform_device *pdev;

	adap = i2c_get_adapter(fa->si570_i2c_adapter_nr);
	pdev = container_of(adap->dev.parent, struct platform_device,
			    dev);

	i2c_put_adapter(adap);

	platform_device_unregister(pdev);
}

int fa_si570_i2c_read_reg(struct fa_dev *fa, u8 reg_addr, u8 *val)
{
	int ret;
	struct i2c_adapter *adap;
	struct i2c_msg msg[2];

	adap = i2c_get_adapter(fa->si570_i2c_adapter_nr);

	msg[0].addr = SI570_I2C_OFF;
	msg[0].flags = 0x0;
	msg[0].len = 1;
	msg[0].buf = &reg_addr;

	msg[1].addr = SI570_I2C_OFF;
	msg[1].flags = I2C_M_RD;
	msg[1].len   = 1;
	msg[1].buf   = val;

	ret = i2c_transfer(adap, msg, 2);
	i2c_put_adapter(adap);

	return ret;
}
