18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright 2019 Inspur Corp.
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/debugfs.h>
78c2ecf20Sopenharmony_ci#include <linux/device.h>
88c2ecf20Sopenharmony_ci#include <linux/fs.h>
98c2ecf20Sopenharmony_ci#include <linux/i2c.h>
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/pmbus.h>
128c2ecf20Sopenharmony_ci#include <linux/hwmon-sysfs.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include "pmbus.h"
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#define IPSPS_REG_VENDOR_ID	0x99
178c2ecf20Sopenharmony_ci#define IPSPS_REG_MODEL		0x9A
188c2ecf20Sopenharmony_ci#define IPSPS_REG_FW_VERSION	0x9B
198c2ecf20Sopenharmony_ci#define IPSPS_REG_PN		0x9C
208c2ecf20Sopenharmony_ci#define IPSPS_REG_SN		0x9E
218c2ecf20Sopenharmony_ci#define IPSPS_REG_HW_VERSION	0xB0
228c2ecf20Sopenharmony_ci#define IPSPS_REG_MODE		0xFC
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define MODE_ACTIVE		0x55
258c2ecf20Sopenharmony_ci#define MODE_STANDBY		0x0E
268c2ecf20Sopenharmony_ci#define MODE_REDUNDANCY		0x00
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci#define MODE_ACTIVE_STRING		"active"
298c2ecf20Sopenharmony_ci#define MODE_STANDBY_STRING		"standby"
308c2ecf20Sopenharmony_ci#define MODE_REDUNDANCY_STRING		"redundancy"
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cienum ipsps_index {
338c2ecf20Sopenharmony_ci	vendor,
348c2ecf20Sopenharmony_ci	model,
358c2ecf20Sopenharmony_ci	fw_version,
368c2ecf20Sopenharmony_ci	part_number,
378c2ecf20Sopenharmony_ci	serial_number,
388c2ecf20Sopenharmony_ci	hw_version,
398c2ecf20Sopenharmony_ci	mode,
408c2ecf20Sopenharmony_ci	num_regs,
418c2ecf20Sopenharmony_ci};
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic const u8 ipsps_regs[num_regs] = {
448c2ecf20Sopenharmony_ci	[vendor] = IPSPS_REG_VENDOR_ID,
458c2ecf20Sopenharmony_ci	[model] = IPSPS_REG_MODEL,
468c2ecf20Sopenharmony_ci	[fw_version] = IPSPS_REG_FW_VERSION,
478c2ecf20Sopenharmony_ci	[part_number] = IPSPS_REG_PN,
488c2ecf20Sopenharmony_ci	[serial_number] = IPSPS_REG_SN,
498c2ecf20Sopenharmony_ci	[hw_version] = IPSPS_REG_HW_VERSION,
508c2ecf20Sopenharmony_ci	[mode] = IPSPS_REG_MODE,
518c2ecf20Sopenharmony_ci};
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic ssize_t ipsps_string_show(struct device *dev,
548c2ecf20Sopenharmony_ci				 struct device_attribute *devattr,
558c2ecf20Sopenharmony_ci				 char *buf)
568c2ecf20Sopenharmony_ci{
578c2ecf20Sopenharmony_ci	u8 reg;
588c2ecf20Sopenharmony_ci	int rc;
598c2ecf20Sopenharmony_ci	char *p;
608c2ecf20Sopenharmony_ci	char data[I2C_SMBUS_BLOCK_MAX + 1];
618c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev->parent);
628c2ecf20Sopenharmony_ci	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	reg = ipsps_regs[attr->index];
658c2ecf20Sopenharmony_ci	rc = i2c_smbus_read_block_data(client, reg, data);
668c2ecf20Sopenharmony_ci	if (rc < 0)
678c2ecf20Sopenharmony_ci		return rc;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	/* filled with printable characters, ending with # */
708c2ecf20Sopenharmony_ci	p = memscan(data, '#', rc);
718c2ecf20Sopenharmony_ci	*p = '\0';
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	return snprintf(buf, PAGE_SIZE, "%s\n", data);
748c2ecf20Sopenharmony_ci}
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistatic ssize_t ipsps_fw_version_show(struct device *dev,
778c2ecf20Sopenharmony_ci				     struct device_attribute *devattr,
788c2ecf20Sopenharmony_ci				     char *buf)
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci	u8 reg;
818c2ecf20Sopenharmony_ci	int rc;
828c2ecf20Sopenharmony_ci	u8 data[I2C_SMBUS_BLOCK_MAX] = { 0 };
838c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev->parent);
848c2ecf20Sopenharmony_ci	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	reg = ipsps_regs[attr->index];
878c2ecf20Sopenharmony_ci	rc = i2c_smbus_read_block_data(client, reg, data);
888c2ecf20Sopenharmony_ci	if (rc < 0)
898c2ecf20Sopenharmony_ci		return rc;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	if (rc != 6)
928c2ecf20Sopenharmony_ci		return -EPROTO;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	return snprintf(buf, PAGE_SIZE, "%u.%02u%u-%u.%02u\n",
958c2ecf20Sopenharmony_ci			data[1], data[2]/* < 100 */, data[3]/*< 10*/,
968c2ecf20Sopenharmony_ci			data[4], data[5]/* < 100 */);
978c2ecf20Sopenharmony_ci}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_cistatic ssize_t ipsps_mode_show(struct device *dev,
1008c2ecf20Sopenharmony_ci			       struct device_attribute *devattr, char *buf)
1018c2ecf20Sopenharmony_ci{
1028c2ecf20Sopenharmony_ci	u8 reg;
1038c2ecf20Sopenharmony_ci	int rc;
1048c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev->parent);
1058c2ecf20Sopenharmony_ci	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	reg = ipsps_regs[attr->index];
1088c2ecf20Sopenharmony_ci	rc = i2c_smbus_read_byte_data(client, reg);
1098c2ecf20Sopenharmony_ci	if (rc < 0)
1108c2ecf20Sopenharmony_ci		return rc;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	switch (rc) {
1138c2ecf20Sopenharmony_ci	case MODE_ACTIVE:
1148c2ecf20Sopenharmony_ci		return snprintf(buf, PAGE_SIZE, "[%s] %s %s\n",
1158c2ecf20Sopenharmony_ci				MODE_ACTIVE_STRING,
1168c2ecf20Sopenharmony_ci				MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING);
1178c2ecf20Sopenharmony_ci	case MODE_STANDBY:
1188c2ecf20Sopenharmony_ci		return snprintf(buf, PAGE_SIZE, "%s [%s] %s\n",
1198c2ecf20Sopenharmony_ci				MODE_ACTIVE_STRING,
1208c2ecf20Sopenharmony_ci				MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING);
1218c2ecf20Sopenharmony_ci	case MODE_REDUNDANCY:
1228c2ecf20Sopenharmony_ci		return snprintf(buf, PAGE_SIZE, "%s %s [%s]\n",
1238c2ecf20Sopenharmony_ci				MODE_ACTIVE_STRING,
1248c2ecf20Sopenharmony_ci				MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING);
1258c2ecf20Sopenharmony_ci	default:
1268c2ecf20Sopenharmony_ci		return snprintf(buf, PAGE_SIZE, "unspecified\n");
1278c2ecf20Sopenharmony_ci	}
1288c2ecf20Sopenharmony_ci}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_cistatic ssize_t ipsps_mode_store(struct device *dev,
1318c2ecf20Sopenharmony_ci				struct device_attribute *devattr,
1328c2ecf20Sopenharmony_ci				const char *buf, size_t count)
1338c2ecf20Sopenharmony_ci{
1348c2ecf20Sopenharmony_ci	u8 reg;
1358c2ecf20Sopenharmony_ci	int rc;
1368c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev->parent);
1378c2ecf20Sopenharmony_ci	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	reg = ipsps_regs[attr->index];
1408c2ecf20Sopenharmony_ci	if (sysfs_streq(MODE_STANDBY_STRING, buf)) {
1418c2ecf20Sopenharmony_ci		rc = i2c_smbus_write_byte_data(client, reg,
1428c2ecf20Sopenharmony_ci					       MODE_STANDBY);
1438c2ecf20Sopenharmony_ci		if (rc < 0)
1448c2ecf20Sopenharmony_ci			return rc;
1458c2ecf20Sopenharmony_ci		return count;
1468c2ecf20Sopenharmony_ci	} else if (sysfs_streq(MODE_ACTIVE_STRING, buf)) {
1478c2ecf20Sopenharmony_ci		rc = i2c_smbus_write_byte_data(client, reg,
1488c2ecf20Sopenharmony_ci					       MODE_ACTIVE);
1498c2ecf20Sopenharmony_ci		if (rc < 0)
1508c2ecf20Sopenharmony_ci			return rc;
1518c2ecf20Sopenharmony_ci		return count;
1528c2ecf20Sopenharmony_ci	}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	return -EINVAL;
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(vendor, ipsps_string, vendor);
1588c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(model, ipsps_string, model);
1598c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(part_number, ipsps_string, part_number);
1608c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(serial_number, ipsps_string, serial_number);
1618c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(hw_version, ipsps_string, hw_version);
1628c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(fw_version, ipsps_fw_version, fw_version);
1638c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(mode, ipsps_mode, mode);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_cistatic struct attribute *ipsps_attrs[] = {
1668c2ecf20Sopenharmony_ci	&sensor_dev_attr_vendor.dev_attr.attr,
1678c2ecf20Sopenharmony_ci	&sensor_dev_attr_model.dev_attr.attr,
1688c2ecf20Sopenharmony_ci	&sensor_dev_attr_part_number.dev_attr.attr,
1698c2ecf20Sopenharmony_ci	&sensor_dev_attr_serial_number.dev_attr.attr,
1708c2ecf20Sopenharmony_ci	&sensor_dev_attr_hw_version.dev_attr.attr,
1718c2ecf20Sopenharmony_ci	&sensor_dev_attr_fw_version.dev_attr.attr,
1728c2ecf20Sopenharmony_ci	&sensor_dev_attr_mode.dev_attr.attr,
1738c2ecf20Sopenharmony_ci	NULL,
1748c2ecf20Sopenharmony_ci};
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ciATTRIBUTE_GROUPS(ipsps);
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_cistatic struct pmbus_driver_info ipsps_info = {
1798c2ecf20Sopenharmony_ci	.pages = 1,
1808c2ecf20Sopenharmony_ci	.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
1818c2ecf20Sopenharmony_ci		PMBUS_HAVE_IIN | PMBUS_HAVE_POUT | PMBUS_HAVE_PIN |
1828c2ecf20Sopenharmony_ci		PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 |
1838c2ecf20Sopenharmony_ci		PMBUS_HAVE_TEMP3 | PMBUS_HAVE_STATUS_VOUT |
1848c2ecf20Sopenharmony_ci		PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT |
1858c2ecf20Sopenharmony_ci		PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_STATUS_FAN12,
1868c2ecf20Sopenharmony_ci	.groups = ipsps_groups,
1878c2ecf20Sopenharmony_ci};
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_cistatic struct pmbus_platform_data ipsps_pdata = {
1908c2ecf20Sopenharmony_ci	.flags = PMBUS_SKIP_STATUS_CHECK,
1918c2ecf20Sopenharmony_ci};
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_cistatic int ipsps_probe(struct i2c_client *client)
1948c2ecf20Sopenharmony_ci{
1958c2ecf20Sopenharmony_ci	client->dev.platform_data = &ipsps_pdata;
1968c2ecf20Sopenharmony_ci	return pmbus_do_probe(client, &ipsps_info);
1978c2ecf20Sopenharmony_ci}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_cistatic const struct i2c_device_id ipsps_id[] = {
2008c2ecf20Sopenharmony_ci	{ "ipsps1", 0 },
2018c2ecf20Sopenharmony_ci	{}
2028c2ecf20Sopenharmony_ci};
2038c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, ipsps_id);
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci#ifdef CONFIG_OF
2068c2ecf20Sopenharmony_cistatic const struct of_device_id ipsps_of_match[] = {
2078c2ecf20Sopenharmony_ci	{ .compatible = "inspur,ipsps1" },
2088c2ecf20Sopenharmony_ci	{}
2098c2ecf20Sopenharmony_ci};
2108c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ipsps_of_match);
2118c2ecf20Sopenharmony_ci#endif
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_cistatic struct i2c_driver ipsps_driver = {
2148c2ecf20Sopenharmony_ci	.driver = {
2158c2ecf20Sopenharmony_ci		.name = "inspur-ipsps",
2168c2ecf20Sopenharmony_ci		.of_match_table = of_match_ptr(ipsps_of_match),
2178c2ecf20Sopenharmony_ci	},
2188c2ecf20Sopenharmony_ci	.probe_new = ipsps_probe,
2198c2ecf20Sopenharmony_ci	.remove = pmbus_do_remove,
2208c2ecf20Sopenharmony_ci	.id_table = ipsps_id,
2218c2ecf20Sopenharmony_ci};
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_cimodule_i2c_driver(ipsps_driver);
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ciMODULE_AUTHOR("John Wang");
2268c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PMBus driver for Inspur Power System power supplies");
2278c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
228