// SPDX-License-Identifier: GPL-2.0-only
/*
 * Voltage Regulator Specification: VRS11 High Current Voltage Regulator
 *
 * Copyright (C) 2022-2024 NVIDIA CORPORATION. All rights reserved.
 */

#include <nvidia/conftest.h>

#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/types.h>
#include <linux/version.h>

#define VOLTAGE_OFFSET		200 // 0.2V
#define VOLTAGE_SCALE		5   // 5mV

/* Vendor ID */
#define NVVRS11_REG_VENDOR_ID		0x00
#define NVVRS11_REG_MODEL_REV		0x01

/* Voltage Output registers */
#define NVVRS11_REG_VOUT_A		0x30
#define NVVRS11_REG_VOUT_B		0x33

/* Current Output registers */
#define NVVRS11_REG_IOUT_A		0x31
#define NVVRS11_REG_IOUT_B		0x34

/* Temperature registers */
#define NVVRS11_REG_TEMP_A		0x32
#define NVVRS11_REG_TEMP_B		0x35

struct nvvrs11_chip {
	struct device *dev;
	struct regmap *rmap;
	struct i2c_client *client;
	const char *loopA_rail_name;
	const char *loopB_rail_name;
};

static const struct regmap_range nvvrs11_readable_ranges[] = {
	regmap_reg_range(NVVRS11_REG_VENDOR_ID, NVVRS11_REG_MODEL_REV),
	regmap_reg_range(NVVRS11_REG_VOUT_A, NVVRS11_REG_TEMP_B),
};

static const struct regmap_access_table nvvrs11_readable_table = {
	.yes_ranges = nvvrs11_readable_ranges,
	.n_yes_ranges = ARRAY_SIZE(nvvrs11_readable_ranges),
};

static const struct regmap_config nvvrs11_regmap_config = {
	.name = "nvvrs11",
	.reg_bits = 8,
	.val_bits = 8,
	.max_register = NVVRS11_REG_TEMP_B + 1,
	.cache_type = REGCACHE_RBTREE,
	.rd_table = &nvvrs11_readable_table,
};

static ssize_t show_loopA_rail_name(struct device *dev,
	struct device_attribute *devattr, char *buf)
{
	struct nvvrs11_chip *nvvrs_chip = dev_get_drvdata(dev);

	return sprintf(buf, "%s\n", nvvrs_chip->loopA_rail_name);
}

static ssize_t show_loopA_rail_voltage(struct device *dev,
	struct device_attribute *devattr, char *buf)
{
	struct nvvrs11_chip *nvvrs_chip = dev_get_drvdata(dev);
	int voltage_A;

	voltage_A = i2c_smbus_read_byte_data(nvvrs_chip->client, NVVRS11_REG_VOUT_A);
	if (voltage_A < 0)
		return voltage_A;

	voltage_A *= VOLTAGE_SCALE;
	voltage_A += VOLTAGE_OFFSET;

	return sprintf(buf, "%u mV\n", voltage_A);
}

static ssize_t show_loopA_rail_current(struct device *dev,
	struct device_attribute *devattr, char *buf)
{
	struct nvvrs11_chip *nvvrs_chip = dev_get_drvdata(dev);
	int current_A;

	current_A = i2c_smbus_read_byte_data(nvvrs_chip->client, NVVRS11_REG_IOUT_A);
	if (current_A < 0)
		return current_A;

	return sprintf(buf, "%u A\n", current_A);
}

static ssize_t show_loopA_rail_power(struct device *dev,
	struct device_attribute *devattr, char *buf)
{
	struct nvvrs11_chip *nvvrs_chip = dev_get_drvdata(dev);
	unsigned int power;
	int voltage_A;
	int current_A;

	voltage_A = i2c_smbus_read_byte_data(nvvrs_chip->client, NVVRS11_REG_VOUT_A);
	if (voltage_A < 0)
		return voltage_A;

	voltage_A *= VOLTAGE_SCALE;
	voltage_A += VOLTAGE_OFFSET;

	current_A = i2c_smbus_read_byte_data(nvvrs_chip->client, NVVRS11_REG_IOUT_A);
	if (current_A < 0)
		return current_A;

	power = (voltage_A * current_A);
	return sprintf(buf, "%u mW\n", power);
}

static ssize_t show_loopB_rail_name(struct device *dev,
	struct device_attribute *devattr, char *buf)
{
	struct nvvrs11_chip *nvvrs_chip = dev_get_drvdata(dev);

	return sprintf(buf, "%s\n", nvvrs_chip->loopB_rail_name);
}

static ssize_t show_loopB_rail_voltage(struct device *dev,
	struct device_attribute *devattr, char *buf)
{
	struct nvvrs11_chip *nvvrs_chip = dev_get_drvdata(dev);
	int voltage_B;

	voltage_B = i2c_smbus_read_byte_data(nvvrs_chip->client, NVVRS11_REG_VOUT_B);
	if (voltage_B < 0)
		return voltage_B;

	voltage_B *= VOLTAGE_SCALE;
	voltage_B += VOLTAGE_OFFSET;

	return sprintf(buf, "%u mV\n", voltage_B);
}

static ssize_t show_loopB_rail_current(struct device *dev,
	struct device_attribute *devattr, char *buf)
{
	struct nvvrs11_chip *nvvrs_chip = dev_get_drvdata(dev);
	int current_B;

	current_B = i2c_smbus_read_byte_data(nvvrs_chip->client, NVVRS11_REG_IOUT_B);
	if (current_B < 0)
		return current_B;

	return sprintf(buf, "%u A\n", current_B);
}

static ssize_t show_loopB_rail_power(struct device *dev,
	struct device_attribute *devattr, char *buf)
{
	struct nvvrs11_chip *nvvrs_chip = dev_get_drvdata(dev);
	unsigned int power;
	int voltage_B;
	int current_B;

	voltage_B = i2c_smbus_read_byte_data(nvvrs_chip->client, NVVRS11_REG_VOUT_B);
	if (voltage_B < 0)
		return voltage_B;

	voltage_B *= VOLTAGE_SCALE;
	voltage_B += VOLTAGE_OFFSET;

	current_B = i2c_smbus_read_byte_data(nvvrs_chip->client, NVVRS11_REG_IOUT_B);
	if (current_B < 0)
		return current_B;

	power = (voltage_B * current_B);

	return sprintf(buf, "%u mW\n", power);
}

static DEVICE_ATTR(loopA_rail_name, S_IRUGO, show_loopA_rail_name, NULL);
static DEVICE_ATTR(loopA_rail_voltage, S_IRUGO, show_loopA_rail_voltage, NULL);
static DEVICE_ATTR(loopA_rail_current, S_IRUGO, show_loopA_rail_current, NULL);
static DEVICE_ATTR(loopA_rail_power, S_IRUGO, show_loopA_rail_power, NULL);
static DEVICE_ATTR(loopB_rail_name, S_IRUGO, show_loopB_rail_name, NULL);
static DEVICE_ATTR(loopB_rail_voltage, S_IRUGO, show_loopB_rail_voltage, NULL);
static DEVICE_ATTR(loopB_rail_current, S_IRUGO, show_loopB_rail_current, NULL);
static DEVICE_ATTR(loopB_rail_power, S_IRUGO, show_loopB_rail_power, NULL);

static struct attribute *nvvrs11_attr[] = {
	&dev_attr_loopA_rail_name.attr,
	&dev_attr_loopA_rail_voltage.attr,
	&dev_attr_loopA_rail_current.attr,
	&dev_attr_loopA_rail_power.attr,
	&dev_attr_loopB_rail_name.attr,
	&dev_attr_loopB_rail_voltage.attr,
	&dev_attr_loopB_rail_current.attr,
	&dev_attr_loopB_rail_power.attr,
	NULL
};

static const struct attribute_group nvvrs11_attr_group = {
	.attrs = nvvrs11_attr,
};

static int nvvrs11_create_sys_files(struct device *dev)
{
	return sysfs_create_group(&dev->kobj, &nvvrs11_attr_group);
}

static void nvvrs11_delete_sys_files(struct device *dev)
{
	return sysfs_remove_group(&dev->kobj, &nvvrs11_attr_group);
}

static int nvvrs11_vendor_info(struct nvvrs11_chip *chip)
{
	struct i2c_client *client = chip->client;
	int vendor_id, model_rev;

	vendor_id = i2c_smbus_read_byte_data(client, NVVRS11_REG_VENDOR_ID);
	if (vendor_id < 0) {
		dev_err(chip->dev, "Failed to read Vendor ID: %d\n", vendor_id);
		return -EINVAL;
	}

	dev_info(chip->dev, "NVVRS11 Vendor ID: 0x%X \n", vendor_id);

	model_rev = i2c_smbus_read_byte_data(client, NVVRS11_REG_MODEL_REV);
	if (model_rev < 0) {
		dev_err(chip->dev, "Failed to read Model Rev: %d\n", model_rev);
		return -EINVAL;
	}

	dev_info(chip->dev, "NVVRS11 Model Rev: 0x%X\n", model_rev);

	return 0;
}

#if defined(NV_I2C_DRIVER_STRUCT_PROBE_WITHOUT_I2C_DEVICE_ID_ARG) /* Linux 6.3 */
static int nvvrs11_probe(struct i2c_client *client)
#else
static int nvvrs11_probe(struct i2c_client *client,
			  const struct i2c_device_id *id)
#endif
{
	const struct regmap_config *rmap_config;
	struct nvvrs11_chip *nvvrs_chip;
	struct device *dev = &client->dev;
	struct device_node *np = dev->of_node;
	int ret;

	nvvrs_chip = devm_kzalloc(&client->dev, sizeof(*nvvrs_chip), GFP_KERNEL);
	if (!nvvrs_chip)
		return -ENOMEM;

	/* Set PEC flag for SMBUS transfer with PEC enabled */
	client->flags |= I2C_CLIENT_PEC;

	i2c_set_clientdata(client, nvvrs_chip);
	nvvrs_chip->client = client;
	nvvrs_chip->dev = &client->dev;
	rmap_config = &nvvrs11_regmap_config;

	nvvrs_chip->rmap = devm_regmap_init_i2c(client, rmap_config);
	if (IS_ERR(nvvrs_chip->rmap)) {
		ret = PTR_ERR(nvvrs_chip->rmap);
		dev_err(nvvrs_chip->dev, "Failed to initialise regmap: %d\n", ret);
		return ret;
	}

	/* Read device tree node info */
	nvvrs_chip->loopA_rail_name = of_get_property(np, "rail-name-loopA", NULL);
	if (!(nvvrs_chip->loopA_rail_name)) {
		dev_info(nvvrs_chip->dev, "loopA rail does not exist\n");
		nvvrs_chip->loopA_rail_name = "LoopA";
	}

	nvvrs_chip->loopB_rail_name = of_get_property(np, "rail-name-loopB", NULL);
	if (!(nvvrs_chip->loopB_rail_name)) {
		dev_info(nvvrs_chip->dev, "loopB rail does not exist\n");
		nvvrs_chip->loopB_rail_name = "LoopB";
	}

	ret = nvvrs11_create_sys_files(nvvrs_chip->dev);
	if (ret) {
		dev_err(nvvrs_chip->dev, "Failed to add sysfs entries: %d\n", ret);
		return ret;
	}

	ret = nvvrs11_vendor_info(nvvrs_chip);
	if (ret < 0) {
		dev_err(nvvrs_chip->dev, "Failed to read vendor info: %d\n", ret);
		goto exit;
	}

	dev_info(nvvrs_chip->dev, "NVVRS11 probe successful");
	return 0;

exit:
	nvvrs11_delete_sys_files(nvvrs_chip->dev);
	dev_info(nvvrs_chip->dev, "NVVRS11 probe failed");
	return ret;
}

#if !defined(NV_I2C_DRIVER_STRUCT_REMOVE_RETURN_TYPE_INT) /* Linux 6.1 */
static void nvvrs11_remove(struct i2c_client *client)
{
	nvvrs11_delete_sys_files(&client->dev);
}
#else
static int nvvrs11_remove(struct i2c_client *client)
{
	nvvrs11_delete_sys_files(&client->dev);
	return 0;
}
#endif

#ifdef CONFIG_PM_SLEEP
static int nvvrs11_i2c_suspend(struct device *dev)
{
	return 0;
}

static int nvvrs11_i2c_resume(struct device *dev)
{
	return 0;
}
#endif

static const struct dev_pm_ops nvvrs11_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(nvvrs11_i2c_suspend, nvvrs11_i2c_resume)
};

static const struct of_device_id nvvrs_dt_match[] = {
	{ .compatible = "nvidia,vrs11" },
	{}
};
MODULE_DEVICE_TABLE(of, nvvrs_dt_match);

static struct i2c_driver nvvrs11_driver = {
	.driver = {
		.name = "nvvrs11",
		.pm = &nvvrs11_pm_ops,
		.of_match_table = of_match_ptr(nvvrs_dt_match),
	},
	.probe = nvvrs11_probe,
	.remove = nvvrs11_remove,
};

static int __init nvvrs11_init(void)
{
	return i2c_add_driver(&nvvrs11_driver);
}
module_init(nvvrs11_init);

static void __exit nvvrs11_exit(void)
{
	i2c_del_driver(&nvvrs11_driver);
}
module_exit(nvvrs11_exit);

MODULE_DESCRIPTION("Nvidia VRS11: High Current Voltage Regulator Spec");
MODULE_AUTHOR("Shubhi Garg <shgarg@nvidia.com>");
MODULE_ALIAS("i2c:nvvrs11");
MODULE_LICENSE("GPL v2");
