| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright 2019 Inspur Corp. |
| */ |
| |
| #include <linux/debugfs.h> |
| #include <linux/device.h> |
| #include <linux/fs.h> |
| #include <linux/i2c.h> |
| #include <linux/module.h> |
| #include <linux/pmbus.h> |
| #include <linux/hwmon-sysfs.h> |
| |
| #include "pmbus.h" |
| |
| #define IPSPS_REG_VENDOR_ID 0x99 |
| #define IPSPS_REG_MODEL 0x9A |
| #define IPSPS_REG_FW_VERSION 0x9B |
| #define IPSPS_REG_PN 0x9C |
| #define IPSPS_REG_SN 0x9E |
| #define IPSPS_REG_HW_VERSION 0xB0 |
| #define IPSPS_REG_MODE 0xFC |
| |
| #define MODE_ACTIVE 0x55 |
| #define MODE_STANDBY 0x0E |
| #define MODE_REDUNDANCY 0x00 |
| |
| #define MODE_ACTIVE_STRING "active" |
| #define MODE_STANDBY_STRING "standby" |
| #define MODE_REDUNDANCY_STRING "redundancy" |
| |
| enum ipsps_index { |
| vendor, |
| model, |
| fw_version, |
| part_number, |
| serial_number, |
| hw_version, |
| mode, |
| num_regs, |
| }; |
| |
| static const u8 ipsps_regs[num_regs] = { |
| [vendor] = IPSPS_REG_VENDOR_ID, |
| [model] = IPSPS_REG_MODEL, |
| [fw_version] = IPSPS_REG_FW_VERSION, |
| [part_number] = IPSPS_REG_PN, |
| [serial_number] = IPSPS_REG_SN, |
| [hw_version] = IPSPS_REG_HW_VERSION, |
| [mode] = IPSPS_REG_MODE, |
| }; |
| |
| static ssize_t ipsps_string_show(struct device *dev, |
| struct device_attribute *devattr, |
| char *buf) |
| { |
| u8 reg; |
| int rc; |
| char *p; |
| char data[I2C_SMBUS_BLOCK_MAX + 1]; |
| struct i2c_client *client = to_i2c_client(dev->parent); |
| struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| |
| reg = ipsps_regs[attr->index]; |
| rc = i2c_smbus_read_block_data(client, reg, data); |
| if (rc < 0) |
| return rc; |
| |
| /* filled with printable characters, ending with # */ |
| p = memscan(data, '#', rc); |
| *p = '\0'; |
| |
| return snprintf(buf, PAGE_SIZE, "%s\n", data); |
| } |
| |
| static ssize_t ipsps_fw_version_show(struct device *dev, |
| struct device_attribute *devattr, |
| char *buf) |
| { |
| u8 reg; |
| int rc; |
| u8 data[I2C_SMBUS_BLOCK_MAX] = { 0 }; |
| struct i2c_client *client = to_i2c_client(dev->parent); |
| struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| |
| reg = ipsps_regs[attr->index]; |
| rc = i2c_smbus_read_block_data(client, reg, data); |
| if (rc < 0) |
| return rc; |
| |
| if (rc != 6) |
| return -EPROTO; |
| |
| return snprintf(buf, PAGE_SIZE, "%u.%02u%u-%u.%02u\n", |
| data[1], data[2]/* < 100 */, data[3]/*< 10*/, |
| data[4], data[5]/* < 100 */); |
| } |
| |
| static ssize_t ipsps_mode_show(struct device *dev, |
| struct device_attribute *devattr, char *buf) |
| { |
| u8 reg; |
| int rc; |
| struct i2c_client *client = to_i2c_client(dev->parent); |
| struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| |
| reg = ipsps_regs[attr->index]; |
| rc = i2c_smbus_read_byte_data(client, reg); |
| if (rc < 0) |
| return rc; |
| |
| switch (rc) { |
| case MODE_ACTIVE: |
| return snprintf(buf, PAGE_SIZE, "[%s] %s %s\n", |
| MODE_ACTIVE_STRING, |
| MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING); |
| case MODE_STANDBY: |
| return snprintf(buf, PAGE_SIZE, "%s [%s] %s\n", |
| MODE_ACTIVE_STRING, |
| MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING); |
| case MODE_REDUNDANCY: |
| return snprintf(buf, PAGE_SIZE, "%s %s [%s]\n", |
| MODE_ACTIVE_STRING, |
| MODE_STANDBY_STRING, MODE_REDUNDANCY_STRING); |
| default: |
| return snprintf(buf, PAGE_SIZE, "unspecified\n"); |
| } |
| } |
| |
| static ssize_t ipsps_mode_store(struct device *dev, |
| struct device_attribute *devattr, |
| const char *buf, size_t count) |
| { |
| u8 reg; |
| int rc; |
| struct i2c_client *client = to_i2c_client(dev->parent); |
| struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); |
| |
| reg = ipsps_regs[attr->index]; |
| if (sysfs_streq(MODE_STANDBY_STRING, buf)) { |
| rc = i2c_smbus_write_byte_data(client, reg, |
| MODE_STANDBY); |
| if (rc < 0) |
| return rc; |
| return count; |
| } else if (sysfs_streq(MODE_ACTIVE_STRING, buf)) { |
| rc = i2c_smbus_write_byte_data(client, reg, |
| MODE_ACTIVE); |
| if (rc < 0) |
| return rc; |
| return count; |
| } |
| |
| return -EINVAL; |
| } |
| |
| static SENSOR_DEVICE_ATTR_RO(vendor, ipsps_string, vendor); |
| static SENSOR_DEVICE_ATTR_RO(model, ipsps_string, model); |
| static SENSOR_DEVICE_ATTR_RO(part_number, ipsps_string, part_number); |
| static SENSOR_DEVICE_ATTR_RO(serial_number, ipsps_string, serial_number); |
| static SENSOR_DEVICE_ATTR_RO(hw_version, ipsps_string, hw_version); |
| static SENSOR_DEVICE_ATTR_RO(fw_version, ipsps_fw_version, fw_version); |
| static SENSOR_DEVICE_ATTR_RW(mode, ipsps_mode, mode); |
| |
| static struct attribute *ipsps_attrs[] = { |
| &sensor_dev_attr_vendor.dev_attr.attr, |
| &sensor_dev_attr_model.dev_attr.attr, |
| &sensor_dev_attr_part_number.dev_attr.attr, |
| &sensor_dev_attr_serial_number.dev_attr.attr, |
| &sensor_dev_attr_hw_version.dev_attr.attr, |
| &sensor_dev_attr_fw_version.dev_attr.attr, |
| &sensor_dev_attr_mode.dev_attr.attr, |
| NULL, |
| }; |
| |
| ATTRIBUTE_GROUPS(ipsps); |
| |
| static struct pmbus_driver_info ipsps_info = { |
| .pages = 1, |
| .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | |
| PMBUS_HAVE_IIN | PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | |
| PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | |
| PMBUS_HAVE_TEMP3 | PMBUS_HAVE_STATUS_VOUT | |
| PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT | |
| PMBUS_HAVE_STATUS_TEMP | PMBUS_HAVE_STATUS_FAN12, |
| .groups = ipsps_groups, |
| }; |
| |
| static struct pmbus_platform_data ipsps_pdata = { |
| .flags = PMBUS_SKIP_STATUS_CHECK, |
| }; |
| |
| static int ipsps_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| client->dev.platform_data = &ipsps_pdata; |
| return pmbus_do_probe(client, id, &ipsps_info); |
| } |
| |
| static const struct i2c_device_id ipsps_id[] = { |
| { "ipsps1", 0 }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(i2c, ipsps_id); |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id ipsps_of_match[] = { |
| { .compatible = "inspur,ipsps1" }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(of, ipsps_of_match); |
| #endif |
| |
| static struct i2c_driver ipsps_driver = { |
| .driver = { |
| .name = "inspur-ipsps", |
| .of_match_table = of_match_ptr(ipsps_of_match), |
| }, |
| .probe = ipsps_probe, |
| .remove = pmbus_do_remove, |
| .id_table = ipsps_id, |
| }; |
| |
| module_i2c_driver(ipsps_driver); |
| |
| MODULE_AUTHOR("John Wang"); |
| MODULE_DESCRIPTION("PMBus driver for Inspur Power System power supplies"); |
| MODULE_LICENSE("GPL"); |