/*
 * Driver to read Quanta PSU information
 *
 * Copyright (C) 2014 Cumulus Networks, LLC
 * Author: Puneet Shenoy <puneet@cumulusnetworks.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>

/*
 * Ideally this driver must be part of hwmon/pmbus. However, there are no
 * sensors to manage here. And Quanta PSU doesnt seem to support most of the
 * PMBUS commands except the ones we are using here. So this driver is
 * specific to Quanta PSUs and belongs in modules.
 */

/* These definitions have been taken from drivers/hwmon/pmbus/pmbus.h */
#define PMBUS_MFR_ID			0x99
#define PMBUS_MFR_MODEL			0x9A
#define PMBUS_MFR_REVISION		0x9B
#define PMBUS_MFR_LOCATION		0x9C
#define PMBUS_MFR_SERIAL		0x9E

enum chips { qct_rangeley_psu };

struct qct_rangeley_psu_data {
	int idx;
};

static ssize_t qct_rangeley_psu_block_show(struct device *dev,
					   struct device_attribute *da,
					   char *buf) {
	struct i2c_client *client = to_i2c_client(dev);
	int cmd = to_sensor_dev_attr(da)->index;
	u8 b_buf[I2C_SMBUS_BLOCK_MAX + 1];
	int ret;

	ret = i2c_smbus_read_block_data(client, cmd, b_buf);
	if (ret < 0) {
		dev_err(&client->dev, "Failed to read cmd 0x%x\n", cmd);
		return ret;
	}
	b_buf[ret] = '\0';
	return sprintf(buf, "%s\n", b_buf);
};

static SENSOR_DEVICE_ATTR(psu1_name,       S_IRUGO, qct_rangeley_psu_block_show,
			  NULL, PMBUS_MFR_ID);
static SENSOR_DEVICE_ATTR(psu1_model,      S_IRUGO, qct_rangeley_psu_block_show,
			  NULL, PMBUS_MFR_REVISION);
static SENSOR_DEVICE_ATTR(psu1_fru,        S_IRUGO, qct_rangeley_psu_block_show,
			  NULL, PMBUS_MFR_MODEL);
static SENSOR_DEVICE_ATTR(psu1_location,   S_IRUGO, qct_rangeley_psu_block_show,
			  NULL, PMBUS_MFR_LOCATION);
static SENSOR_DEVICE_ATTR(psu1_serial_num, S_IRUGO, qct_rangeley_psu_block_show,
			  NULL, PMBUS_MFR_SERIAL);

static struct attribute *qct_rangeley_psu1_attrs[] = {
	&sensor_dev_attr_psu1_name.dev_attr.attr,
	&sensor_dev_attr_psu1_model.dev_attr.attr,
	&sensor_dev_attr_psu1_fru.dev_attr.attr,
	&sensor_dev_attr_psu1_location.dev_attr.attr,
	&sensor_dev_attr_psu1_serial_num.dev_attr.attr,
	NULL
};

static const struct attribute_group qct_rangeley_psu1_attr_grp = {
	.attrs = qct_rangeley_psu1_attrs,
};

static SENSOR_DEVICE_ATTR(psu2_name,       S_IRUGO, qct_rangeley_psu_block_show,
			  NULL, PMBUS_MFR_ID);
static SENSOR_DEVICE_ATTR(psu2_model,      S_IRUGO, qct_rangeley_psu_block_show,
			  NULL, PMBUS_MFR_REVISION);
static SENSOR_DEVICE_ATTR(psu2_fru,        S_IRUGO, qct_rangeley_psu_block_show,
			  NULL, PMBUS_MFR_MODEL);
static SENSOR_DEVICE_ATTR(psu2_location,   S_IRUGO, qct_rangeley_psu_block_show,
			  NULL, PMBUS_MFR_LOCATION);
static SENSOR_DEVICE_ATTR(psu2_serial_num, S_IRUGO, qct_rangeley_psu_block_show,
			  NULL, PMBUS_MFR_SERIAL);

static struct attribute *qct_rangeley_psu2_attrs[] = {
	&sensor_dev_attr_psu2_name.dev_attr.attr,
	&sensor_dev_attr_psu2_model.dev_attr.attr,
	&sensor_dev_attr_psu2_fru.dev_attr.attr,
	&sensor_dev_attr_psu2_location.dev_attr.attr,
	&sensor_dev_attr_psu2_serial_num.dev_attr.attr,
	NULL
};

static const struct attribute_group qct_rangeley_psu2_attr_grp = {
	.attrs = qct_rangeley_psu2_attrs,
};

static int qct_rangeley_psu_probe(struct i2c_client *client,
			  const struct i2c_device_id *id)
{
	struct qct_rangeley_psu_data *data;
	int psu_idx;
	int ret;

	if (!i2c_check_functionality(client->adapter,
				     I2C_FUNC_SMBUS_BLOCK_DATA))
		return -ENODEV;

	if (!(data = kzalloc(sizeof(struct qct_rangeley_psu_data),
			     GFP_KERNEL))) {
		dev_err(&client->dev, "out of memory.\n");
		return -ENOMEM;
	}

	data = dev_get_platdata(&client->dev);
	psu_idx = data->idx;

	if (psu_idx == 1) {
		ret =  sysfs_create_group(&client->dev.kobj,
					  &qct_rangeley_psu1_attr_grp);
		if (ret)
			dev_err(&client->dev, "failed to create sysfs group for PSU%d\n", psu_idx);
	} else if (psu_idx == 2) {
		ret =  sysfs_create_group(&client->dev.kobj,
					  &qct_rangeley_psu2_attr_grp);
		if (ret)
			dev_err(&client->dev, "failed to create sysfs group for PSU%d\n", psu_idx);
	} else {
		dev_err(&client->dev, "Invalid PSU Index: %d\n", psu_idx);
		ret = -EINVAL;
	}
	if (ret < 0)
		kfree(data);

	return ret;
}

static int qct_rangeley_psu_remove(struct i2c_client *client)
{
	struct qct_rangeley_psu_data *data = i2c_get_clientdata(client);

	sysfs_remove_group(&client->dev.kobj, &qct_rangeley_psu1_attr_grp);
	sysfs_remove_group(&client->dev.kobj, &qct_rangeley_psu1_attr_grp);
	kfree(data);
	return 0;
}

static const struct i2c_device_id qct_rangeley_psu_id[] = {
	{"quanta_rangeley_psu", qct_rangeley_psu},
	{}
};
MODULE_DEVICE_TABLE(i2c, qct_rangeley_psu_id);

static struct i2c_driver qct_rangeley_psu_driver = {
	.driver = {
		.name = "quanta_rangeley_psu",
	},
	.probe = qct_rangeley_psu_probe,
	.remove = qct_rangeley_psu_remove,
	.id_table = qct_rangeley_psu_id,
};

module_i2c_driver(qct_rangeley_psu_driver);

MODULE_AUTHOR("Puneet Shenoy");
MODULE_DESCRIPTION("Driver for Quanta PSUs");
MODULE_LICENSE("GPL");
