diff options
Diffstat (limited to 'drivers/hwmon/pmbus')
-rw-r--r-- | drivers/hwmon/pmbus/Kconfig | 18 | ||||
-rw-r--r-- | drivers/hwmon/pmbus/Makefile | 2 | ||||
-rw-r--r-- | drivers/hwmon/pmbus/ibm-cffps.c | 151 | ||||
-rw-r--r-- | drivers/hwmon/pmbus/lm25066.c | 47 | ||||
-rw-r--r-- | drivers/hwmon/pmbus/pmbus.h | 2 | ||||
-rw-r--r-- | drivers/hwmon/pmbus/pmbus_core.c | 292 | ||||
-rw-r--r-- | drivers/hwmon/pmbus/tps53679.c | 113 |
7 files changed, 598 insertions, 27 deletions
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 68d717a3fd59..40019325b517 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -37,6 +37,15 @@ config SENSORS_ADM1275 This driver can also be built as a module. If so, the module will be called adm1275. +config SENSORS_IBM_CFFPS + tristate "IBM Common Form Factor Power Supply" + help + If you say yes here you get hardware monitoring support for the IBM + Common Form Factor power supply. + + This driver can also be built as a module. If so, the module will + be called ibm-cffps. + config SENSORS_IR35221 tristate "Infineon IR35221" default n @@ -135,6 +144,15 @@ config SENSORS_TPS40422 This driver can also be built as a module. If so, the module will be called tps40422. +config SENSORS_TPS53679 + tristate "TI TPS53679" + help + If you say yes here you get hardware monitoring support for TI + TPS53679. + + This driver can also be built as a module. If so, the module will + be called tps53679. + config SENSORS_UCD9000 tristate "TI UCD90120, UCD90124, UCD90160, UCD9090, UCD90910" default n diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 75bb7ca619d9..459a6be3390e 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_PMBUS) += pmbus_core.o obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o obj-$(CONFIG_SENSORS_ADM1275) += adm1275.o +obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o obj-$(CONFIG_SENSORS_IR35221) += ir35221.o obj-$(CONFIG_SENSORS_LM25066) += lm25066.o obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o @@ -14,6 +15,7 @@ obj-$(CONFIG_SENSORS_MAX20751) += max20751.o obj-$(CONFIG_SENSORS_MAX34440) += max34440.o obj-$(CONFIG_SENSORS_MAX8688) += max8688.o obj-$(CONFIG_SENSORS_TPS40422) += tps40422.o +obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o diff --git a/drivers/hwmon/pmbus/ibm-cffps.c b/drivers/hwmon/pmbus/ibm-cffps.c new file mode 100644 index 000000000000..cb56da6834e5 --- /dev/null +++ b/drivers/hwmon/pmbus/ibm-cffps.c @@ -0,0 +1,151 @@ +/* + * Copyright 2017 IBM Corp. + * + * 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. + */ + +#include <linux/bitops.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/module.h> + +#include "pmbus.h" + +/* STATUS_MFR_SPECIFIC bits */ +#define CFFPS_MFR_FAN_FAULT BIT(0) +#define CFFPS_MFR_THERMAL_FAULT BIT(1) +#define CFFPS_MFR_OV_FAULT BIT(2) +#define CFFPS_MFR_UV_FAULT BIT(3) +#define CFFPS_MFR_PS_KILL BIT(4) +#define CFFPS_MFR_OC_FAULT BIT(5) +#define CFFPS_MFR_VAUX_FAULT BIT(6) +#define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7) + +static int ibm_cffps_read_byte_data(struct i2c_client *client, int page, + int reg) +{ + int rc, mfr; + + switch (reg) { + case PMBUS_STATUS_VOUT: + case PMBUS_STATUS_IOUT: + case PMBUS_STATUS_TEMPERATURE: + case PMBUS_STATUS_FAN_12: + rc = pmbus_read_byte_data(client, page, reg); + if (rc < 0) + return rc; + + mfr = pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfr < 0) + /* + * Return the status register instead of an error, + * since we successfully read status. + */ + return rc; + + /* Add MFR_SPECIFIC bits to the standard pmbus status regs. */ + if (reg == PMBUS_STATUS_FAN_12) { + if (mfr & CFFPS_MFR_FAN_FAULT) + rc |= PB_FAN_FAN1_FAULT; + } else if (reg == PMBUS_STATUS_TEMPERATURE) { + if (mfr & CFFPS_MFR_THERMAL_FAULT) + rc |= PB_TEMP_OT_FAULT; + } else if (reg == PMBUS_STATUS_VOUT) { + if (mfr & (CFFPS_MFR_OV_FAULT | CFFPS_MFR_VAUX_FAULT)) + rc |= PB_VOLTAGE_OV_FAULT; + if (mfr & CFFPS_MFR_UV_FAULT) + rc |= PB_VOLTAGE_UV_FAULT; + } else if (reg == PMBUS_STATUS_IOUT) { + if (mfr & CFFPS_MFR_OC_FAULT) + rc |= PB_IOUT_OC_FAULT; + if (mfr & CFFPS_MFR_CURRENT_SHARE_WARNING) + rc |= PB_CURRENT_SHARE_FAULT; + } + break; + default: + rc = -ENODATA; + break; + } + + return rc; +} + +static int ibm_cffps_read_word_data(struct i2c_client *client, int page, + int reg) +{ + int rc, mfr; + + switch (reg) { + case PMBUS_STATUS_WORD: + rc = pmbus_read_word_data(client, page, reg); + if (rc < 0) + return rc; + + mfr = pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfr < 0) + /* + * Return the status register instead of an error, + * since we successfully read status. + */ + return rc; + + if (mfr & CFFPS_MFR_PS_KILL) + rc |= PB_STATUS_OFF; + break; + default: + rc = -ENODATA; + break; + } + + return rc; +} + +static struct pmbus_driver_info ibm_cffps_info = { + .pages = 1, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + 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, + .read_byte_data = ibm_cffps_read_byte_data, + .read_word_data = ibm_cffps_read_word_data, +}; + +static int ibm_cffps_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &ibm_cffps_info); +} + +static const struct i2c_device_id ibm_cffps_id[] = { + { "ibm_cffps1", 1 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ibm_cffps_id); + +static const struct of_device_id ibm_cffps_of_match[] = { + { .compatible = "ibm,cffps1" }, + {} +}; +MODULE_DEVICE_TABLE(of, ibm_cffps_of_match); + +static struct i2c_driver ibm_cffps_driver = { + .driver = { + .name = "ibm-cffps", + .of_match_table = ibm_cffps_of_match, + }, + .probe = ibm_cffps_probe, + .remove = pmbus_do_remove, + .id_table = ibm_cffps_id, +}; + +module_i2c_driver(ibm_cffps_driver); + +MODULE_AUTHOR("Eddie James"); +MODULE_DESCRIPTION("PMBus driver for IBM Common Form Factor power supplies"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c index a3d912cd3b8d..10d17fb8f283 100644 --- a/drivers/hwmon/pmbus/lm25066.c +++ b/drivers/hwmon/pmbus/lm25066.c @@ -28,7 +28,7 @@ #include <linux/i2c.h> #include "pmbus.h" -enum chips { lm25056, lm25063, lm25066, lm5064, lm5066 }; +enum chips { lm25056, lm25063, lm25066, lm5064, lm5066, lm5066i }; #define LM25066_READ_VAUX 0xd0 #define LM25066_MFR_READ_IIN 0xd1 @@ -65,7 +65,7 @@ struct __coeff { #define PSC_CURRENT_IN_L (PSC_NUM_CLASSES) #define PSC_POWER_L (PSC_NUM_CLASSES + 1) -static struct __coeff lm25066_coeff[5][PSC_NUM_CLASSES + 2] = { +static struct __coeff lm25066_coeff[6][PSC_NUM_CLASSES + 2] = { [lm25056] = { [PSC_VOLTAGE_IN] = { .m = 16296, @@ -210,6 +210,41 @@ static struct __coeff lm25066_coeff[5][PSC_NUM_CLASSES + 2] = { .m = 16, }, }, + [lm5066i] = { + [PSC_VOLTAGE_IN] = { + .m = 4617, + .b = -140, + .R = -2, + }, + [PSC_VOLTAGE_OUT] = { + .m = 4602, + .b = 500, + .R = -2, + }, + [PSC_CURRENT_IN] = { + .m = 15076, + .b = -504, + .R = -2, + }, + [PSC_CURRENT_IN_L] = { + .m = 7645, + .b = 100, + .R = -2, + }, + [PSC_POWER] = { + .m = 1701, + .b = -4000, + .R = -3, + }, + [PSC_POWER_L] = { + .m = 861, + .b = -965, + .R = -3, + }, + [PSC_TEMPERATURE] = { + .m = 16, + }, + }, }; struct lm25066_data { @@ -250,6 +285,7 @@ static int lm25066_read_word_data(struct i2c_client *client, int page, int reg) ret = DIV_ROUND_CLOSEST(ret * 70, 453); break; case lm5066: + case lm5066i: /* VIN: 2.18 mV VAUX: 725 uV LSB */ ret = DIV_ROUND_CLOSEST(ret * 725, 2180); break; @@ -488,16 +524,18 @@ static int lm25066_probe(struct i2c_client *client, info->m[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].m; info->b[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].b; info->R[PSC_VOLTAGE_OUT] = coeff[PSC_VOLTAGE_OUT].R; - info->b[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].b; info->R[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].R; - info->b[PSC_POWER] = coeff[PSC_POWER].b; info->R[PSC_POWER] = coeff[PSC_POWER].R; if (config & LM25066_DEV_SETUP_CL) { info->m[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN_L].m; + info->b[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN_L].b; info->m[PSC_POWER] = coeff[PSC_POWER_L].m; + info->b[PSC_POWER] = coeff[PSC_POWER_L].b; } else { info->m[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].m; + info->b[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].b; info->m[PSC_POWER] = coeff[PSC_POWER].m; + info->b[PSC_POWER] = coeff[PSC_POWER].b; } return pmbus_do_probe(client, id, info); @@ -509,6 +547,7 @@ static const struct i2c_device_id lm25066_id[] = { {"lm25066", lm25066}, {"lm5064", lm5064}, {"lm5066", lm5066}, + {"lm5066i", lm5066i}, { } }; diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index bfcb13bae34b..4efa2bd4f6d8 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -341,7 +341,7 @@ enum pmbus_sensor_classes { #define PMBUS_HAVE_STATUS_VMON BIT(19) enum pmbus_data_format { linear = 0, direct, vid }; -enum vrm_version { vr11 = 0, vr12 }; +enum vrm_version { vr11 = 0, vr12, vr13 }; struct pmbus_driver_info { int pages; /* Total number of pages */ diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index f1eff6b6c798..302f0aef59de 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -19,6 +19,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include <linux/debugfs.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> @@ -101,6 +102,7 @@ struct pmbus_data { int num_attributes; struct attribute_group group; const struct attribute_group *groups[2]; + struct dentry *debugfs; /* debugfs device directory */ struct pmbus_sensor *sensors; @@ -112,12 +114,20 @@ struct pmbus_data { * A single status register covers multiple attributes, * so we keep them all together. */ - u8 status[PB_NUM_STATUS_REG]; - u8 status_register; + u16 status[PB_NUM_STATUS_REG]; + + bool has_status_word; /* device uses STATUS_WORD register */ + int (*read_status)(struct i2c_client *client, int page); u8 currpage; }; +struct pmbus_debugfs_entry { + struct i2c_client *client; + u8 page; + u8 reg; +}; + void pmbus_clear_cache(struct i2c_client *client) { struct pmbus_data *data = i2c_get_clientdata(client); @@ -324,7 +334,7 @@ static int pmbus_check_status_cml(struct i2c_client *client) struct pmbus_data *data = i2c_get_clientdata(client); int status, status2; - status = _pmbus_read_byte_data(client, -1, data->status_register); + status = data->read_status(client, -1); if (status < 0 || (status & PB_STATUS_CML)) { status2 = _pmbus_read_byte_data(client, -1, PMBUS_STATUS_CML); if (status2 < 0 || (status2 & PB_CML_FAULT_INVALID_COMMAND)) @@ -348,6 +358,23 @@ static bool pmbus_check_register(struct i2c_client *client, return rv >= 0; } +static bool pmbus_check_status_register(struct i2c_client *client, int page) +{ + int status; + struct pmbus_data *data = i2c_get_clientdata(client); + + status = data->read_status(client, page); + if (status >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK) && + (status & PB_STATUS_CML)) { + status = _pmbus_read_byte_data(client, -1, PMBUS_STATUS_CML); + if (status < 0 || (status & PB_CML_FAULT_INVALID_COMMAND)) + status = -EIO; + } + + pmbus_clear_fault_page(client, -1); + return status >= 0; +} + bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg) { return pmbus_check_register(client, _pmbus_read_byte_data, page, reg); @@ -394,8 +421,7 @@ static struct pmbus_data *pmbus_update_device(struct device *dev) for (i = 0; i < info->pages; i++) { data->status[PB_STATUS_BASE + i] - = _pmbus_read_byte_data(client, i, - data->status_register); + = data->read_status(client, i); for (j = 0; j < ARRAY_SIZE(pmbus_status); j++) { struct _pmbus_status *s = &pmbus_status[j]; @@ -531,6 +557,10 @@ static long pmbus_reg2data_vid(struct pmbus_data *data, if (val >= 0x01) rv = 250 + (val - 1) * 5; break; + case vr13: + if (val >= 0x01) + rv = 500 + (val - 1) * 10; + break; } return rv; } @@ -716,10 +746,10 @@ static int pmbus_get_boolean(struct pmbus_data *data, struct pmbus_boolean *b, { struct pmbus_sensor *s1 = b->s1; struct pmbus_sensor *s2 = b->s2; - u16 reg = (index >> 8) & 0xffff; - u8 mask = index & 0xff; + u16 reg = (index >> 16) & 0xffff; + u16 mask = index & 0xffff; int ret, status; - u8 regval; + u16 regval; status = data->status[reg]; if (status < 0) @@ -860,7 +890,7 @@ static int pmbus_add_boolean(struct pmbus_data *data, const char *name, const char *type, int seq, struct pmbus_sensor *s1, struct pmbus_sensor *s2, - u16 reg, u8 mask) + u16 reg, u16 mask) { struct pmbus_boolean *boolean; struct sensor_device_attribute *a; @@ -876,7 +906,7 @@ static int pmbus_add_boolean(struct pmbus_data *data, boolean->s1 = s1; boolean->s2 = s2; pmbus_attr_init(a, boolean->name, S_IRUGO, pmbus_show_boolean, NULL, - (reg << 8) | mask); + (reg << 16) | mask); return pmbus_add_attribute(data, &a->dev_attr.attr); } @@ -962,7 +992,7 @@ struct pmbus_limit_attr { */ struct pmbus_sensor_attr { u16 reg; /* sensor register */ - u8 gbit; /* generic status bit */ + u16 gbit; /* generic status bit */ u8 nlimit; /* # of limit registers */ enum pmbus_sensor_classes class;/* sensor class */ const char *label; /* sensor label */ @@ -1028,6 +1058,7 @@ static int pmbus_add_sensor_attrs_one(struct i2c_client *client, const struct pmbus_sensor_attr *attr) { struct pmbus_sensor *base; + bool upper = !!(attr->gbit & 0xff00); /* need to check STATUS_WORD */ int ret; if (attr->label) { @@ -1048,11 +1079,12 @@ static int pmbus_add_sensor_attrs_one(struct i2c_client *client, /* * Add generic alarm attribute only if there are no individual * alarm attributes, if there is a global alarm bit, and if - * the generic status register for this page is accessible. + * the generic status register (word or byte, depending on + * which global bit is set) for this page is accessible. */ if (!ret && attr->gbit && - pmbus_check_byte_register(client, page, - data->status_register)) { + (!upper || (upper && data->has_status_word)) && + pmbus_check_status_register(client, page)) { ret = pmbus_add_boolean(data, name, "alarm", index, NULL, NULL, PB_STATUS_BASE + page, @@ -1308,6 +1340,7 @@ static const struct pmbus_sensor_attr current_attributes[] = { .func = PMBUS_HAVE_IIN, .sfunc = PMBUS_HAVE_STATUS_INPUT, .sbase = PB_STATUS_INPUT_BASE, + .gbit = PB_STATUS_INPUT, .limit = iin_limit_attrs, .nlimit = ARRAY_SIZE(iin_limit_attrs), }, { @@ -1392,6 +1425,7 @@ static const struct pmbus_sensor_attr power_attributes[] = { .func = PMBUS_HAVE_PIN, .sfunc = PMBUS_HAVE_STATUS_INPUT, .sbase = PB_STATUS_INPUT_BASE, + .gbit = PB_STATUS_INPUT, .limit = pin_limit_attrs, .nlimit = ARRAY_SIZE(pin_limit_attrs), }, { @@ -1729,6 +1763,16 @@ static int pmbus_identify_common(struct i2c_client *client, return 0; } +static int pmbus_read_status_byte(struct i2c_client *client, int page) +{ + return _pmbus_read_byte_data(client, page, PMBUS_STATUS_BYTE); +} + +static int pmbus_read_status_word(struct i2c_client *client, int page) +{ + return _pmbus_read_word_data(client, page, PMBUS_STATUS_WORD); +} + static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, struct pmbus_driver_info *info) { @@ -1736,19 +1780,21 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, int page, ret; /* - * Some PMBus chips don't support PMBUS_STATUS_BYTE, so try - * to use PMBUS_STATUS_WORD instead if that is the case. + * Some PMBus chips don't support PMBUS_STATUS_WORD, so try + * to use PMBUS_STATUS_BYTE instead if that is the case. * Bail out if both registers are not supported. */ - data->status_register = PMBUS_STATUS_BYTE; - ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE); - if (ret < 0 || ret == 0xff) { - data->status_register = PMBUS_STATUS_WORD; - ret = i2c_smbus_read_word_data(client, PMBUS_STATUS_WORD); - if (ret < 0 || ret == 0xffff) { + data->read_status = pmbus_read_status_word; + ret = i2c_smbus_read_word_data(client, PMBUS_STATUS_WORD); + if (ret < 0 || ret == 0xffff) { + data->read_status = pmbus_read_status_byte; + ret = i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE); + if (ret < 0 || ret == 0xff) { dev_err(dev, "PMBus status register not found\n"); return -ENODEV; } + } else { + data->has_status_word = true; } /* Enable PEC if the controller supports it */ @@ -1859,6 +1905,184 @@ static int pmbus_regulator_register(struct pmbus_data *data) } #endif +static struct dentry *pmbus_debugfs_dir; /* pmbus debugfs directory */ + +#if IS_ENABLED(CONFIG_DEBUG_FS) +static int pmbus_debugfs_get(void *data, u64 *val) +{ + int rc; + struct pmbus_debugfs_entry *entry = data; + + rc = _pmbus_read_byte_data(entry->client, entry->page, entry->reg); + if (rc < 0) + return rc; + + *val = rc; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops, pmbus_debugfs_get, NULL, + "0x%02llx\n"); + +static int pmbus_debugfs_get_status(void *data, u64 *val) +{ + int rc; + struct pmbus_debugfs_entry *entry = data; + struct pmbus_data *pdata = i2c_get_clientdata(entry->client); + + rc = pdata->read_status(entry->client, entry->page); + if (rc < 0) + return rc; + + *val = rc; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(pmbus_debugfs_ops_status, pmbus_debugfs_get_status, + NULL, "0x%04llx\n"); + +static int pmbus_init_debugfs(struct i2c_client *client, + struct pmbus_data *data) +{ + int i, idx = 0; + char name[PMBUS_NAME_SIZE]; + struct pmbus_debugfs_entry *entries; + + if (!pmbus_debugfs_dir) + return -ENODEV; + + /* + * Create the debugfs directory for this device. Use the hwmon device + * name to avoid conflicts (hwmon numbers are globally unique). + */ + data->debugfs = debugfs_create_dir(dev_name(data->hwmon_dev), + pmbus_debugfs_dir); + if (IS_ERR_OR_NULL(data->debugfs)) { + data->debugfs = NULL; + return -ENODEV; + } + + /* Allocate the max possible entries we need. */ + entries = devm_kzalloc(data->dev, + sizeof(*entries) * (data->info->pages * 10), + GFP_KERNEL); + if (!entries) + return -ENOMEM; + + for (i = 0; i < data->info->pages; ++i) { + /* Check accessibility of status register if it's not page 0 */ + if (!i || pmbus_check_status_register(client, i)) { + /* No need to set reg as we have special read op. */ + entries[idx].client = client; + entries[idx].page = i; + scnprintf(name, PMBUS_NAME_SIZE, "status%d", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops_status); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_VOUT) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_VOUT; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_vout", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_IOUT) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_IOUT; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_iout", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_INPUT) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_INPUT; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_input", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_TEMP) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_TEMPERATURE; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_temp", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (pmbus_check_byte_register(client, i, PMBUS_STATUS_CML)) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_CML; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_cml", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (pmbus_check_byte_register(client, i, PMBUS_STATUS_OTHER)) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_OTHER; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_other", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (pmbus_check_byte_register(client, i, + PMBUS_STATUS_MFR_SPECIFIC)) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_MFR_SPECIFIC; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_mfr", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_FAN12) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_FAN_12; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_fan12", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + + if (data->info->func[i] & PMBUS_HAVE_STATUS_FAN34) { + entries[idx].client = client; + entries[idx].page = i; + entries[idx].reg = PMBUS_STATUS_FAN_34; + scnprintf(name, PMBUS_NAME_SIZE, "status%d_fan34", i); + debugfs_create_file(name, 0444, data->debugfs, + &entries[idx++], + &pmbus_debugfs_ops); + } + } + + return 0; +} +#else +static int pmbus_init_debugfs(struct i2c_client *client, + struct pmbus_data *data) +{ + return 0; +} +#endif /* IS_ENABLED(CONFIG_DEBUG_FS) */ + int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, struct pmbus_driver_info *info) { @@ -1918,6 +2142,10 @@ int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, if (ret) goto out_unregister; + ret = pmbus_init_debugfs(client, data); + if (ret) + dev_warn(dev, "Failed to register debugfs\n"); + return 0; out_unregister: @@ -1931,12 +2159,32 @@ EXPORT_SYMBOL_GPL(pmbus_do_probe); int pmbus_do_remove(struct i2c_client *client) { struct pmbus_data *data = i2c_get_clientdata(client); + + debugfs_remove_recursive(data->debugfs); + hwmon_device_unregister(data->hwmon_dev); kfree(data->group.attrs); return 0; } EXPORT_SYMBOL_GPL(pmbus_do_remove); +static int __init pmbus_core_init(void) +{ + pmbus_debugfs_dir = debugfs_create_dir("pmbus", NULL); + if (IS_ERR(pmbus_debugfs_dir)) + pmbus_debugfs_dir = NULL; + + return 0; +} + +static void __exit pmbus_core_exit(void) +{ + debugfs_remove_recursive(pmbus_debugfs_dir); +} + +module_init(pmbus_core_init); +module_exit(pmbus_core_exit); + MODULE_AUTHOR("Guenter Roeck"); MODULE_DESCRIPTION("PMBus core driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/tps53679.c b/drivers/hwmon/pmbus/tps53679.c new file mode 100644 index 000000000000..85b515cd9df0 --- /dev/null +++ b/drivers/hwmon/pmbus/tps53679.c @@ -0,0 +1,113 @@ +/* + * Hardware monitoring driver for Texas Instruments TPS53679 + * + * Copyright (c) 2017 Mellanox Technologies. All rights reserved. + * Copyright (c) 2017 Vadim Pasternak <vadimp@mellanox.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. + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include "pmbus.h" + +#define TPS53679_PROT_VR12_5MV 0x01 /* VR12.0 mode, 5-mV DAC */ +#define TPS53679_PROT_VR12_5_10MV 0x02 /* VR12.5 mode, 10-mV DAC */ +#define TPS53679_PROT_VR13_10MV 0x04 /* VR13.0 mode, 10-mV DAC */ +#define TPS53679_PROT_IMVP8_5MV 0x05 /* IMVP8 mode, 5-mV DAC */ +#define TPS53679_PROT_VR13_5MV 0x07 /* VR13.0 mode, 5-mV DAC */ +#define TPS53679_PAGE_NUM 2 + +static int tps53679_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + u8 vout_params; + int ret; + + /* Read the register with VOUT scaling value.*/ + ret = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (ret < 0) + return ret; + + vout_params = ret & GENMASK(4, 0); + + switch (vout_params) { + case TPS53679_PROT_VR13_10MV: + case TPS53679_PROT_VR12_5_10MV: + info->vrm_version = vr13; + break; + case TPS53679_PROT_VR13_5MV: + case TPS53679_PROT_VR12_5MV: + case TPS53679_PROT_IMVP8_5MV: + info->vrm_version = vr12; + break; + default: + return -EINVAL; + } + + return 0; +} + +static struct pmbus_driver_info tps53679_info = { + .pages = TPS53679_PAGE_NUM, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = vid, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT, + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT, + .identify = tps53679_identify, +}; + +static int tps53679_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &tps53679_info); +} + +static const struct i2c_device_id tps53679_id[] = { + {"tps53679", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, tps53679_id); + +static const struct of_device_id tps53679_of_match[] = { + {.compatible = "ti,tps53679"}, + {} +}; +MODULE_DEVICE_TABLE(of, tps53679_of_match); + +static struct i2c_driver tps53679_driver = { + .driver = { + .name = "tps53679", + .of_match_table = of_match_ptr(tps53679_of_match), + }, + .probe = tps53679_probe, + .remove = pmbus_do_remove, + .id_table = tps53679_id, +}; + +module_i2c_driver(tps53679_driver); + +MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); +MODULE_DESCRIPTION("PMBus driver for Texas Instruments TPS53679"); +MODULE_LICENSE("GPL"); |