diff options
Diffstat (limited to 'drivers/hwmon/pmbus')
| -rw-r--r-- | drivers/hwmon/pmbus/Kconfig | 124 | ||||
| -rw-r--r-- | drivers/hwmon/pmbus/Makefile | 15 | ||||
| -rw-r--r-- | drivers/hwmon/pmbus/adm1275.c | 397 | ||||
| -rw-r--r-- | drivers/hwmon/pmbus/lm25066.c | 530 | ||||
| -rw-r--r-- | drivers/hwmon/pmbus/ltc2978.c | 507 | ||||
| -rw-r--r-- | drivers/hwmon/pmbus/max16064.c | 127 | ||||
| -rw-r--r-- | drivers/hwmon/pmbus/max34440.c | 435 | ||||
| -rw-r--r-- | drivers/hwmon/pmbus/max8688.c | 204 | ||||
| -rw-r--r-- | drivers/hwmon/pmbus/pmbus.c | 217 | ||||
| -rw-r--r-- | drivers/hwmon/pmbus/pmbus.h | 387 | ||||
| -rw-r--r-- | drivers/hwmon/pmbus/pmbus_core.c | 1803 | ||||
| -rw-r--r-- | drivers/hwmon/pmbus/ucd9000.c | 246 | ||||
| -rw-r--r-- | drivers/hwmon/pmbus/ucd9200.c | 180 | ||||
| -rw-r--r-- | drivers/hwmon/pmbus/zl6100.c | 419 | 
14 files changed, 5591 insertions, 0 deletions
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig new file mode 100644 index 00000000000..39cc63edfbb --- /dev/null +++ b/drivers/hwmon/pmbus/Kconfig @@ -0,0 +1,124 @@ +# +# PMBus chip drivers configuration +# + +menuconfig PMBUS +	tristate "PMBus support" +	depends on I2C +	default n +	help +	  Say yes here if you want to enable PMBus support. + +	  This driver can also be built as a module. If so, the module will +	  be called pmbus_core. + +if PMBUS + +config SENSORS_PMBUS +	tristate "Generic PMBus devices" +	default y +	help +	  If you say yes here you get hardware monitoring support for generic +	  PMBus devices, including but not limited to ADP4000, BMR453, BMR454, +	  MDT040, NCP4200, NCP4208, PDT003, PDT006, PDT012, UDT020, TPS40400, +	  and TPS40422. + +	  This driver can also be built as a module. If so, the module will +	  be called pmbus. + +config SENSORS_ADM1275 +	tristate "Analog Devices ADM1275 and compatibles" +	default n +	help +	  If you say yes here you get hardware monitoring support for Analog +	  Devices ADM1075, ADM1275, and ADM1276 Hot-Swap Controller and Digital +	  Power Monitors. + +	  This driver can also be built as a module. If so, the module will +	  be called adm1275. + +config SENSORS_LM25066 +	tristate "National Semiconductor LM25066 and compatibles" +	default n +	help +	  If you say yes here you get hardware monitoring support for National +	  Semiconductor LM25056, LM25066, LM5064, and LM5066. + +	  This driver can also be built as a module. If so, the module will +	  be called lm25066. + +config SENSORS_LTC2978 +	tristate "Linear Technologies LTC2974, LTC2978, LTC3880, and LTC3883" +	default n +	help +	  If you say yes here you get hardware monitoring support for Linear +	  Technology LTC2974, LTC2978, LTC3880, and LTC3883. + +	  This driver can also be built as a module. If so, the module will +	  be called ltc2978. + +config SENSORS_MAX16064 +	tristate "Maxim MAX16064" +	default n +	help +	  If you say yes here you get hardware monitoring support for Maxim +	  MAX16064. + +	  This driver can also be built as a module. If so, the module will +	  be called max16064. + +config SENSORS_MAX34440 +	tristate "Maxim MAX34440 and compatibles" +	default n +	help +	  If you say yes here you get hardware monitoring support for Maxim +	  MAX34440, MAX34441, MAX34446, MAX34460, and MAX34461. + +	  This driver can also be built as a module. If so, the module will +	  be called max34440. + +config SENSORS_MAX8688 +	tristate "Maxim MAX8688" +	default n +	help +	  If you say yes here you get hardware monitoring support for Maxim +	  MAX8688. + +	  This driver can also be built as a module. If so, the module will +	  be called max8688. + +config SENSORS_UCD9000 +	tristate "TI UCD90120, UCD90124, UCD9090, UCD90910" +	default n +	help +	  If you say yes here you get hardware monitoring support for TI +	  UCD90120, UCD90124, UCD9090, UCD90910 Sequencer and System Health +	  Controllers. + +	  This driver can also be built as a module. If so, the module will +	  be called ucd9000. + +config SENSORS_UCD9200 +	tristate "TI UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, UCD9248" +	default n +	help +	  If you say yes here you get hardware monitoring support for TI +	  UCD9220, UCD9222, UCD9224, UCD9240, UCD9244, UCD9246, and UCD9248 +	  Digital PWM System Controllers. + +	  This driver can also be built as a module. If so, the module will +	  be called ucd9200. + +config SENSORS_ZL6100 +	tristate "Intersil ZL6100 and compatibles" +	default n +	help +	  If you say yes here you get hardware monitoring support for Intersil +	  ZL2004, ZL2005, ZL2006, ZL2008, ZL2105, ZL2106, ZL6100, ZL6105, +	  ZL9101M, and ZL9117M Digital DC/DC Controllers, as well as for +	  Ericsson BMR450, BMR451, BMR462, BMR463, and BMR464. + +	  This driver can also be built as a module. If so, the module will +	  be called zl6100. + +endif # PMBUS diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile new file mode 100644 index 00000000000..789376c85db --- /dev/null +++ b/drivers/hwmon/pmbus/Makefile @@ -0,0 +1,15 @@ +# +# Makefile for PMBus chip drivers. +# + +obj-$(CONFIG_PMBUS)		+= pmbus_core.o +obj-$(CONFIG_SENSORS_PMBUS)	+= pmbus.o +obj-$(CONFIG_SENSORS_ADM1275)	+= adm1275.o +obj-$(CONFIG_SENSORS_LM25066)	+= lm25066.o +obj-$(CONFIG_SENSORS_LTC2978)	+= ltc2978.o +obj-$(CONFIG_SENSORS_MAX16064)	+= max16064.o +obj-$(CONFIG_SENSORS_MAX34440)	+= max34440.o +obj-$(CONFIG_SENSORS_MAX8688)	+= max8688.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/adm1275.c b/drivers/hwmon/pmbus/adm1275.c new file mode 100644 index 00000000000..60aad9570f0 --- /dev/null +++ b/drivers/hwmon/pmbus/adm1275.c @@ -0,0 +1,397 @@ +/* + * Hardware monitoring driver for Analog Devices ADM1275 Hot-Swap Controller + * and Digital Power Monitor + * + * Copyright (c) 2011 Ericsson AB. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include "pmbus.h" + +enum chips { adm1075, adm1275, adm1276 }; + +#define ADM1275_PEAK_IOUT		0xd0 +#define ADM1275_PEAK_VIN		0xd1 +#define ADM1275_PEAK_VOUT		0xd2 +#define ADM1275_PMON_CONFIG		0xd4 + +#define ADM1275_VIN_VOUT_SELECT		(1 << 6) +#define ADM1275_VRANGE			(1 << 5) +#define ADM1075_IRANGE_50		(1 << 4) +#define ADM1075_IRANGE_25		(1 << 3) +#define ADM1075_IRANGE_MASK		((1 << 3) | (1 << 4)) + +#define ADM1275_IOUT_WARN2_LIMIT	0xd7 +#define ADM1275_DEVICE_CONFIG		0xd8 + +#define ADM1275_IOUT_WARN2_SELECT	(1 << 4) + +#define ADM1276_PEAK_PIN		0xda + +#define ADM1275_MFR_STATUS_IOUT_WARN2	(1 << 0) + +#define ADM1075_READ_VAUX		0xdd +#define ADM1075_VAUX_OV_WARN_LIMIT	0xde +#define ADM1075_VAUX_UV_WARN_LIMIT	0xdf +#define ADM1075_VAUX_STATUS		0xf6 + +#define ADM1075_VAUX_OV_WARN		(1<<7) +#define ADM1075_VAUX_UV_WARN		(1<<6) + +struct adm1275_data { +	int id; +	bool have_oc_fault; +	struct pmbus_driver_info info; +}; + +#define to_adm1275_data(x)  container_of(x, struct adm1275_data, info) + +static int adm1275_read_word_data(struct i2c_client *client, int page, int reg) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	const struct adm1275_data *data = to_adm1275_data(info); +	int ret = 0; + +	if (page) +		return -ENXIO; + +	switch (reg) { +	case PMBUS_IOUT_UC_FAULT_LIMIT: +		if (data->have_oc_fault) { +			ret = -ENXIO; +			break; +		} +		ret = pmbus_read_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT); +		break; +	case PMBUS_IOUT_OC_FAULT_LIMIT: +		if (!data->have_oc_fault) { +			ret = -ENXIO; +			break; +		} +		ret = pmbus_read_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT); +		break; +	case PMBUS_VOUT_OV_WARN_LIMIT: +		if (data->id != adm1075) { +			ret = -ENODATA; +			break; +		} +		ret = pmbus_read_word_data(client, 0, +					   ADM1075_VAUX_OV_WARN_LIMIT); +		break; +	case PMBUS_VOUT_UV_WARN_LIMIT: +		if (data->id != adm1075) { +			ret = -ENODATA; +			break; +		} +		ret = pmbus_read_word_data(client, 0, +					   ADM1075_VAUX_UV_WARN_LIMIT); +		break; +	case PMBUS_READ_VOUT: +		if (data->id != adm1075) { +			ret = -ENODATA; +			break; +		} +		ret = pmbus_read_word_data(client, 0, ADM1075_READ_VAUX); +		break; +	case PMBUS_VIRT_READ_IOUT_MAX: +		ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_IOUT); +		break; +	case PMBUS_VIRT_READ_VOUT_MAX: +		ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_VOUT); +		break; +	case PMBUS_VIRT_READ_VIN_MAX: +		ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_VIN); +		break; +	case PMBUS_VIRT_READ_PIN_MAX: +		if (data->id == adm1275) { +			ret = -ENXIO; +			break; +		} +		ret = pmbus_read_word_data(client, 0, ADM1276_PEAK_PIN); +		break; +	case PMBUS_VIRT_RESET_IOUT_HISTORY: +	case PMBUS_VIRT_RESET_VOUT_HISTORY: +	case PMBUS_VIRT_RESET_VIN_HISTORY: +		break; +	case PMBUS_VIRT_RESET_PIN_HISTORY: +		if (data->id == adm1275) +			ret = -ENXIO; +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static int adm1275_write_word_data(struct i2c_client *client, int page, int reg, +				   u16 word) +{ +	int ret; + +	if (page) +		return -ENXIO; + +	switch (reg) { +	case PMBUS_IOUT_UC_FAULT_LIMIT: +	case PMBUS_IOUT_OC_FAULT_LIMIT: +		ret = pmbus_write_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT, +					    word); +		break; +	case PMBUS_VIRT_RESET_IOUT_HISTORY: +		ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_IOUT, 0); +		break; +	case PMBUS_VIRT_RESET_VOUT_HISTORY: +		ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_VOUT, 0); +		break; +	case PMBUS_VIRT_RESET_VIN_HISTORY: +		ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_VIN, 0); +		break; +	case PMBUS_VIRT_RESET_PIN_HISTORY: +		ret = pmbus_write_word_data(client, 0, ADM1276_PEAK_PIN, 0); +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static int adm1275_read_byte_data(struct i2c_client *client, int page, int reg) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	const struct adm1275_data *data = to_adm1275_data(info); +	int mfr_status, ret; + +	if (page > 0) +		return -ENXIO; + +	switch (reg) { +	case PMBUS_STATUS_IOUT: +		ret = pmbus_read_byte_data(client, page, PMBUS_STATUS_IOUT); +		if (ret < 0) +			break; +		mfr_status = pmbus_read_byte_data(client, page, +						  PMBUS_STATUS_MFR_SPECIFIC); +		if (mfr_status < 0) { +			ret = mfr_status; +			break; +		} +		if (mfr_status & ADM1275_MFR_STATUS_IOUT_WARN2) { +			ret |= data->have_oc_fault ? +			  PB_IOUT_OC_FAULT : PB_IOUT_UC_FAULT; +		} +		break; +	case PMBUS_STATUS_VOUT: +		if (data->id != adm1075) { +			ret = -ENODATA; +			break; +		} +		ret = 0; +		mfr_status = pmbus_read_byte_data(client, 0, +						  ADM1075_VAUX_STATUS); +		if (mfr_status & ADM1075_VAUX_OV_WARN) +			ret |= PB_VOLTAGE_OV_WARNING; +		if (mfr_status & ADM1075_VAUX_UV_WARN) +			ret |= PB_VOLTAGE_UV_WARNING; +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static const struct i2c_device_id adm1275_id[] = { +	{ "adm1075", adm1075 }, +	{ "adm1275", adm1275 }, +	{ "adm1276", adm1276 }, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, adm1275_id); + +static int adm1275_probe(struct i2c_client *client, +			 const struct i2c_device_id *id) +{ +	u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; +	int config, device_config; +	int ret; +	struct pmbus_driver_info *info; +	struct adm1275_data *data; +	const struct i2c_device_id *mid; + +	if (!i2c_check_functionality(client->adapter, +				     I2C_FUNC_SMBUS_READ_BYTE_DATA +				     | I2C_FUNC_SMBUS_BLOCK_DATA)) +		return -ENODEV; + +	ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer); +	if (ret < 0) { +		dev_err(&client->dev, "Failed to read Manufacturer ID\n"); +		return ret; +	} +	if (ret != 3 || strncmp(block_buffer, "ADI", 3)) { +		dev_err(&client->dev, "Unsupported Manufacturer ID\n"); +		return -ENODEV; +	} + +	ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer); +	if (ret < 0) { +		dev_err(&client->dev, "Failed to read Manufacturer Model\n"); +		return ret; +	} +	for (mid = adm1275_id; mid->name[0]; mid++) { +		if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) +			break; +	} +	if (!mid->name[0]) { +		dev_err(&client->dev, "Unsupported device\n"); +		return -ENODEV; +	} + +	if (id->driver_data != mid->driver_data) +		dev_notice(&client->dev, +			   "Device mismatch: Configured %s, detected %s\n", +			   id->name, mid->name); + +	config = i2c_smbus_read_byte_data(client, ADM1275_PMON_CONFIG); +	if (config < 0) +		return config; + +	device_config = i2c_smbus_read_byte_data(client, ADM1275_DEVICE_CONFIG); +	if (device_config < 0) +		return device_config; + +	data = devm_kzalloc(&client->dev, sizeof(struct adm1275_data), +			    GFP_KERNEL); +	if (!data) +		return -ENOMEM; + +	data->id = mid->driver_data; + +	info = &data->info; + +	info->pages = 1; +	info->format[PSC_VOLTAGE_IN] = direct; +	info->format[PSC_VOLTAGE_OUT] = direct; +	info->format[PSC_CURRENT_OUT] = direct; +	info->m[PSC_CURRENT_OUT] = 807; +	info->b[PSC_CURRENT_OUT] = 20475; +	info->R[PSC_CURRENT_OUT] = -1; +	info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; + +	info->read_word_data = adm1275_read_word_data; +	info->read_byte_data = adm1275_read_byte_data; +	info->write_word_data = adm1275_write_word_data; + +	if (data->id == adm1075) { +		info->m[PSC_VOLTAGE_IN] = 27169; +		info->b[PSC_VOLTAGE_IN] = 0; +		info->R[PSC_VOLTAGE_IN] = -1; +		info->m[PSC_VOLTAGE_OUT] = 27169; +		info->b[PSC_VOLTAGE_OUT] = 0; +		info->R[PSC_VOLTAGE_OUT] = -1; +	} else if (config & ADM1275_VRANGE) { +		info->m[PSC_VOLTAGE_IN] = 19199; +		info->b[PSC_VOLTAGE_IN] = 0; +		info->R[PSC_VOLTAGE_IN] = -2; +		info->m[PSC_VOLTAGE_OUT] = 19199; +		info->b[PSC_VOLTAGE_OUT] = 0; +		info->R[PSC_VOLTAGE_OUT] = -2; +	} else { +		info->m[PSC_VOLTAGE_IN] = 6720; +		info->b[PSC_VOLTAGE_IN] = 0; +		info->R[PSC_VOLTAGE_IN] = -1; +		info->m[PSC_VOLTAGE_OUT] = 6720; +		info->b[PSC_VOLTAGE_OUT] = 0; +		info->R[PSC_VOLTAGE_OUT] = -1; +	} + +	if (device_config & ADM1275_IOUT_WARN2_SELECT) +		data->have_oc_fault = true; + +	switch (data->id) { +	case adm1075: +		info->format[PSC_POWER] = direct; +		info->b[PSC_POWER] = 0; +		info->R[PSC_POWER] = -1; +		switch (config & ADM1075_IRANGE_MASK) { +		case ADM1075_IRANGE_25: +			info->m[PSC_POWER] = 8549; +			info->m[PSC_CURRENT_OUT] = 806; +			break; +		case ADM1075_IRANGE_50: +			info->m[PSC_POWER] = 4279; +			info->m[PSC_CURRENT_OUT] = 404; +			break; +		default: +			dev_err(&client->dev, "Invalid input current range"); +			info->m[PSC_POWER] = 0; +			info->m[PSC_CURRENT_OUT] = 0; +			break; +		} +		info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_PIN +		  | PMBUS_HAVE_STATUS_INPUT; +		if (config & ADM1275_VIN_VOUT_SELECT) +			info->func[0] |= +			  PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; +		break; +	case adm1275: +		if (config & ADM1275_VIN_VOUT_SELECT) +			info->func[0] |= +			  PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; +		else +			info->func[0] |= +			  PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT; +		break; +	case adm1276: +		info->format[PSC_POWER] = direct; +		info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_PIN +		  | PMBUS_HAVE_STATUS_INPUT; +		if (config & ADM1275_VIN_VOUT_SELECT) +			info->func[0] |= +			  PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; +		if (config & ADM1275_VRANGE) { +			info->m[PSC_POWER] = 6043; +			info->b[PSC_POWER] = 0; +			info->R[PSC_POWER] = -2; +		} else { +			info->m[PSC_POWER] = 2115; +			info->b[PSC_POWER] = 0; +			info->R[PSC_POWER] = -1; +		} +		break; +	} + +	return pmbus_do_probe(client, id, info); +} + +static struct i2c_driver adm1275_driver = { +	.driver = { +		   .name = "adm1275", +		   }, +	.probe = adm1275_probe, +	.remove = pmbus_do_remove, +	.id_table = adm1275_id, +}; + +module_i2c_driver(adm1275_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1275 and compatibles"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/lm25066.c b/drivers/hwmon/pmbus/lm25066.c new file mode 100644 index 00000000000..a26b1d1d951 --- /dev/null +++ b/drivers/hwmon/pmbus/lm25066.c @@ -0,0 +1,530 @@ +/* + * Hardware monitoring driver for LM25056 / LM25063 / LM25066 / LM5064 / LM5066 + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2013 Guenter Roeck + * + * 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 "pmbus.h" + +enum chips { lm25056, lm25063, lm25066, lm5064, lm5066 }; + +#define LM25066_READ_VAUX		0xd0 +#define LM25066_MFR_READ_IIN		0xd1 +#define LM25066_MFR_READ_PIN		0xd2 +#define LM25066_MFR_IIN_OC_WARN_LIMIT	0xd3 +#define LM25066_MFR_PIN_OP_WARN_LIMIT	0xd4 +#define LM25066_READ_PIN_PEAK		0xd5 +#define LM25066_CLEAR_PIN_PEAK		0xd6 +#define LM25066_DEVICE_SETUP		0xd9 +#define LM25066_READ_AVG_VIN		0xdc +#define LM25066_READ_AVG_VOUT		0xdd +#define LM25066_READ_AVG_IIN		0xde +#define LM25066_READ_AVG_PIN		0xdf + +#define LM25066_DEV_SETUP_CL		(1 << 4)	/* Current limit */ + +/* LM25056 only */ + +#define LM25056_VAUX_OV_WARN_LIMIT	0xe3 +#define LM25056_VAUX_UV_WARN_LIMIT	0xe4 + +#define LM25056_MFR_STS_VAUX_OV_WARN	(1 << 1) +#define LM25056_MFR_STS_VAUX_UV_WARN	(1 << 0) + +/* LM25063 only */ + +#define LM25063_READ_VOUT_MAX		0xe5 +#define LM25063_READ_VOUT_MIN		0xe6 + +struct __coeff { +	short m, b, R; +}; + +#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] = { +	[lm25056] = { +		[PSC_VOLTAGE_IN] = { +			.m = 16296, +			.R = -2, +		}, +		[PSC_CURRENT_IN] = { +			.m = 13797, +			.R = -2, +		}, +		[PSC_CURRENT_IN_L] = { +			.m = 6726, +			.R = -2, +		}, +		[PSC_POWER] = { +			.m = 5501, +			.R = -3, +		}, +		[PSC_POWER_L] = { +			.m = 26882, +			.R = -4, +		}, +		[PSC_TEMPERATURE] = { +			.m = 1580, +			.b = -14500, +			.R = -2, +		}, +	}, +	[lm25066] = { +		[PSC_VOLTAGE_IN] = { +			.m = 22070, +			.R = -2, +		}, +		[PSC_VOLTAGE_OUT] = { +			.m = 22070, +			.R = -2, +		}, +		[PSC_CURRENT_IN] = { +			.m = 13661, +			.R = -2, +		}, +		[PSC_CURRENT_IN_L] = { +			.m = 6852, +			.R = -2, +		}, +		[PSC_POWER] = { +			.m = 736, +			.R = -2, +		}, +		[PSC_POWER_L] = { +			.m = 369, +			.R = -2, +		}, +		[PSC_TEMPERATURE] = { +			.m = 16, +		}, +	}, +	[lm25063] = { +		[PSC_VOLTAGE_IN] = { +			.m = 16000, +			.R = -2, +		}, +		[PSC_VOLTAGE_OUT] = { +			.m = 16000, +			.R = -2, +		}, +		[PSC_CURRENT_IN] = { +			.m = 10000, +			.R = -2, +		}, +		[PSC_CURRENT_IN_L] = { +			.m = 10000, +			.R = -2, +		}, +		[PSC_POWER] = { +			.m = 5000, +			.R = -3, +		}, +		[PSC_POWER_L] = { +			.m = 5000, +			.R = -3, +		}, +		[PSC_TEMPERATURE] = { +			.m = 15596, +			.R = -3, +		}, +	}, +	[lm5064] = { +		[PSC_VOLTAGE_IN] = { +			.m = 4611, +			.R = -2, +		}, +		[PSC_VOLTAGE_OUT] = { +			.m = 4621, +			.R = -2, +		}, +		[PSC_CURRENT_IN] = { +			.m = 10742, +			.R = -2, +		}, +		[PSC_CURRENT_IN_L] = { +			.m = 5456, +			.R = -2, +		}, +		[PSC_POWER] = { +			.m = 1204, +			.R = -3, +		}, +		[PSC_POWER_L] = { +			.m = 612, +			.R = -3, +		}, +		[PSC_TEMPERATURE] = { +			.m = 16, +		}, +	}, +	[lm5066] = { +		[PSC_VOLTAGE_IN] = { +			.m = 4587, +			.R = -2, +		}, +		[PSC_VOLTAGE_OUT] = { +			.m = 4587, +			.R = -2, +		}, +		[PSC_CURRENT_IN] = { +			.m = 10753, +			.R = -2, +		}, +		[PSC_CURRENT_IN_L] = { +			.m = 5405, +			.R = -2, +		}, +		[PSC_POWER] = { +			.m = 1204, +			.R = -3, +		}, +		[PSC_POWER_L] = { +			.m = 605, +			.R = -3, +		}, +		[PSC_TEMPERATURE] = { +			.m = 16, +		}, +	}, +}; + +struct lm25066_data { +	int id; +	u16 rlimit;			/* Maximum register value */ +	struct pmbus_driver_info info; +}; + +#define to_lm25066_data(x)  container_of(x, struct lm25066_data, info) + +static int lm25066_read_word_data(struct i2c_client *client, int page, int reg) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	const struct lm25066_data *data = to_lm25066_data(info); +	int ret; + +	switch (reg) { +	case PMBUS_VIRT_READ_VMON: +		ret = pmbus_read_word_data(client, 0, LM25066_READ_VAUX); +		if (ret < 0) +			break; +		/* Adjust returned value to match VIN coefficients */ +		switch (data->id) { +		case lm25056: +			/* VIN: 6.14 mV VAUX: 293 uV LSB */ +			ret = DIV_ROUND_CLOSEST(ret * 293, 6140); +			break; +		case lm25063: +			/* VIN: 6.25 mV VAUX: 200.0 uV LSB */ +			ret = DIV_ROUND_CLOSEST(ret * 20, 625); +			break; +		case lm25066: +			/* VIN: 4.54 mV VAUX: 283.2 uV LSB */ +			ret = DIV_ROUND_CLOSEST(ret * 2832, 45400); +			break; +		case lm5064: +			/* VIN: 4.53 mV VAUX: 700 uV LSB */ +			ret = DIV_ROUND_CLOSEST(ret * 70, 453); +			break; +		case lm5066: +			/* VIN: 2.18 mV VAUX: 725 uV LSB */ +			ret = DIV_ROUND_CLOSEST(ret * 725, 2180); +			break; +		} +		break; +	case PMBUS_READ_IIN: +		ret = pmbus_read_word_data(client, 0, LM25066_MFR_READ_IIN); +		break; +	case PMBUS_READ_PIN: +		ret = pmbus_read_word_data(client, 0, LM25066_MFR_READ_PIN); +		break; +	case PMBUS_IIN_OC_WARN_LIMIT: +		ret = pmbus_read_word_data(client, 0, +					   LM25066_MFR_IIN_OC_WARN_LIMIT); +		break; +	case PMBUS_PIN_OP_WARN_LIMIT: +		ret = pmbus_read_word_data(client, 0, +					   LM25066_MFR_PIN_OP_WARN_LIMIT); +		break; +	case PMBUS_VIRT_READ_VIN_AVG: +		ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_VIN); +		break; +	case PMBUS_VIRT_READ_VOUT_AVG: +		ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_VOUT); +		break; +	case PMBUS_VIRT_READ_IIN_AVG: +		ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_IIN); +		break; +	case PMBUS_VIRT_READ_PIN_AVG: +		ret = pmbus_read_word_data(client, 0, LM25066_READ_AVG_PIN); +		break; +	case PMBUS_VIRT_READ_PIN_MAX: +		ret = pmbus_read_word_data(client, 0, LM25066_READ_PIN_PEAK); +		break; +	case PMBUS_VIRT_RESET_PIN_HISTORY: +		ret = 0; +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static int lm25063_read_word_data(struct i2c_client *client, int page, int reg) +{ +	int ret; + +	switch (reg) { +	case PMBUS_VIRT_READ_VOUT_MAX: +		ret = pmbus_read_word_data(client, 0, LM25063_READ_VOUT_MAX); +		break; +	case PMBUS_VIRT_READ_VOUT_MIN: +		ret = pmbus_read_word_data(client, 0, LM25063_READ_VOUT_MIN); +		break; +	default: +		ret = lm25066_read_word_data(client, page, reg); +		break; +	} +	return ret; +} + +static int lm25056_read_word_data(struct i2c_client *client, int page, int reg) +{ +	int ret; + +	switch (reg) { +	case PMBUS_VIRT_VMON_UV_WARN_LIMIT: +		ret = pmbus_read_word_data(client, 0, +					   LM25056_VAUX_UV_WARN_LIMIT); +		if (ret < 0) +			break; +		/* Adjust returned value to match VIN coefficients */ +		ret = DIV_ROUND_CLOSEST(ret * 293, 6140); +		break; +	case PMBUS_VIRT_VMON_OV_WARN_LIMIT: +		ret = pmbus_read_word_data(client, 0, +					   LM25056_VAUX_OV_WARN_LIMIT); +		if (ret < 0) +			break; +		/* Adjust returned value to match VIN coefficients */ +		ret = DIV_ROUND_CLOSEST(ret * 293, 6140); +		break; +	default: +		ret = lm25066_read_word_data(client, page, reg); +		break; +	} +	return ret; +} + +static int lm25056_read_byte_data(struct i2c_client *client, int page, int reg) +{ +	int ret, s; + +	switch (reg) { +	case PMBUS_VIRT_STATUS_VMON: +		ret = pmbus_read_byte_data(client, 0, +					   PMBUS_STATUS_MFR_SPECIFIC); +		if (ret < 0) +			break; +		s = 0; +		if (ret & LM25056_MFR_STS_VAUX_UV_WARN) +			s |= PB_VOLTAGE_UV_WARNING; +		if (ret & LM25056_MFR_STS_VAUX_OV_WARN) +			s |= PB_VOLTAGE_OV_WARNING; +		ret = s; +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static int lm25066_write_word_data(struct i2c_client *client, int page, int reg, +				   u16 word) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	const struct lm25066_data *data = to_lm25066_data(info); +	int ret; + +	switch (reg) { +	case PMBUS_POUT_OP_FAULT_LIMIT: +	case PMBUS_POUT_OP_WARN_LIMIT: +	case PMBUS_VOUT_UV_WARN_LIMIT: +	case PMBUS_OT_FAULT_LIMIT: +	case PMBUS_OT_WARN_LIMIT: +	case PMBUS_IIN_OC_FAULT_LIMIT: +	case PMBUS_VIN_UV_WARN_LIMIT: +	case PMBUS_VIN_UV_FAULT_LIMIT: +	case PMBUS_VIN_OV_FAULT_LIMIT: +	case PMBUS_VIN_OV_WARN_LIMIT: +		word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); +		ret = pmbus_write_word_data(client, 0, reg, word); +		pmbus_clear_cache(client); +		break; +	case PMBUS_IIN_OC_WARN_LIMIT: +		word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); +		ret = pmbus_write_word_data(client, 0, +					    LM25066_MFR_IIN_OC_WARN_LIMIT, +					    word); +		pmbus_clear_cache(client); +		break; +	case PMBUS_PIN_OP_WARN_LIMIT: +		word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); +		ret = pmbus_write_word_data(client, 0, +					    LM25066_MFR_PIN_OP_WARN_LIMIT, +					    word); +		pmbus_clear_cache(client); +		break; +	case PMBUS_VIRT_VMON_UV_WARN_LIMIT: +		/* Adjust from VIN coefficients (for LM25056) */ +		word = DIV_ROUND_CLOSEST((int)word * 6140, 293); +		word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); +		ret = pmbus_write_word_data(client, 0, +					    LM25056_VAUX_UV_WARN_LIMIT, word); +		pmbus_clear_cache(client); +		break; +	case PMBUS_VIRT_VMON_OV_WARN_LIMIT: +		/* Adjust from VIN coefficients (for LM25056) */ +		word = DIV_ROUND_CLOSEST((int)word * 6140, 293); +		word = ((s16)word < 0) ? 0 : clamp_val(word, 0, data->rlimit); +		ret = pmbus_write_word_data(client, 0, +					    LM25056_VAUX_OV_WARN_LIMIT, word); +		pmbus_clear_cache(client); +		break; +	case PMBUS_VIRT_RESET_PIN_HISTORY: +		ret = pmbus_write_byte(client, 0, LM25066_CLEAR_PIN_PEAK); +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static int lm25066_probe(struct i2c_client *client, +			  const struct i2c_device_id *id) +{ +	int config; +	struct lm25066_data *data; +	struct pmbus_driver_info *info; +	struct __coeff *coeff; + +	if (!i2c_check_functionality(client->adapter, +				     I2C_FUNC_SMBUS_READ_BYTE_DATA)) +		return -ENODEV; + +	data = devm_kzalloc(&client->dev, sizeof(struct lm25066_data), +			    GFP_KERNEL); +	if (!data) +		return -ENOMEM; + +	config = i2c_smbus_read_byte_data(client, LM25066_DEVICE_SETUP); +	if (config < 0) +		return config; + +	data->id = id->driver_data; +	info = &data->info; + +	info->pages = 1; +	info->format[PSC_VOLTAGE_IN] = direct; +	info->format[PSC_VOLTAGE_OUT] = direct; +	info->format[PSC_CURRENT_IN] = direct; +	info->format[PSC_TEMPERATURE] = direct; +	info->format[PSC_POWER] = direct; + +	info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VMON +	  | PMBUS_HAVE_PIN | PMBUS_HAVE_IIN | PMBUS_HAVE_STATUS_INPUT +	  | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + +	if (data->id == lm25056) { +		info->func[0] |= PMBUS_HAVE_STATUS_VMON; +		info->read_word_data = lm25056_read_word_data; +		info->read_byte_data = lm25056_read_byte_data; +		data->rlimit = 0x0fff; +	} else if (data->id == lm25063) { +		info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_POUT; +		info->read_word_data = lm25063_read_word_data; +		data->rlimit = 0xffff; +	} else { +		info->func[0] |= PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; +		info->read_word_data = lm25066_read_word_data; +		data->rlimit = 0x0fff; +	} +	info->write_word_data = lm25066_write_word_data; + +	coeff = &lm25066_coeff[data->id][0]; +	info->m[PSC_TEMPERATURE] = coeff[PSC_TEMPERATURE].m; +	info->b[PSC_TEMPERATURE] = coeff[PSC_TEMPERATURE].b; +	info->R[PSC_TEMPERATURE] = coeff[PSC_TEMPERATURE].R; +	info->m[PSC_VOLTAGE_IN] = coeff[PSC_VOLTAGE_IN].m; +	info->b[PSC_VOLTAGE_IN] = coeff[PSC_VOLTAGE_IN].b; +	info->R[PSC_VOLTAGE_IN] = coeff[PSC_VOLTAGE_IN].R; +	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->m[PSC_POWER] = coeff[PSC_POWER_L].m; +	} else { +		info->m[PSC_CURRENT_IN] = coeff[PSC_CURRENT_IN].m; +		info->m[PSC_POWER] = coeff[PSC_POWER].m; +	} + +	return pmbus_do_probe(client, id, info); +} + +static const struct i2c_device_id lm25066_id[] = { +	{"lm25056", lm25056}, +	{"lm25063", lm25063}, +	{"lm25066", lm25066}, +	{"lm5064", lm5064}, +	{"lm5066", lm5066}, +	{ } +}; + +MODULE_DEVICE_TABLE(i2c, lm25066_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver lm25066_driver = { +	.driver = { +		   .name = "lm25066", +		   }, +	.probe = lm25066_probe, +	.remove = pmbus_do_remove, +	.id_table = lm25066_id, +}; + +module_i2c_driver(lm25066_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for LM25066 and compatible chips"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c new file mode 100644 index 00000000000..e24ed521051 --- /dev/null +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -0,0 +1,507 @@ +/* + * Hardware monitoring driver for LTC2974, LTC2977, LTC2978, LTC3880, + * LTC3883, and LTM4676 + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2013, 2014 Guenter Roeck + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include "pmbus.h" + +enum chips { ltc2974, ltc2977, ltc2978, ltc3880, ltc3883, ltm4676 }; + +/* Common for all chips */ +#define LTC2978_MFR_VOUT_PEAK		0xdd +#define LTC2978_MFR_VIN_PEAK		0xde +#define LTC2978_MFR_TEMPERATURE_PEAK	0xdf +#define LTC2978_MFR_SPECIAL_ID		0xe7 + +/* LTC2974, LCT2977, and LTC2978 */ +#define LTC2978_MFR_VOUT_MIN		0xfb +#define LTC2978_MFR_VIN_MIN		0xfc +#define LTC2978_MFR_TEMPERATURE_MIN	0xfd + +/* LTC2974 only */ +#define LTC2974_MFR_IOUT_PEAK		0xd7 +#define LTC2974_MFR_IOUT_MIN		0xd8 + +/* LTC3880, LTC3883, and LTM4676 */ +#define LTC3880_MFR_IOUT_PEAK		0xd7 +#define LTC3880_MFR_CLEAR_PEAKS		0xe3 +#define LTC3880_MFR_TEMPERATURE2_PEAK	0xf4 + +/* LTC3883 only */ +#define LTC3883_MFR_IIN_PEAK		0xe1 + +#define LTC2974_ID_REV1			0x0212 +#define LTC2974_ID_REV2			0x0213 +#define LTC2977_ID			0x0130 +#define LTC2978_ID_REV1			0x0121 +#define LTC2978_ID_REV2			0x0122 +#define LTC2978A_ID			0x0124 +#define LTC3880_ID			0x4000 +#define LTC3880_ID_MASK			0xff00 +#define LTC3883_ID			0x4300 +#define LTC3883_ID_MASK			0xff00 +#define LTM4676_ID			0x4480	/* datasheet claims 0x440X */ +#define LTM4676_ID_MASK			0xfff0 + +#define LTC2974_NUM_PAGES		4 +#define LTC2978_NUM_PAGES		8 +#define LTC3880_NUM_PAGES		2 +#define LTC3883_NUM_PAGES		1 + +/* + * LTC2978 clears peak data whenever the CLEAR_FAULTS command is executed, which + * happens pretty much each time chip data is updated. Raw peak data therefore + * does not provide much value. To be able to provide useful peak data, keep an + * internal cache of measured peak data, which is only cleared if an explicit + * "clear peak" command is executed for the sensor in question. + */ + +struct ltc2978_data { +	enum chips id; +	u16 vin_min, vin_max; +	u16 temp_min[LTC2974_NUM_PAGES], temp_max[LTC2974_NUM_PAGES]; +	u16 vout_min[LTC2978_NUM_PAGES], vout_max[LTC2978_NUM_PAGES]; +	u16 iout_min[LTC2974_NUM_PAGES], iout_max[LTC2974_NUM_PAGES]; +	u16 iin_max; +	u16 temp2_max; +	struct pmbus_driver_info info; +}; + +#define to_ltc2978_data(x)  container_of(x, struct ltc2978_data, info) + +static inline int lin11_to_val(int data) +{ +	s16 e = ((s16)data) >> 11; +	s32 m = (((s16)(data << 5)) >> 5); + +	/* +	 * mantissa is 10 bit + sign, exponent adds up to 15 bit. +	 * Add 6 bit to exponent for maximum accuracy (10 + 15 + 6 = 31). +	 */ +	e += 6; +	return (e < 0 ? m >> -e : m << e); +} + +static int ltc2978_read_word_data_common(struct i2c_client *client, int page, +					 int reg) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	struct ltc2978_data *data = to_ltc2978_data(info); +	int ret; + +	switch (reg) { +	case PMBUS_VIRT_READ_VIN_MAX: +		ret = pmbus_read_word_data(client, page, LTC2978_MFR_VIN_PEAK); +		if (ret >= 0) { +			if (lin11_to_val(ret) > lin11_to_val(data->vin_max)) +				data->vin_max = ret; +			ret = data->vin_max; +		} +		break; +	case PMBUS_VIRT_READ_VOUT_MAX: +		ret = pmbus_read_word_data(client, page, LTC2978_MFR_VOUT_PEAK); +		if (ret >= 0) { +			/* +			 * VOUT is 16 bit unsigned with fixed exponent, +			 * so we can compare it directly +			 */ +			if (ret > data->vout_max[page]) +				data->vout_max[page] = ret; +			ret = data->vout_max[page]; +		} +		break; +	case PMBUS_VIRT_READ_TEMP_MAX: +		ret = pmbus_read_word_data(client, page, +					   LTC2978_MFR_TEMPERATURE_PEAK); +		if (ret >= 0) { +			if (lin11_to_val(ret) +			    > lin11_to_val(data->temp_max[page])) +				data->temp_max[page] = ret; +			ret = data->temp_max[page]; +		} +		break; +	case PMBUS_VIRT_RESET_VOUT_HISTORY: +	case PMBUS_VIRT_RESET_VIN_HISTORY: +	case PMBUS_VIRT_RESET_TEMP_HISTORY: +		ret = 0; +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static int ltc2978_read_word_data(struct i2c_client *client, int page, int reg) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	struct ltc2978_data *data = to_ltc2978_data(info); +	int ret; + +	switch (reg) { +	case PMBUS_VIRT_READ_VIN_MIN: +		ret = pmbus_read_word_data(client, page, LTC2978_MFR_VIN_MIN); +		if (ret >= 0) { +			if (lin11_to_val(ret) < lin11_to_val(data->vin_min)) +				data->vin_min = ret; +			ret = data->vin_min; +		} +		break; +	case PMBUS_VIRT_READ_VOUT_MIN: +		ret = pmbus_read_word_data(client, page, LTC2978_MFR_VOUT_MIN); +		if (ret >= 0) { +			/* +			 * VOUT_MIN is known to not be supported on some lots +			 * of LTC2978 revision 1, and will return the maximum +			 * possible voltage if read. If VOUT_MAX is valid and +			 * lower than the reading of VOUT_MIN, use it instead. +			 */ +			if (data->vout_max[page] && ret > data->vout_max[page]) +				ret = data->vout_max[page]; +			if (ret < data->vout_min[page]) +				data->vout_min[page] = ret; +			ret = data->vout_min[page]; +		} +		break; +	case PMBUS_VIRT_READ_TEMP_MIN: +		ret = pmbus_read_word_data(client, page, +					   LTC2978_MFR_TEMPERATURE_MIN); +		if (ret >= 0) { +			if (lin11_to_val(ret) +			    < lin11_to_val(data->temp_min[page])) +				data->temp_min[page] = ret; +			ret = data->temp_min[page]; +		} +		break; +	case PMBUS_VIRT_READ_IOUT_MAX: +	case PMBUS_VIRT_RESET_IOUT_HISTORY: +	case PMBUS_VIRT_READ_TEMP2_MAX: +	case PMBUS_VIRT_RESET_TEMP2_HISTORY: +		ret = -ENXIO; +		break; +	default: +		ret = ltc2978_read_word_data_common(client, page, reg); +		break; +	} +	return ret; +} + +static int ltc2974_read_word_data(struct i2c_client *client, int page, int reg) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	struct ltc2978_data *data = to_ltc2978_data(info); +	int ret; + +	switch (reg) { +	case PMBUS_VIRT_READ_IOUT_MAX: +		ret = pmbus_read_word_data(client, page, LTC2974_MFR_IOUT_PEAK); +		if (ret >= 0) { +			if (lin11_to_val(ret) +			    > lin11_to_val(data->iout_max[page])) +				data->iout_max[page] = ret; +			ret = data->iout_max[page]; +		} +		break; +	case PMBUS_VIRT_READ_IOUT_MIN: +		ret = pmbus_read_word_data(client, page, LTC2974_MFR_IOUT_MIN); +		if (ret >= 0) { +			if (lin11_to_val(ret) +			    < lin11_to_val(data->iout_min[page])) +				data->iout_min[page] = ret; +			ret = data->iout_min[page]; +		} +		break; +	case PMBUS_VIRT_RESET_IOUT_HISTORY: +		ret = 0; +		break; +	default: +		ret = ltc2978_read_word_data(client, page, reg); +		break; +	} +	return ret; +} + +static int ltc3880_read_word_data(struct i2c_client *client, int page, int reg) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	struct ltc2978_data *data = to_ltc2978_data(info); +	int ret; + +	switch (reg) { +	case PMBUS_VIRT_READ_IOUT_MAX: +		ret = pmbus_read_word_data(client, page, LTC3880_MFR_IOUT_PEAK); +		if (ret >= 0) { +			if (lin11_to_val(ret) +			    > lin11_to_val(data->iout_max[page])) +				data->iout_max[page] = ret; +			ret = data->iout_max[page]; +		} +		break; +	case PMBUS_VIRT_READ_TEMP2_MAX: +		ret = pmbus_read_word_data(client, page, +					   LTC3880_MFR_TEMPERATURE2_PEAK); +		if (ret >= 0) { +			if (lin11_to_val(ret) > lin11_to_val(data->temp2_max)) +				data->temp2_max = ret; +			ret = data->temp2_max; +		} +		break; +	case PMBUS_VIRT_READ_VIN_MIN: +	case PMBUS_VIRT_READ_VOUT_MIN: +	case PMBUS_VIRT_READ_TEMP_MIN: +		ret = -ENXIO; +		break; +	case PMBUS_VIRT_RESET_IOUT_HISTORY: +	case PMBUS_VIRT_RESET_TEMP2_HISTORY: +		ret = 0; +		break; +	default: +		ret = ltc2978_read_word_data_common(client, page, reg); +		break; +	} +	return ret; +} + +static int ltc3883_read_word_data(struct i2c_client *client, int page, int reg) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	struct ltc2978_data *data = to_ltc2978_data(info); +	int ret; + +	switch (reg) { +	case PMBUS_VIRT_READ_IIN_MAX: +		ret = pmbus_read_word_data(client, page, LTC3883_MFR_IIN_PEAK); +		if (ret >= 0) { +			if (lin11_to_val(ret) +			    > lin11_to_val(data->iin_max)) +				data->iin_max = ret; +			ret = data->iin_max; +		} +		break; +	case PMBUS_VIRT_RESET_IIN_HISTORY: +		ret = 0; +		break; +	default: +		ret = ltc3880_read_word_data(client, page, reg); +		break; +	} +	return ret; +} + +static int ltc2978_clear_peaks(struct i2c_client *client, int page, +			       enum chips id) +{ +	int ret; + +	if (id == ltc3880 || id == ltc3883) +		ret = pmbus_write_byte(client, 0, LTC3880_MFR_CLEAR_PEAKS); +	else +		ret = pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); + +	return ret; +} + +static int ltc2978_write_word_data(struct i2c_client *client, int page, +				    int reg, u16 word) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	struct ltc2978_data *data = to_ltc2978_data(info); +	int ret; + +	switch (reg) { +	case PMBUS_VIRT_RESET_IIN_HISTORY: +		data->iin_max = 0x7c00; +		ret = ltc2978_clear_peaks(client, page, data->id); +		break; +	case PMBUS_VIRT_RESET_IOUT_HISTORY: +		data->iout_max[page] = 0x7c00; +		data->iout_min[page] = 0xfbff; +		ret = ltc2978_clear_peaks(client, page, data->id); +		break; +	case PMBUS_VIRT_RESET_TEMP2_HISTORY: +		data->temp2_max = 0x7c00; +		ret = ltc2978_clear_peaks(client, page, data->id); +		break; +	case PMBUS_VIRT_RESET_VOUT_HISTORY: +		data->vout_min[page] = 0xffff; +		data->vout_max[page] = 0; +		ret = ltc2978_clear_peaks(client, page, data->id); +		break; +	case PMBUS_VIRT_RESET_VIN_HISTORY: +		data->vin_min = 0x7bff; +		data->vin_max = 0x7c00; +		ret = ltc2978_clear_peaks(client, page, data->id); +		break; +	case PMBUS_VIRT_RESET_TEMP_HISTORY: +		data->temp_min[page] = 0x7bff; +		data->temp_max[page] = 0x7c00; +		ret = ltc2978_clear_peaks(client, page, data->id); +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static const struct i2c_device_id ltc2978_id[] = { +	{"ltc2974", ltc2974}, +	{"ltc2977", ltc2977}, +	{"ltc2978", ltc2978}, +	{"ltc3880", ltc3880}, +	{"ltc3883", ltc3883}, +	{"ltm4676", ltm4676}, +	{} +}; +MODULE_DEVICE_TABLE(i2c, ltc2978_id); + +static int ltc2978_probe(struct i2c_client *client, +			 const struct i2c_device_id *id) +{ +	int chip_id, i; +	struct ltc2978_data *data; +	struct pmbus_driver_info *info; + +	if (!i2c_check_functionality(client->adapter, +				     I2C_FUNC_SMBUS_READ_WORD_DATA)) +		return -ENODEV; + +	data = devm_kzalloc(&client->dev, sizeof(struct ltc2978_data), +			    GFP_KERNEL); +	if (!data) +		return -ENOMEM; + +	chip_id = i2c_smbus_read_word_data(client, LTC2978_MFR_SPECIAL_ID); +	if (chip_id < 0) +		return chip_id; + +	if (chip_id == LTC2974_ID_REV1 || chip_id == LTC2974_ID_REV2) { +		data->id = ltc2974; +	} else if (chip_id == LTC2977_ID) { +		data->id = ltc2977; +	} else if (chip_id == LTC2978_ID_REV1 || chip_id == LTC2978_ID_REV2 || +		   chip_id == LTC2978A_ID) { +		data->id = ltc2978; +	} else if ((chip_id & LTC3880_ID_MASK) == LTC3880_ID) { +		data->id = ltc3880; +	} else if ((chip_id & LTC3883_ID_MASK) == LTC3883_ID) { +		data->id = ltc3883; +	} else if ((chip_id & LTM4676_ID_MASK) == LTM4676_ID) { +		data->id = ltm4676; +	} else { +		dev_err(&client->dev, "Unsupported chip ID 0x%x\n", chip_id); +		return -ENODEV; +	} +	if (data->id != id->driver_data) +		dev_warn(&client->dev, +			 "Device mismatch: Configured %s, detected %s\n", +			 id->name, +			 ltc2978_id[data->id].name); + +	info = &data->info; +	info->write_word_data = ltc2978_write_word_data; + +	data->vin_min = 0x7bff; +	data->vin_max = 0x7c00; +	for (i = 0; i < ARRAY_SIZE(data->vout_min); i++) +		data->vout_min[i] = 0xffff; +	for (i = 0; i < ARRAY_SIZE(data->iout_min); i++) +		data->iout_min[i] = 0xfbff; +	for (i = 0; i < ARRAY_SIZE(data->iout_max); i++) +		data->iout_max[i] = 0x7c00; +	for (i = 0; i < ARRAY_SIZE(data->temp_min); i++) +		data->temp_min[i] = 0x7bff; +	for (i = 0; i < ARRAY_SIZE(data->temp_max); i++) +		data->temp_max[i] = 0x7c00; +	data->temp2_max = 0x7c00; + +	switch (data->id) { +	case ltc2974: +		info->read_word_data = ltc2974_read_word_data; +		info->pages = LTC2974_NUM_PAGES; +		info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT +		  | PMBUS_HAVE_TEMP2; +		for (i = 0; i < info->pages; i++) { +			info->func[i] |= PMBUS_HAVE_VOUT +			  | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_POUT +			  | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP +			  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; +		} +		break; +	case ltc2977: +	case ltc2978: +		info->read_word_data = ltc2978_read_word_data; +		info->pages = LTC2978_NUM_PAGES; +		info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT +		  | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; +		for (i = 1; i < LTC2978_NUM_PAGES; i++) { +			info->func[i] = PMBUS_HAVE_VOUT +			  | PMBUS_HAVE_STATUS_VOUT; +		} +		break; +	case ltc3880: +	case ltm4676: +		info->read_word_data = ltc3880_read_word_data; +		info->pages = LTC3880_NUM_PAGES; +		info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN +		  | PMBUS_HAVE_STATUS_INPUT +		  | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT +		  | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP +		  | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; +		info->func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT +		  | PMBUS_HAVE_POUT +		  | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; +		break; +	case ltc3883: +		info->read_word_data = ltc3883_read_word_data; +		info->pages = LTC3883_NUM_PAGES; +		info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN +		  | PMBUS_HAVE_STATUS_INPUT +		  | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT +		  | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP +		  | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; +		break; +	default: +		return -ENODEV; +	} +	return pmbus_do_probe(client, id, info); +} + +/* This is the driver that will be inserted */ +static struct i2c_driver ltc2978_driver = { +	.driver = { +		   .name = "ltc2978", +		   }, +	.probe = ltc2978_probe, +	.remove = pmbus_do_remove, +	.id_table = ltc2978_id, +}; + +module_i2c_driver(ltc2978_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for LTC2974, LTC2978, LTC3880, LTC3883, and LTM4676"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/max16064.c b/drivers/hwmon/pmbus/max16064.c new file mode 100644 index 00000000000..fa237a3c329 --- /dev/null +++ b/drivers/hwmon/pmbus/max16064.c @@ -0,0 +1,127 @@ +/* + * Hardware monitoring driver for Maxim MAX16064 + * + * Copyright (c) 2011 Ericsson AB. + * + * 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/i2c.h> +#include "pmbus.h" + +#define MAX16064_MFR_VOUT_PEAK		0xd4 +#define MAX16064_MFR_TEMPERATURE_PEAK	0xd6 + +static int max16064_read_word_data(struct i2c_client *client, int page, int reg) +{ +	int ret; + +	switch (reg) { +	case PMBUS_VIRT_READ_VOUT_MAX: +		ret = pmbus_read_word_data(client, page, +					   MAX16064_MFR_VOUT_PEAK); +		break; +	case PMBUS_VIRT_READ_TEMP_MAX: +		ret = pmbus_read_word_data(client, page, +					   MAX16064_MFR_TEMPERATURE_PEAK); +		break; +	case PMBUS_VIRT_RESET_VOUT_HISTORY: +	case PMBUS_VIRT_RESET_TEMP_HISTORY: +		ret = 0; +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static int max16064_write_word_data(struct i2c_client *client, int page, +				    int reg, u16 word) +{ +	int ret; + +	switch (reg) { +	case PMBUS_VIRT_RESET_VOUT_HISTORY: +		ret = pmbus_write_word_data(client, page, +					    MAX16064_MFR_VOUT_PEAK, 0); +		break; +	case PMBUS_VIRT_RESET_TEMP_HISTORY: +		ret = pmbus_write_word_data(client, page, +					    MAX16064_MFR_TEMPERATURE_PEAK, +					    0xffff); +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static struct pmbus_driver_info max16064_info = { +	.pages = 4, +	.format[PSC_VOLTAGE_IN] = direct, +	.format[PSC_VOLTAGE_OUT] = direct, +	.format[PSC_TEMPERATURE] = direct, +	.m[PSC_VOLTAGE_IN] = 19995, +	.b[PSC_VOLTAGE_IN] = 0, +	.R[PSC_VOLTAGE_IN] = -1, +	.m[PSC_VOLTAGE_OUT] = 19995, +	.b[PSC_VOLTAGE_OUT] = 0, +	.R[PSC_VOLTAGE_OUT] = -1, +	.m[PSC_TEMPERATURE] = -7612, +	.b[PSC_TEMPERATURE] = 335, +	.R[PSC_TEMPERATURE] = -3, +	.func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_TEMP +		| PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_TEMP, +	.func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +	.func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +	.func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +	.read_word_data = max16064_read_word_data, +	.write_word_data = max16064_write_word_data, +}; + +static int max16064_probe(struct i2c_client *client, +			  const struct i2c_device_id *id) +{ +	return pmbus_do_probe(client, id, &max16064_info); +} + +static const struct i2c_device_id max16064_id[] = { +	{"max16064", 0}, +	{} +}; + +MODULE_DEVICE_TABLE(i2c, max16064_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max16064_driver = { +	.driver = { +		   .name = "max16064", +		   }, +	.probe = max16064_probe, +	.remove = pmbus_do_remove, +	.id_table = max16064_id, +}; + +module_i2c_driver(max16064_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX16064"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/max34440.c b/drivers/hwmon/pmbus/max34440.c new file mode 100644 index 00000000000..7e930c3ce1a --- /dev/null +++ b/drivers/hwmon/pmbus/max34440.c @@ -0,0 +1,435 @@ +/* + * Hardware monitoring driver for Maxim MAX34440/MAX34441 + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * + * 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/i2c.h> +#include "pmbus.h" + +enum chips { max34440, max34441, max34446, max34460, max34461 }; + +#define MAX34440_MFR_VOUT_PEAK		0xd4 +#define MAX34440_MFR_IOUT_PEAK		0xd5 +#define MAX34440_MFR_TEMPERATURE_PEAK	0xd6 +#define MAX34440_MFR_VOUT_MIN		0xd7 + +#define MAX34446_MFR_POUT_PEAK		0xe0 +#define MAX34446_MFR_POUT_AVG		0xe1 +#define MAX34446_MFR_IOUT_AVG		0xe2 +#define MAX34446_MFR_TEMPERATURE_AVG	0xe3 + +#define MAX34440_STATUS_OC_WARN		(1 << 0) +#define MAX34440_STATUS_OC_FAULT	(1 << 1) +#define MAX34440_STATUS_OT_FAULT	(1 << 5) +#define MAX34440_STATUS_OT_WARN		(1 << 6) + +struct max34440_data { +	int id; +	struct pmbus_driver_info info; +}; + +#define to_max34440_data(x)  container_of(x, struct max34440_data, info) + +static int max34440_read_word_data(struct i2c_client *client, int page, int reg) +{ +	int ret; +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	const struct max34440_data *data = to_max34440_data(info); + +	switch (reg) { +	case PMBUS_VIRT_READ_VOUT_MIN: +		ret = pmbus_read_word_data(client, page, +					   MAX34440_MFR_VOUT_MIN); +		break; +	case PMBUS_VIRT_READ_VOUT_MAX: +		ret = pmbus_read_word_data(client, page, +					   MAX34440_MFR_VOUT_PEAK); +		break; +	case PMBUS_VIRT_READ_IOUT_AVG: +		if (data->id != max34446) +			return -ENXIO; +		ret = pmbus_read_word_data(client, page, +					   MAX34446_MFR_IOUT_AVG); +		break; +	case PMBUS_VIRT_READ_IOUT_MAX: +		ret = pmbus_read_word_data(client, page, +					   MAX34440_MFR_IOUT_PEAK); +		break; +	case PMBUS_VIRT_READ_POUT_AVG: +		if (data->id != max34446) +			return -ENXIO; +		ret = pmbus_read_word_data(client, page, +					   MAX34446_MFR_POUT_AVG); +		break; +	case PMBUS_VIRT_READ_POUT_MAX: +		if (data->id != max34446) +			return -ENXIO; +		ret = pmbus_read_word_data(client, page, +					   MAX34446_MFR_POUT_PEAK); +		break; +	case PMBUS_VIRT_READ_TEMP_AVG: +		if (data->id != max34446 && data->id != max34460 && +		    data->id != max34461) +			return -ENXIO; +		ret = pmbus_read_word_data(client, page, +					   MAX34446_MFR_TEMPERATURE_AVG); +		break; +	case PMBUS_VIRT_READ_TEMP_MAX: +		ret = pmbus_read_word_data(client, page, +					   MAX34440_MFR_TEMPERATURE_PEAK); +		break; +	case PMBUS_VIRT_RESET_POUT_HISTORY: +		if (data->id != max34446) +			return -ENXIO; +		ret = 0; +		break; +	case PMBUS_VIRT_RESET_VOUT_HISTORY: +	case PMBUS_VIRT_RESET_IOUT_HISTORY: +	case PMBUS_VIRT_RESET_TEMP_HISTORY: +		ret = 0; +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static int max34440_write_word_data(struct i2c_client *client, int page, +				    int reg, u16 word) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	const struct max34440_data *data = to_max34440_data(info); +	int ret; + +	switch (reg) { +	case PMBUS_VIRT_RESET_POUT_HISTORY: +		ret = pmbus_write_word_data(client, page, +					    MAX34446_MFR_POUT_PEAK, 0); +		if (ret) +			break; +		ret = pmbus_write_word_data(client, page, +					    MAX34446_MFR_POUT_AVG, 0); +		break; +	case PMBUS_VIRT_RESET_VOUT_HISTORY: +		ret = pmbus_write_word_data(client, page, +					    MAX34440_MFR_VOUT_MIN, 0x7fff); +		if (ret) +			break; +		ret = pmbus_write_word_data(client, page, +					    MAX34440_MFR_VOUT_PEAK, 0); +		break; +	case PMBUS_VIRT_RESET_IOUT_HISTORY: +		ret = pmbus_write_word_data(client, page, +					    MAX34440_MFR_IOUT_PEAK, 0); +		if (!ret && data->id == max34446) +			ret = pmbus_write_word_data(client, page, +					MAX34446_MFR_IOUT_AVG, 0); + +		break; +	case PMBUS_VIRT_RESET_TEMP_HISTORY: +		ret = pmbus_write_word_data(client, page, +					    MAX34440_MFR_TEMPERATURE_PEAK, +					    0x8000); +		if (!ret && data->id == max34446) +			ret = pmbus_write_word_data(client, page, +					MAX34446_MFR_TEMPERATURE_AVG, 0); +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static int max34440_read_byte_data(struct i2c_client *client, int page, int reg) +{ +	int ret = 0; +	int mfg_status; + +	if (page >= 0) { +		ret = pmbus_set_page(client, page); +		if (ret < 0) +			return ret; +	} + +	switch (reg) { +	case PMBUS_STATUS_IOUT: +		mfg_status = pmbus_read_word_data(client, 0, +						  PMBUS_STATUS_MFR_SPECIFIC); +		if (mfg_status < 0) +			return mfg_status; +		if (mfg_status & MAX34440_STATUS_OC_WARN) +			ret |= PB_IOUT_OC_WARNING; +		if (mfg_status & MAX34440_STATUS_OC_FAULT) +			ret |= PB_IOUT_OC_FAULT; +		break; +	case PMBUS_STATUS_TEMPERATURE: +		mfg_status = pmbus_read_word_data(client, 0, +						  PMBUS_STATUS_MFR_SPECIFIC); +		if (mfg_status < 0) +			return mfg_status; +		if (mfg_status & MAX34440_STATUS_OT_WARN) +			ret |= PB_TEMP_OT_WARNING; +		if (mfg_status & MAX34440_STATUS_OT_FAULT) +			ret |= PB_TEMP_OT_FAULT; +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static struct pmbus_driver_info max34440_info[] = { +	[max34440] = { +		.pages = 14, +		.format[PSC_VOLTAGE_IN] = direct, +		.format[PSC_VOLTAGE_OUT] = direct, +		.format[PSC_TEMPERATURE] = direct, +		.format[PSC_CURRENT_OUT] = direct, +		.m[PSC_VOLTAGE_IN] = 1, +		.b[PSC_VOLTAGE_IN] = 0, +		.R[PSC_VOLTAGE_IN] = 3,	    /* R = 0 in datasheet reflects mV */ +		.m[PSC_VOLTAGE_OUT] = 1, +		.b[PSC_VOLTAGE_OUT] = 0, +		.R[PSC_VOLTAGE_OUT] = 3,    /* R = 0 in datasheet reflects mV */ +		.m[PSC_CURRENT_OUT] = 1, +		.b[PSC_CURRENT_OUT] = 0, +		.R[PSC_CURRENT_OUT] = 3,    /* R = 0 in datasheet reflects mA */ +		.m[PSC_TEMPERATURE] = 1, +		.b[PSC_TEMPERATURE] = 0, +		.R[PSC_TEMPERATURE] = 2, +		.func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +		.func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +		.func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +		.func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +		.func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +		.func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +		.func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[7] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[8] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[9] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[10] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[12] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[13] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.read_byte_data = max34440_read_byte_data, +		.read_word_data = max34440_read_word_data, +		.write_word_data = max34440_write_word_data, +	}, +	[max34441] = { +		.pages = 12, +		.format[PSC_VOLTAGE_IN] = direct, +		.format[PSC_VOLTAGE_OUT] = direct, +		.format[PSC_TEMPERATURE] = direct, +		.format[PSC_CURRENT_OUT] = direct, +		.format[PSC_FAN] = direct, +		.m[PSC_VOLTAGE_IN] = 1, +		.b[PSC_VOLTAGE_IN] = 0, +		.R[PSC_VOLTAGE_IN] = 3, +		.m[PSC_VOLTAGE_OUT] = 1, +		.b[PSC_VOLTAGE_OUT] = 0, +		.R[PSC_VOLTAGE_OUT] = 3, +		.m[PSC_CURRENT_OUT] = 1, +		.b[PSC_CURRENT_OUT] = 0, +		.R[PSC_CURRENT_OUT] = 3, +		.m[PSC_TEMPERATURE] = 1, +		.b[PSC_TEMPERATURE] = 0, +		.R[PSC_TEMPERATURE] = 2, +		.m[PSC_FAN] = 1, +		.b[PSC_FAN] = 0, +		.R[PSC_FAN] = 0, +		.func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +		.func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +		.func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +		.func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +		.func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +		.func[5] = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12, +		.func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[7] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[8] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[9] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[10] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.read_byte_data = max34440_read_byte_data, +		.read_word_data = max34440_read_word_data, +		.write_word_data = max34440_write_word_data, +	}, +	[max34446] = { +		.pages = 7, +		.format[PSC_VOLTAGE_IN] = direct, +		.format[PSC_VOLTAGE_OUT] = direct, +		.format[PSC_TEMPERATURE] = direct, +		.format[PSC_CURRENT_OUT] = direct, +		.format[PSC_POWER] = direct, +		.m[PSC_VOLTAGE_IN] = 1, +		.b[PSC_VOLTAGE_IN] = 0, +		.R[PSC_VOLTAGE_IN] = 3, +		.m[PSC_VOLTAGE_OUT] = 1, +		.b[PSC_VOLTAGE_OUT] = 0, +		.R[PSC_VOLTAGE_OUT] = 3, +		.m[PSC_CURRENT_OUT] = 1, +		.b[PSC_CURRENT_OUT] = 0, +		.R[PSC_CURRENT_OUT] = 3, +		.m[PSC_POWER] = 1, +		.b[PSC_POWER] = 0, +		.R[PSC_POWER] = 3, +		.m[PSC_TEMPERATURE] = 1, +		.b[PSC_TEMPERATURE] = 0, +		.R[PSC_TEMPERATURE] = 2, +		.func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, +		.func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +		.func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_POUT, +		.func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +		  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, +		.func[4] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[5] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.read_byte_data = max34440_read_byte_data, +		.read_word_data = max34440_read_word_data, +		.write_word_data = max34440_write_word_data, +	}, +	[max34460] = { +		.pages = 18, +		.format[PSC_VOLTAGE_OUT] = direct, +		.format[PSC_TEMPERATURE] = direct, +		.m[PSC_VOLTAGE_OUT] = 1, +		.b[PSC_VOLTAGE_OUT] = 0, +		.R[PSC_VOLTAGE_OUT] = 3, +		.m[PSC_TEMPERATURE] = 1, +		.b[PSC_TEMPERATURE] = 0, +		.R[PSC_TEMPERATURE] = 2, +		.func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[6] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[7] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[8] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[9] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[10] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[11] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[13] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[14] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[15] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[16] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.read_byte_data = max34440_read_byte_data, +		.read_word_data = max34440_read_word_data, +		.write_word_data = max34440_write_word_data, +	}, +	[max34461] = { +		.pages = 23, +		.format[PSC_VOLTAGE_OUT] = direct, +		.format[PSC_TEMPERATURE] = direct, +		.m[PSC_VOLTAGE_OUT] = 1, +		.b[PSC_VOLTAGE_OUT] = 0, +		.R[PSC_VOLTAGE_OUT] = 3, +		.m[PSC_TEMPERATURE] = 1, +		.b[PSC_TEMPERATURE] = 0, +		.R[PSC_TEMPERATURE] = 2, +		.func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[6] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[7] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[8] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[9] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[10] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[11] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[12] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[13] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[14] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		.func[15] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +		/* page 16 is reserved */ +		.func[17] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[18] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[19] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[20] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.func[21] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, +		.read_byte_data = max34440_read_byte_data, +		.read_word_data = max34440_read_word_data, +		.write_word_data = max34440_write_word_data, +	}, +}; + +static int max34440_probe(struct i2c_client *client, +			  const struct i2c_device_id *id) +{ +	struct max34440_data *data; + +	data = devm_kzalloc(&client->dev, sizeof(struct max34440_data), +			    GFP_KERNEL); +	if (!data) +		return -ENOMEM; +	data->id = id->driver_data; +	data->info = max34440_info[id->driver_data]; + +	return pmbus_do_probe(client, id, &data->info); +} + +static const struct i2c_device_id max34440_id[] = { +	{"max34440", max34440}, +	{"max34441", max34441}, +	{"max34446", max34446}, +	{"max34460", max34460}, +	{"max34461", max34461}, +	{} +}; +MODULE_DEVICE_TABLE(i2c, max34440_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max34440_driver = { +	.driver = { +		   .name = "max34440", +		   }, +	.probe = max34440_probe, +	.remove = pmbus_do_remove, +	.id_table = max34440_id, +}; + +module_i2c_driver(max34440_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX34440/MAX34441"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/max8688.c b/drivers/hwmon/pmbus/max8688.c new file mode 100644 index 00000000000..f04454a42fd --- /dev/null +++ b/drivers/hwmon/pmbus/max8688.c @@ -0,0 +1,204 @@ +/* + * Hardware monitoring driver for Maxim MAX8688 + * + * Copyright (c) 2011 Ericsson AB. + * + * 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/i2c.h> +#include "pmbus.h" + +#define MAX8688_MFR_VOUT_PEAK		0xd4 +#define MAX8688_MFR_IOUT_PEAK		0xd5 +#define MAX8688_MFR_TEMPERATURE_PEAK	0xd6 +#define MAX8688_MFG_STATUS		0xd8 + +#define MAX8688_STATUS_OC_FAULT		(1 << 4) +#define MAX8688_STATUS_OV_FAULT		(1 << 5) +#define MAX8688_STATUS_OV_WARNING	(1 << 8) +#define MAX8688_STATUS_UV_FAULT		(1 << 9) +#define MAX8688_STATUS_UV_WARNING	(1 << 10) +#define MAX8688_STATUS_UC_FAULT		(1 << 11) +#define MAX8688_STATUS_OC_WARNING	(1 << 12) +#define MAX8688_STATUS_OT_FAULT		(1 << 13) +#define MAX8688_STATUS_OT_WARNING	(1 << 14) + +static int max8688_read_word_data(struct i2c_client *client, int page, int reg) +{ +	int ret; + +	if (page) +		return -ENXIO; + +	switch (reg) { +	case PMBUS_VIRT_READ_VOUT_MAX: +		ret = pmbus_read_word_data(client, 0, MAX8688_MFR_VOUT_PEAK); +		break; +	case PMBUS_VIRT_READ_IOUT_MAX: +		ret = pmbus_read_word_data(client, 0, MAX8688_MFR_IOUT_PEAK); +		break; +	case PMBUS_VIRT_READ_TEMP_MAX: +		ret = pmbus_read_word_data(client, 0, +					   MAX8688_MFR_TEMPERATURE_PEAK); +		break; +	case PMBUS_VIRT_RESET_VOUT_HISTORY: +	case PMBUS_VIRT_RESET_IOUT_HISTORY: +	case PMBUS_VIRT_RESET_TEMP_HISTORY: +		ret = 0; +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static int max8688_write_word_data(struct i2c_client *client, int page, int reg, +				   u16 word) +{ +	int ret; + +	switch (reg) { +	case PMBUS_VIRT_RESET_VOUT_HISTORY: +		ret = pmbus_write_word_data(client, 0, MAX8688_MFR_VOUT_PEAK, +					    0); +		break; +	case PMBUS_VIRT_RESET_IOUT_HISTORY: +		ret = pmbus_write_word_data(client, 0, MAX8688_MFR_IOUT_PEAK, +					    0); +		break; +	case PMBUS_VIRT_RESET_TEMP_HISTORY: +		ret = pmbus_write_word_data(client, 0, +					    MAX8688_MFR_TEMPERATURE_PEAK, +					    0xffff); +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static int max8688_read_byte_data(struct i2c_client *client, int page, int reg) +{ +	int ret = 0; +	int mfg_status; + +	if (page > 0) +		return -ENXIO; + +	switch (reg) { +	case PMBUS_STATUS_VOUT: +		mfg_status = pmbus_read_word_data(client, 0, +						  MAX8688_MFG_STATUS); +		if (mfg_status < 0) +			return mfg_status; +		if (mfg_status & MAX8688_STATUS_UV_WARNING) +			ret |= PB_VOLTAGE_UV_WARNING; +		if (mfg_status & MAX8688_STATUS_UV_FAULT) +			ret |= PB_VOLTAGE_UV_FAULT; +		if (mfg_status & MAX8688_STATUS_OV_WARNING) +			ret |= PB_VOLTAGE_OV_WARNING; +		if (mfg_status & MAX8688_STATUS_OV_FAULT) +			ret |= PB_VOLTAGE_OV_FAULT; +		break; +	case PMBUS_STATUS_IOUT: +		mfg_status = pmbus_read_word_data(client, 0, +						  MAX8688_MFG_STATUS); +		if (mfg_status < 0) +			return mfg_status; +		if (mfg_status & MAX8688_STATUS_UC_FAULT) +			ret |= PB_IOUT_UC_FAULT; +		if (mfg_status & MAX8688_STATUS_OC_WARNING) +			ret |= PB_IOUT_OC_WARNING; +		if (mfg_status & MAX8688_STATUS_OC_FAULT) +			ret |= PB_IOUT_OC_FAULT; +		break; +	case PMBUS_STATUS_TEMPERATURE: +		mfg_status = pmbus_read_word_data(client, 0, +						  MAX8688_MFG_STATUS); +		if (mfg_status < 0) +			return mfg_status; +		if (mfg_status & MAX8688_STATUS_OT_WARNING) +			ret |= PB_TEMP_OT_WARNING; +		if (mfg_status & MAX8688_STATUS_OT_FAULT) +			ret |= PB_TEMP_OT_FAULT; +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static struct pmbus_driver_info max8688_info = { +	.pages = 1, +	.format[PSC_VOLTAGE_IN] = direct, +	.format[PSC_VOLTAGE_OUT] = direct, +	.format[PSC_TEMPERATURE] = direct, +	.format[PSC_CURRENT_OUT] = direct, +	.m[PSC_VOLTAGE_IN] = 19995, +	.b[PSC_VOLTAGE_IN] = 0, +	.R[PSC_VOLTAGE_IN] = -1, +	.m[PSC_VOLTAGE_OUT] = 19995, +	.b[PSC_VOLTAGE_OUT] = 0, +	.R[PSC_VOLTAGE_OUT] = -1, +	.m[PSC_CURRENT_OUT] = 23109, +	.b[PSC_CURRENT_OUT] = 0, +	.R[PSC_CURRENT_OUT] = -2, +	.m[PSC_TEMPERATURE] = -7612, +	.b[PSC_TEMPERATURE] = 335, +	.R[PSC_TEMPERATURE] = -3, +	.func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP +		| PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT +		| PMBUS_HAVE_STATUS_TEMP, +	.read_byte_data = max8688_read_byte_data, +	.read_word_data = max8688_read_word_data, +	.write_word_data = max8688_write_word_data, +}; + +static int max8688_probe(struct i2c_client *client, +			 const struct i2c_device_id *id) +{ +	return pmbus_do_probe(client, id, &max8688_info); +} + +static const struct i2c_device_id max8688_id[] = { +	{"max8688", 0}, +	{ } +}; + +MODULE_DEVICE_TABLE(i2c, max8688_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max8688_driver = { +	.driver = { +		   .name = "max8688", +		   }, +	.probe = max8688_probe, +	.remove = pmbus_do_remove, +	.id_table = max8688_id, +}; + +module_i2c_driver(max8688_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX8688"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/pmbus.c b/drivers/hwmon/pmbus/pmbus.c new file mode 100644 index 00000000000..7e91700131a --- /dev/null +++ b/drivers/hwmon/pmbus/pmbus.c @@ -0,0 +1,217 @@ +/* + * Hardware monitoring driver for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * + * 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/mutex.h> +#include <linux/i2c.h> +#include "pmbus.h" + +/* + * Find sensor groups and status registers on each page. + */ +static void pmbus_find_sensor_groups(struct i2c_client *client, +				     struct pmbus_driver_info *info) +{ +	int page; + +	/* Sensors detected on page 0 only */ +	if (pmbus_check_word_register(client, 0, PMBUS_READ_VIN)) +		info->func[0] |= PMBUS_HAVE_VIN; +	if (pmbus_check_word_register(client, 0, PMBUS_READ_VCAP)) +		info->func[0] |= PMBUS_HAVE_VCAP; +	if (pmbus_check_word_register(client, 0, PMBUS_READ_IIN)) +		info->func[0] |= PMBUS_HAVE_IIN; +	if (pmbus_check_word_register(client, 0, PMBUS_READ_PIN)) +		info->func[0] |= PMBUS_HAVE_PIN; +	if (info->func[0] +	    && pmbus_check_byte_register(client, 0, PMBUS_STATUS_INPUT)) +		info->func[0] |= PMBUS_HAVE_STATUS_INPUT; +	if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_12) && +	    pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_1)) { +		info->func[0] |= PMBUS_HAVE_FAN12; +		if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_12)) +			info->func[0] |= PMBUS_HAVE_STATUS_FAN12; +	} +	if (pmbus_check_byte_register(client, 0, PMBUS_FAN_CONFIG_34) && +	    pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_3)) { +		info->func[0] |= PMBUS_HAVE_FAN34; +		if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_34)) +			info->func[0] |= PMBUS_HAVE_STATUS_FAN34; +	} +	if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_1)) +		info->func[0] |= PMBUS_HAVE_TEMP; +	if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_2)) +		info->func[0] |= PMBUS_HAVE_TEMP2; +	if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_3)) +		info->func[0] |= PMBUS_HAVE_TEMP3; +	if (info->func[0] & (PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 +			     | PMBUS_HAVE_TEMP3) +	    && pmbus_check_byte_register(client, 0, +					 PMBUS_STATUS_TEMPERATURE)) +			info->func[0] |= PMBUS_HAVE_STATUS_TEMP; + +	/* Sensors detected on all pages */ +	for (page = 0; page < info->pages; page++) { +		if (pmbus_check_word_register(client, page, PMBUS_READ_VOUT)) { +			info->func[page] |= PMBUS_HAVE_VOUT; +			if (pmbus_check_byte_register(client, page, +						      PMBUS_STATUS_VOUT)) +				info->func[page] |= PMBUS_HAVE_STATUS_VOUT; +		} +		if (pmbus_check_word_register(client, page, PMBUS_READ_IOUT)) { +			info->func[page] |= PMBUS_HAVE_IOUT; +			if (pmbus_check_byte_register(client, 0, +						      PMBUS_STATUS_IOUT)) +				info->func[page] |= PMBUS_HAVE_STATUS_IOUT; +		} +		if (pmbus_check_word_register(client, page, PMBUS_READ_POUT)) +			info->func[page] |= PMBUS_HAVE_POUT; +	} +} + +/* + * Identify chip parameters. + */ +static int pmbus_identify(struct i2c_client *client, +			  struct pmbus_driver_info *info) +{ +	int ret = 0; + +	if (!info->pages) { +		/* +		 * Check if the PAGE command is supported. If it is, +		 * keep setting the page number until it fails or until the +		 * maximum number of pages has been reached. Assume that +		 * this is the number of pages supported by the chip. +		 */ +		if (pmbus_check_byte_register(client, 0, PMBUS_PAGE)) { +			int page; + +			for (page = 1; page < PMBUS_PAGES; page++) { +				if (pmbus_set_page(client, page) < 0) +					break; +			} +			pmbus_set_page(client, 0); +			info->pages = page; +		} else { +			info->pages = 1; +		} +	} + +	if (pmbus_check_byte_register(client, 0, PMBUS_VOUT_MODE)) { +		int vout_mode; + +		vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); +		if (vout_mode >= 0 && vout_mode != 0xff) { +			switch (vout_mode >> 5) { +			case 0: +				break; +			case 1: +				info->format[PSC_VOLTAGE_OUT] = vid; +				break; +			case 2: +				info->format[PSC_VOLTAGE_OUT] = direct; +				break; +			default: +				ret = -ENODEV; +				goto abort; +			} +		} +	} + +	/* +	 * We should check if the COEFFICIENTS register is supported. +	 * If it is, and the chip is configured for direct mode, we can read +	 * the coefficients from the chip, one set per group of sensor +	 * registers. +	 * +	 * To do this, we will need access to a chip which actually supports the +	 * COEFFICIENTS command, since the command is too complex to implement +	 * without testing it. Until then, abort if a chip configured for direct +	 * mode was detected. +	 */ +	if (info->format[PSC_VOLTAGE_OUT] == direct) { +		ret = -ENODEV; +		goto abort; +	} + +	/* Try to find sensor groups  */ +	pmbus_find_sensor_groups(client, info); +abort: +	return ret; +} + +static int pmbus_probe(struct i2c_client *client, +		       const struct i2c_device_id *id) +{ +	struct pmbus_driver_info *info; + +	info = devm_kzalloc(&client->dev, sizeof(struct pmbus_driver_info), +			    GFP_KERNEL); +	if (!info) +		return -ENOMEM; + +	info->pages = id->driver_data; +	info->identify = pmbus_identify; + +	return pmbus_do_probe(client, id, info); +} + +/* + * Use driver_data to set the number of pages supported by the chip. + */ +static const struct i2c_device_id pmbus_id[] = { +	{"adp4000", 1}, +	{"bmr453", 1}, +	{"bmr454", 1}, +	{"mdt040", 1}, +	{"ncp4200", 1}, +	{"ncp4208", 1}, +	{"pdt003", 1}, +	{"pdt006", 1}, +	{"pdt012", 1}, +	{"pmbus", 0}, +	{"tps40400", 1}, +	{"tps40422", 2}, +	{"udt020", 1}, +	{} +}; + +MODULE_DEVICE_TABLE(i2c, pmbus_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver pmbus_driver = { +	.driver = { +		   .name = "pmbus", +		   }, +	.probe = pmbus_probe, +	.remove = pmbus_do_remove, +	.id_table = pmbus_id, +}; + +module_i2c_driver(pmbus_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("Generic PMBus driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h new file mode 100644 index 00000000000..fa9beb3eb60 --- /dev/null +++ b/drivers/hwmon/pmbus/pmbus.h @@ -0,0 +1,387 @@ +/* + * pmbus.h - Common defines and structures for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * + * 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. + */ + +#ifndef PMBUS_H +#define PMBUS_H + +/* + * Registers + */ +#define PMBUS_PAGE			0x00 +#define PMBUS_OPERATION			0x01 +#define PMBUS_ON_OFF_CONFIG		0x02 +#define PMBUS_CLEAR_FAULTS		0x03 +#define PMBUS_PHASE			0x04 + +#define PMBUS_CAPABILITY		0x19 +#define PMBUS_QUERY			0x1A + +#define PMBUS_VOUT_MODE			0x20 +#define PMBUS_VOUT_COMMAND		0x21 +#define PMBUS_VOUT_TRIM			0x22 +#define PMBUS_VOUT_CAL_OFFSET		0x23 +#define PMBUS_VOUT_MAX			0x24 +#define PMBUS_VOUT_MARGIN_HIGH		0x25 +#define PMBUS_VOUT_MARGIN_LOW		0x26 +#define PMBUS_VOUT_TRANSITION_RATE	0x27 +#define PMBUS_VOUT_DROOP		0x28 +#define PMBUS_VOUT_SCALE_LOOP		0x29 +#define PMBUS_VOUT_SCALE_MONITOR	0x2A + +#define PMBUS_COEFFICIENTS		0x30 +#define PMBUS_POUT_MAX			0x31 + +#define PMBUS_FAN_CONFIG_12		0x3A +#define PMBUS_FAN_COMMAND_1		0x3B +#define PMBUS_FAN_COMMAND_2		0x3C +#define PMBUS_FAN_CONFIG_34		0x3D +#define PMBUS_FAN_COMMAND_3		0x3E +#define PMBUS_FAN_COMMAND_4		0x3F + +#define PMBUS_VOUT_OV_FAULT_LIMIT	0x40 +#define PMBUS_VOUT_OV_FAULT_RESPONSE	0x41 +#define PMBUS_VOUT_OV_WARN_LIMIT	0x42 +#define PMBUS_VOUT_UV_WARN_LIMIT	0x43 +#define PMBUS_VOUT_UV_FAULT_LIMIT	0x44 +#define PMBUS_VOUT_UV_FAULT_RESPONSE	0x45 +#define PMBUS_IOUT_OC_FAULT_LIMIT	0x46 +#define PMBUS_IOUT_OC_FAULT_RESPONSE	0x47 +#define PMBUS_IOUT_OC_LV_FAULT_LIMIT	0x48 +#define PMBUS_IOUT_OC_LV_FAULT_RESPONSE	0x49 +#define PMBUS_IOUT_OC_WARN_LIMIT	0x4A +#define PMBUS_IOUT_UC_FAULT_LIMIT	0x4B +#define PMBUS_IOUT_UC_FAULT_RESPONSE	0x4C + +#define PMBUS_OT_FAULT_LIMIT		0x4F +#define PMBUS_OT_FAULT_RESPONSE		0x50 +#define PMBUS_OT_WARN_LIMIT		0x51 +#define PMBUS_UT_WARN_LIMIT		0x52 +#define PMBUS_UT_FAULT_LIMIT		0x53 +#define PMBUS_UT_FAULT_RESPONSE		0x54 +#define PMBUS_VIN_OV_FAULT_LIMIT	0x55 +#define PMBUS_VIN_OV_FAULT_RESPONSE	0x56 +#define PMBUS_VIN_OV_WARN_LIMIT		0x57 +#define PMBUS_VIN_UV_WARN_LIMIT		0x58 +#define PMBUS_VIN_UV_FAULT_LIMIT	0x59 + +#define PMBUS_IIN_OC_FAULT_LIMIT	0x5B +#define PMBUS_IIN_OC_WARN_LIMIT		0x5D + +#define PMBUS_POUT_OP_FAULT_LIMIT	0x68 +#define PMBUS_POUT_OP_WARN_LIMIT	0x6A +#define PMBUS_PIN_OP_WARN_LIMIT		0x6B + +#define PMBUS_STATUS_BYTE		0x78 +#define PMBUS_STATUS_WORD		0x79 +#define PMBUS_STATUS_VOUT		0x7A +#define PMBUS_STATUS_IOUT		0x7B +#define PMBUS_STATUS_INPUT		0x7C +#define PMBUS_STATUS_TEMPERATURE	0x7D +#define PMBUS_STATUS_CML		0x7E +#define PMBUS_STATUS_OTHER		0x7F +#define PMBUS_STATUS_MFR_SPECIFIC	0x80 +#define PMBUS_STATUS_FAN_12		0x81 +#define PMBUS_STATUS_FAN_34		0x82 + +#define PMBUS_READ_VIN			0x88 +#define PMBUS_READ_IIN			0x89 +#define PMBUS_READ_VCAP			0x8A +#define PMBUS_READ_VOUT			0x8B +#define PMBUS_READ_IOUT			0x8C +#define PMBUS_READ_TEMPERATURE_1	0x8D +#define PMBUS_READ_TEMPERATURE_2	0x8E +#define PMBUS_READ_TEMPERATURE_3	0x8F +#define PMBUS_READ_FAN_SPEED_1		0x90 +#define PMBUS_READ_FAN_SPEED_2		0x91 +#define PMBUS_READ_FAN_SPEED_3		0x92 +#define PMBUS_READ_FAN_SPEED_4		0x93 +#define PMBUS_READ_DUTY_CYCLE		0x94 +#define PMBUS_READ_FREQUENCY		0x95 +#define PMBUS_READ_POUT			0x96 +#define PMBUS_READ_PIN			0x97 + +#define PMBUS_REVISION			0x98 +#define PMBUS_MFR_ID			0x99 +#define PMBUS_MFR_MODEL			0x9A +#define PMBUS_MFR_REVISION		0x9B +#define PMBUS_MFR_LOCATION		0x9C +#define PMBUS_MFR_DATE			0x9D +#define PMBUS_MFR_SERIAL		0x9E + +/* + * Virtual registers. + * Useful to support attributes which are not supported by standard PMBus + * registers but exist as manufacturer specific registers on individual chips. + * Must be mapped to real registers in device specific code. + * + * Semantics: + * Virtual registers are all word size. + * READ registers are read-only; writes are either ignored or return an error. + * RESET registers are read/write. Reading reset registers returns zero + * (used for detection), writing any value causes the associated history to be + * reset. + * Virtual registers have to be handled in device specific driver code. Chip + * driver code returns non-negative register values if a virtual register is + * supported, or a negative error code if not. The chip driver may return + * -ENODATA or any other error code in this case, though an error code other + * than -ENODATA is handled more efficiently and thus preferred. Either case, + * the calling PMBus core code will abort if the chip driver returns an error + * code when reading or writing virtual registers. + */ +#define PMBUS_VIRT_BASE			0x100 +#define PMBUS_VIRT_READ_TEMP_AVG	(PMBUS_VIRT_BASE + 0) +#define PMBUS_VIRT_READ_TEMP_MIN	(PMBUS_VIRT_BASE + 1) +#define PMBUS_VIRT_READ_TEMP_MAX	(PMBUS_VIRT_BASE + 2) +#define PMBUS_VIRT_RESET_TEMP_HISTORY	(PMBUS_VIRT_BASE + 3) +#define PMBUS_VIRT_READ_VIN_AVG		(PMBUS_VIRT_BASE + 4) +#define PMBUS_VIRT_READ_VIN_MIN		(PMBUS_VIRT_BASE + 5) +#define PMBUS_VIRT_READ_VIN_MAX		(PMBUS_VIRT_BASE + 6) +#define PMBUS_VIRT_RESET_VIN_HISTORY	(PMBUS_VIRT_BASE + 7) +#define PMBUS_VIRT_READ_IIN_AVG		(PMBUS_VIRT_BASE + 8) +#define PMBUS_VIRT_READ_IIN_MIN		(PMBUS_VIRT_BASE + 9) +#define PMBUS_VIRT_READ_IIN_MAX		(PMBUS_VIRT_BASE + 10) +#define PMBUS_VIRT_RESET_IIN_HISTORY	(PMBUS_VIRT_BASE + 11) +#define PMBUS_VIRT_READ_PIN_AVG		(PMBUS_VIRT_BASE + 12) +#define PMBUS_VIRT_READ_PIN_MAX		(PMBUS_VIRT_BASE + 13) +#define PMBUS_VIRT_RESET_PIN_HISTORY	(PMBUS_VIRT_BASE + 14) +#define PMBUS_VIRT_READ_POUT_AVG	(PMBUS_VIRT_BASE + 15) +#define PMBUS_VIRT_READ_POUT_MAX	(PMBUS_VIRT_BASE + 16) +#define PMBUS_VIRT_RESET_POUT_HISTORY	(PMBUS_VIRT_BASE + 17) +#define PMBUS_VIRT_READ_VOUT_AVG	(PMBUS_VIRT_BASE + 18) +#define PMBUS_VIRT_READ_VOUT_MIN	(PMBUS_VIRT_BASE + 19) +#define PMBUS_VIRT_READ_VOUT_MAX	(PMBUS_VIRT_BASE + 20) +#define PMBUS_VIRT_RESET_VOUT_HISTORY	(PMBUS_VIRT_BASE + 21) +#define PMBUS_VIRT_READ_IOUT_AVG	(PMBUS_VIRT_BASE + 22) +#define PMBUS_VIRT_READ_IOUT_MIN	(PMBUS_VIRT_BASE + 23) +#define PMBUS_VIRT_READ_IOUT_MAX	(PMBUS_VIRT_BASE + 24) +#define PMBUS_VIRT_RESET_IOUT_HISTORY	(PMBUS_VIRT_BASE + 25) +#define PMBUS_VIRT_READ_TEMP2_AVG	(PMBUS_VIRT_BASE + 26) +#define PMBUS_VIRT_READ_TEMP2_MIN	(PMBUS_VIRT_BASE + 27) +#define PMBUS_VIRT_READ_TEMP2_MAX	(PMBUS_VIRT_BASE + 28) +#define PMBUS_VIRT_RESET_TEMP2_HISTORY	(PMBUS_VIRT_BASE + 29) + +#define PMBUS_VIRT_READ_VMON		(PMBUS_VIRT_BASE + 30) +#define PMBUS_VIRT_VMON_UV_WARN_LIMIT	(PMBUS_VIRT_BASE + 31) +#define PMBUS_VIRT_VMON_OV_WARN_LIMIT	(PMBUS_VIRT_BASE + 32) +#define PMBUS_VIRT_VMON_UV_FAULT_LIMIT	(PMBUS_VIRT_BASE + 33) +#define PMBUS_VIRT_VMON_OV_FAULT_LIMIT	(PMBUS_VIRT_BASE + 34) +#define PMBUS_VIRT_STATUS_VMON		(PMBUS_VIRT_BASE + 35) + +/* + * CAPABILITY + */ +#define PB_CAPABILITY_SMBALERT		(1<<4) +#define PB_CAPABILITY_ERROR_CHECK	(1<<7) + +/* + * VOUT_MODE + */ +#define PB_VOUT_MODE_MODE_MASK		0xe0 +#define PB_VOUT_MODE_PARAM_MASK		0x1f + +#define PB_VOUT_MODE_LINEAR		0x00 +#define PB_VOUT_MODE_VID		0x20 +#define PB_VOUT_MODE_DIRECT		0x40 + +/* + * Fan configuration + */ +#define PB_FAN_2_PULSE_MASK		((1 << 0) | (1 << 1)) +#define PB_FAN_2_RPM			(1 << 2) +#define PB_FAN_2_INSTALLED		(1 << 3) +#define PB_FAN_1_PULSE_MASK		((1 << 4) | (1 << 5)) +#define PB_FAN_1_RPM			(1 << 6) +#define PB_FAN_1_INSTALLED		(1 << 7) + +/* + * STATUS_BYTE, STATUS_WORD (lower) + */ +#define PB_STATUS_NONE_ABOVE		(1<<0) +#define PB_STATUS_CML			(1<<1) +#define PB_STATUS_TEMPERATURE		(1<<2) +#define PB_STATUS_VIN_UV		(1<<3) +#define PB_STATUS_IOUT_OC		(1<<4) +#define PB_STATUS_VOUT_OV		(1<<5) +#define PB_STATUS_OFF			(1<<6) +#define PB_STATUS_BUSY			(1<<7) + +/* + * STATUS_WORD (upper) + */ +#define PB_STATUS_UNKNOWN		(1<<8) +#define PB_STATUS_OTHER			(1<<9) +#define PB_STATUS_FANS			(1<<10) +#define PB_STATUS_POWER_GOOD_N		(1<<11) +#define PB_STATUS_WORD_MFR		(1<<12) +#define PB_STATUS_INPUT			(1<<13) +#define PB_STATUS_IOUT_POUT		(1<<14) +#define PB_STATUS_VOUT			(1<<15) + +/* + * STATUS_IOUT + */ +#define PB_POUT_OP_WARNING		(1<<0) +#define PB_POUT_OP_FAULT		(1<<1) +#define PB_POWER_LIMITING		(1<<2) +#define PB_CURRENT_SHARE_FAULT		(1<<3) +#define PB_IOUT_UC_FAULT		(1<<4) +#define PB_IOUT_OC_WARNING		(1<<5) +#define PB_IOUT_OC_LV_FAULT		(1<<6) +#define PB_IOUT_OC_FAULT		(1<<7) + +/* + * STATUS_VOUT, STATUS_INPUT + */ +#define PB_VOLTAGE_UV_FAULT		(1<<4) +#define PB_VOLTAGE_UV_WARNING		(1<<5) +#define PB_VOLTAGE_OV_WARNING		(1<<6) +#define PB_VOLTAGE_OV_FAULT		(1<<7) + +/* + * STATUS_INPUT + */ +#define PB_PIN_OP_WARNING		(1<<0) +#define PB_IIN_OC_WARNING		(1<<1) +#define PB_IIN_OC_FAULT			(1<<2) + +/* + * STATUS_TEMPERATURE + */ +#define PB_TEMP_UT_FAULT		(1<<4) +#define PB_TEMP_UT_WARNING		(1<<5) +#define PB_TEMP_OT_WARNING		(1<<6) +#define PB_TEMP_OT_FAULT		(1<<7) + +/* + * STATUS_FAN + */ +#define PB_FAN_AIRFLOW_WARNING		(1<<0) +#define PB_FAN_AIRFLOW_FAULT		(1<<1) +#define PB_FAN_FAN2_SPEED_OVERRIDE	(1<<2) +#define PB_FAN_FAN1_SPEED_OVERRIDE	(1<<3) +#define PB_FAN_FAN2_WARNING		(1<<4) +#define PB_FAN_FAN1_WARNING		(1<<5) +#define PB_FAN_FAN2_FAULT		(1<<6) +#define PB_FAN_FAN1_FAULT		(1<<7) + +/* + * CML_FAULT_STATUS + */ +#define PB_CML_FAULT_OTHER_MEM_LOGIC	(1<<0) +#define PB_CML_FAULT_OTHER_COMM		(1<<1) +#define PB_CML_FAULT_PROCESSOR		(1<<3) +#define PB_CML_FAULT_MEMORY		(1<<4) +#define PB_CML_FAULT_PACKET_ERROR	(1<<5) +#define PB_CML_FAULT_INVALID_DATA	(1<<6) +#define PB_CML_FAULT_INVALID_COMMAND	(1<<7) + +enum pmbus_sensor_classes { +	PSC_VOLTAGE_IN = 0, +	PSC_VOLTAGE_OUT, +	PSC_CURRENT_IN, +	PSC_CURRENT_OUT, +	PSC_POWER, +	PSC_TEMPERATURE, +	PSC_FAN, +	PSC_NUM_CLASSES		/* Number of power sensor classes */ +}; + +#define PMBUS_PAGES	32	/* Per PMBus specification */ + +/* Functionality bit mask */ +#define PMBUS_HAVE_VIN		(1 << 0) +#define PMBUS_HAVE_VCAP		(1 << 1) +#define PMBUS_HAVE_VOUT		(1 << 2) +#define PMBUS_HAVE_IIN		(1 << 3) +#define PMBUS_HAVE_IOUT		(1 << 4) +#define PMBUS_HAVE_PIN		(1 << 5) +#define PMBUS_HAVE_POUT		(1 << 6) +#define PMBUS_HAVE_FAN12	(1 << 7) +#define PMBUS_HAVE_FAN34	(1 << 8) +#define PMBUS_HAVE_TEMP		(1 << 9) +#define PMBUS_HAVE_TEMP2	(1 << 10) +#define PMBUS_HAVE_TEMP3	(1 << 11) +#define PMBUS_HAVE_STATUS_VOUT	(1 << 12) +#define PMBUS_HAVE_STATUS_IOUT	(1 << 13) +#define PMBUS_HAVE_STATUS_INPUT	(1 << 14) +#define PMBUS_HAVE_STATUS_TEMP	(1 << 15) +#define PMBUS_HAVE_STATUS_FAN12	(1 << 16) +#define PMBUS_HAVE_STATUS_FAN34	(1 << 17) +#define PMBUS_HAVE_VMON		(1 << 18) +#define PMBUS_HAVE_STATUS_VMON	(1 << 19) + +enum pmbus_data_format { linear = 0, direct, vid }; + +struct pmbus_driver_info { +	int pages;		/* Total number of pages */ +	enum pmbus_data_format format[PSC_NUM_CLASSES]; +	/* +	 * Support one set of coefficients for each sensor type +	 * Used for chips providing data in direct mode. +	 */ +	int m[PSC_NUM_CLASSES];	/* mantissa for direct data format */ +	int b[PSC_NUM_CLASSES];	/* offset */ +	int R[PSC_NUM_CLASSES];	/* exponent */ + +	u32 func[PMBUS_PAGES];	/* Functionality, per page */ +	/* +	 * The following functions map manufacturing specific register values +	 * to PMBus standard register values. Specify only if mapping is +	 * necessary. +	 * Functions return the register value (read) or zero (write) if +	 * successful. A return value of -ENODATA indicates that there is no +	 * manufacturer specific register, but that a standard PMBus register +	 * may exist. Any other negative return value indicates that the +	 * register does not exist, and that no attempt should be made to read +	 * the standard register. +	 */ +	int (*read_byte_data)(struct i2c_client *client, int page, int reg); +	int (*read_word_data)(struct i2c_client *client, int page, int reg); +	int (*write_word_data)(struct i2c_client *client, int page, int reg, +			       u16 word); +	int (*write_byte)(struct i2c_client *client, int page, u8 value); +	/* +	 * The identify function determines supported PMBus functionality. +	 * This function is only necessary if a chip driver supports multiple +	 * chips, and the chip functionality is not pre-determined. +	 */ +	int (*identify)(struct i2c_client *client, +			struct pmbus_driver_info *info); +}; + +/* Function declarations */ + +void pmbus_clear_cache(struct i2c_client *client); +int pmbus_set_page(struct i2c_client *client, u8 page); +int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg); +int pmbus_write_word_data(struct i2c_client *client, u8 page, u8 reg, u16 word); +int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg); +int pmbus_write_byte(struct i2c_client *client, int page, u8 value); +void pmbus_clear_faults(struct i2c_client *client); +bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); +bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); +int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, +		   struct pmbus_driver_info *info); +int pmbus_do_remove(struct i2c_client *client); +const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client +						      *client); + +#endif /* PMBUS_H */ diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c new file mode 100644 index 00000000000..291d11fe93e --- /dev/null +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -0,0 +1,1803 @@ +/* + * Hardware monitoring driver for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * + * 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> +#include <linux/jiffies.h> +#include <linux/i2c/pmbus.h> +#include "pmbus.h" + +/* + * Number of additional attribute pointers to allocate + * with each call to krealloc + */ +#define PMBUS_ATTR_ALLOC_SIZE	32 + +/* + * Index into status register array, per status register group + */ +#define PB_STATUS_BASE		0 +#define PB_STATUS_VOUT_BASE	(PB_STATUS_BASE + PMBUS_PAGES) +#define PB_STATUS_IOUT_BASE	(PB_STATUS_VOUT_BASE + PMBUS_PAGES) +#define PB_STATUS_FAN_BASE	(PB_STATUS_IOUT_BASE + PMBUS_PAGES) +#define PB_STATUS_FAN34_BASE	(PB_STATUS_FAN_BASE + PMBUS_PAGES) +#define PB_STATUS_TEMP_BASE	(PB_STATUS_FAN34_BASE + PMBUS_PAGES) +#define PB_STATUS_INPUT_BASE	(PB_STATUS_TEMP_BASE + PMBUS_PAGES) +#define PB_STATUS_VMON_BASE	(PB_STATUS_INPUT_BASE + 1) + +#define PB_NUM_STATUS_REG	(PB_STATUS_VMON_BASE + 1) + +#define PMBUS_NAME_SIZE		24 + +struct pmbus_sensor { +	struct pmbus_sensor *next; +	char name[PMBUS_NAME_SIZE];	/* sysfs sensor name */ +	struct device_attribute attribute; +	u8 page;		/* page number */ +	u16 reg;		/* register */ +	enum pmbus_sensor_classes class;	/* sensor class */ +	bool update;		/* runtime sensor update needed */ +	int data;		/* Sensor data. +				   Negative if there was a read error */ +}; +#define to_pmbus_sensor(_attr) \ +	container_of(_attr, struct pmbus_sensor, attribute) + +struct pmbus_boolean { +	char name[PMBUS_NAME_SIZE];	/* sysfs boolean name */ +	struct sensor_device_attribute attribute; +	struct pmbus_sensor *s1; +	struct pmbus_sensor *s2; +}; +#define to_pmbus_boolean(_attr) \ +	container_of(_attr, struct pmbus_boolean, attribute) + +struct pmbus_label { +	char name[PMBUS_NAME_SIZE];	/* sysfs label name */ +	struct device_attribute attribute; +	char label[PMBUS_NAME_SIZE];	/* label */ +}; +#define to_pmbus_label(_attr) \ +	container_of(_attr, struct pmbus_label, attribute) + +struct pmbus_data { +	struct device *dev; +	struct device *hwmon_dev; + +	u32 flags;		/* from platform data */ + +	int exponent[PMBUS_PAGES]; +				/* linear mode: exponent for output voltages */ + +	const struct pmbus_driver_info *info; + +	int max_attributes; +	int num_attributes; +	struct attribute_group group; +	const struct attribute_group *groups[2]; + +	struct pmbus_sensor *sensors; + +	struct mutex update_lock; +	bool valid; +	unsigned long last_updated;	/* in jiffies */ + +	/* +	 * A single status register covers multiple attributes, +	 * so we keep them all together. +	 */ +	u8 status[PB_NUM_STATUS_REG]; +	u8 status_register; + +	u8 currpage; +}; + +void pmbus_clear_cache(struct i2c_client *client) +{ +	struct pmbus_data *data = i2c_get_clientdata(client); + +	data->valid = false; +} +EXPORT_SYMBOL_GPL(pmbus_clear_cache); + +int pmbus_set_page(struct i2c_client *client, u8 page) +{ +	struct pmbus_data *data = i2c_get_clientdata(client); +	int rv = 0; +	int newpage; + +	if (page != data->currpage) { +		rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); +		newpage = i2c_smbus_read_byte_data(client, PMBUS_PAGE); +		if (newpage != page) +			rv = -EIO; +		else +			data->currpage = page; +	} +	return rv; +} +EXPORT_SYMBOL_GPL(pmbus_set_page); + +int pmbus_write_byte(struct i2c_client *client, int page, u8 value) +{ +	int rv; + +	if (page >= 0) { +		rv = pmbus_set_page(client, page); +		if (rv < 0) +			return rv; +	} + +	return i2c_smbus_write_byte(client, value); +} +EXPORT_SYMBOL_GPL(pmbus_write_byte); + +/* + * _pmbus_write_byte() is similar to pmbus_write_byte(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_write_byte(struct i2c_client *client, int page, u8 value) +{ +	struct pmbus_data *data = i2c_get_clientdata(client); +	const struct pmbus_driver_info *info = data->info; +	int status; + +	if (info->write_byte) { +		status = info->write_byte(client, page, value); +		if (status != -ENODATA) +			return status; +	} +	return pmbus_write_byte(client, page, value); +} + +int pmbus_write_word_data(struct i2c_client *client, u8 page, u8 reg, u16 word) +{ +	int rv; + +	rv = pmbus_set_page(client, page); +	if (rv < 0) +		return rv; + +	return i2c_smbus_write_word_data(client, reg, word); +} +EXPORT_SYMBOL_GPL(pmbus_write_word_data); + +/* + * _pmbus_write_word_data() is similar to pmbus_write_word_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg, +				  u16 word) +{ +	struct pmbus_data *data = i2c_get_clientdata(client); +	const struct pmbus_driver_info *info = data->info; +	int status; + +	if (info->write_word_data) { +		status = info->write_word_data(client, page, reg, word); +		if (status != -ENODATA) +			return status; +	} +	if (reg >= PMBUS_VIRT_BASE) +		return -ENXIO; +	return pmbus_write_word_data(client, page, reg, word); +} + +int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg) +{ +	int rv; + +	rv = pmbus_set_page(client, page); +	if (rv < 0) +		return rv; + +	return i2c_smbus_read_word_data(client, reg); +} +EXPORT_SYMBOL_GPL(pmbus_read_word_data); + +/* + * _pmbus_read_word_data() is similar to pmbus_read_word_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_read_word_data(struct i2c_client *client, int page, int reg) +{ +	struct pmbus_data *data = i2c_get_clientdata(client); +	const struct pmbus_driver_info *info = data->info; +	int status; + +	if (info->read_word_data) { +		status = info->read_word_data(client, page, reg); +		if (status != -ENODATA) +			return status; +	} +	if (reg >= PMBUS_VIRT_BASE) +		return -ENXIO; +	return pmbus_read_word_data(client, page, reg); +} + +int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg) +{ +	int rv; + +	if (page >= 0) { +		rv = pmbus_set_page(client, page); +		if (rv < 0) +			return rv; +	} + +	return i2c_smbus_read_byte_data(client, reg); +} +EXPORT_SYMBOL_GPL(pmbus_read_byte_data); + +/* + * _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg) +{ +	struct pmbus_data *data = i2c_get_clientdata(client); +	const struct pmbus_driver_info *info = data->info; +	int status; + +	if (info->read_byte_data) { +		status = info->read_byte_data(client, page, reg); +		if (status != -ENODATA) +			return status; +	} +	return pmbus_read_byte_data(client, page, reg); +} + +static void pmbus_clear_fault_page(struct i2c_client *client, int page) +{ +	_pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); +} + +void pmbus_clear_faults(struct i2c_client *client) +{ +	struct pmbus_data *data = i2c_get_clientdata(client); +	int i; + +	for (i = 0; i < data->info->pages; i++) +		pmbus_clear_fault_page(client, i); +} +EXPORT_SYMBOL_GPL(pmbus_clear_faults); + +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); +	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)) +			return -EIO; +	} +	return 0; +} + +static bool pmbus_check_register(struct i2c_client *client, +				 int (*func)(struct i2c_client *client, +					     int page, int reg), +				 int page, int reg) +{ +	int rv; +	struct pmbus_data *data = i2c_get_clientdata(client); + +	rv = func(client, page, reg); +	if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK)) +		rv = pmbus_check_status_cml(client); +	pmbus_clear_fault_page(client, -1); +	return rv >= 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); +} +EXPORT_SYMBOL_GPL(pmbus_check_byte_register); + +bool pmbus_check_word_register(struct i2c_client *client, int page, int reg) +{ +	return pmbus_check_register(client, _pmbus_read_word_data, page, reg); +} +EXPORT_SYMBOL_GPL(pmbus_check_word_register); + +const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client) +{ +	struct pmbus_data *data = i2c_get_clientdata(client); + +	return data->info; +} +EXPORT_SYMBOL_GPL(pmbus_get_driver_info); + +static struct _pmbus_status { +	u32 func; +	u16 base; +	u16 reg; +} pmbus_status[] = { +	{ PMBUS_HAVE_STATUS_VOUT, PB_STATUS_VOUT_BASE, PMBUS_STATUS_VOUT }, +	{ PMBUS_HAVE_STATUS_IOUT, PB_STATUS_IOUT_BASE, PMBUS_STATUS_IOUT }, +	{ PMBUS_HAVE_STATUS_TEMP, PB_STATUS_TEMP_BASE, +	  PMBUS_STATUS_TEMPERATURE }, +	{ PMBUS_HAVE_STATUS_FAN12, PB_STATUS_FAN_BASE, PMBUS_STATUS_FAN_12 }, +	{ PMBUS_HAVE_STATUS_FAN34, PB_STATUS_FAN34_BASE, PMBUS_STATUS_FAN_34 }, +}; + +static struct pmbus_data *pmbus_update_device(struct device *dev) +{ +	struct i2c_client *client = to_i2c_client(dev->parent); +	struct pmbus_data *data = i2c_get_clientdata(client); +	const struct pmbus_driver_info *info = data->info; +	struct pmbus_sensor *sensor; + +	mutex_lock(&data->update_lock); +	if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { +		int i, j; + +		for (i = 0; i < info->pages; i++) { +			data->status[PB_STATUS_BASE + i] +			    = _pmbus_read_byte_data(client, i, +						    data->status_register); +			for (j = 0; j < ARRAY_SIZE(pmbus_status); j++) { +				struct _pmbus_status *s = &pmbus_status[j]; + +				if (!(info->func[i] & s->func)) +					continue; +				data->status[s->base + i] +					= _pmbus_read_byte_data(client, i, +								s->reg); +			} +		} + +		if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) +			data->status[PB_STATUS_INPUT_BASE] +			  = _pmbus_read_byte_data(client, 0, +						  PMBUS_STATUS_INPUT); + +		if (info->func[0] & PMBUS_HAVE_STATUS_VMON) +			data->status[PB_STATUS_VMON_BASE] +			  = _pmbus_read_byte_data(client, 0, +						  PMBUS_VIRT_STATUS_VMON); + +		for (sensor = data->sensors; sensor; sensor = sensor->next) { +			if (!data->valid || sensor->update) +				sensor->data +				    = _pmbus_read_word_data(client, +							    sensor->page, +							    sensor->reg); +		} +		pmbus_clear_faults(client); +		data->last_updated = jiffies; +		data->valid = 1; +	} +	mutex_unlock(&data->update_lock); +	return data; +} + +/* + * Convert linear sensor values to milli- or micro-units + * depending on sensor type. + */ +static long pmbus_reg2data_linear(struct pmbus_data *data, +				  struct pmbus_sensor *sensor) +{ +	s16 exponent; +	s32 mantissa; +	long val; + +	if (sensor->class == PSC_VOLTAGE_OUT) {	/* LINEAR16 */ +		exponent = data->exponent[sensor->page]; +		mantissa = (u16) sensor->data; +	} else {				/* LINEAR11 */ +		exponent = ((s16)sensor->data) >> 11; +		mantissa = ((s16)((sensor->data & 0x7ff) << 5)) >> 5; +	} + +	val = mantissa; + +	/* scale result to milli-units for all sensors except fans */ +	if (sensor->class != PSC_FAN) +		val = val * 1000L; + +	/* scale result to micro-units for power sensors */ +	if (sensor->class == PSC_POWER) +		val = val * 1000L; + +	if (exponent >= 0) +		val <<= exponent; +	else +		val >>= -exponent; + +	return val; +} + +/* + * Convert direct sensor values to milli- or micro-units + * depending on sensor type. + */ +static long pmbus_reg2data_direct(struct pmbus_data *data, +				  struct pmbus_sensor *sensor) +{ +	long val = (s16) sensor->data; +	long m, b, R; + +	m = data->info->m[sensor->class]; +	b = data->info->b[sensor->class]; +	R = data->info->R[sensor->class]; + +	if (m == 0) +		return 0; + +	/* X = 1/m * (Y * 10^-R - b) */ +	R = -R; +	/* scale result to milli-units for everything but fans */ +	if (sensor->class != PSC_FAN) { +		R += 3; +		b *= 1000; +	} + +	/* scale result to micro-units for power sensors */ +	if (sensor->class == PSC_POWER) { +		R += 3; +		b *= 1000; +	} + +	while (R > 0) { +		val *= 10; +		R--; +	} +	while (R < 0) { +		val = DIV_ROUND_CLOSEST(val, 10); +		R++; +	} + +	return (val - b) / m; +} + +/* + * Convert VID sensor values to milli- or micro-units + * depending on sensor type. + * We currently only support VR11. + */ +static long pmbus_reg2data_vid(struct pmbus_data *data, +			       struct pmbus_sensor *sensor) +{ +	long val = sensor->data; + +	if (val < 0x02 || val > 0xb2) +		return 0; +	return DIV_ROUND_CLOSEST(160000 - (val - 2) * 625, 100); +} + +static long pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor) +{ +	long val; + +	switch (data->info->format[sensor->class]) { +	case direct: +		val = pmbus_reg2data_direct(data, sensor); +		break; +	case vid: +		val = pmbus_reg2data_vid(data, sensor); +		break; +	case linear: +	default: +		val = pmbus_reg2data_linear(data, sensor); +		break; +	} +	return val; +} + +#define MAX_MANTISSA	(1023 * 1000) +#define MIN_MANTISSA	(511 * 1000) + +static u16 pmbus_data2reg_linear(struct pmbus_data *data, +				 struct pmbus_sensor *sensor, long val) +{ +	s16 exponent = 0, mantissa; +	bool negative = false; + +	/* simple case */ +	if (val == 0) +		return 0; + +	if (sensor->class == PSC_VOLTAGE_OUT) { +		/* LINEAR16 does not support negative voltages */ +		if (val < 0) +			return 0; + +		/* +		 * For a static exponents, we don't have a choice +		 * but to adjust the value to it. +		 */ +		if (data->exponent[sensor->page] < 0) +			val <<= -data->exponent[sensor->page]; +		else +			val >>= data->exponent[sensor->page]; +		val = DIV_ROUND_CLOSEST(val, 1000); +		return val & 0xffff; +	} + +	if (val < 0) { +		negative = true; +		val = -val; +	} + +	/* Power is in uW. Convert to mW before converting. */ +	if (sensor->class == PSC_POWER) +		val = DIV_ROUND_CLOSEST(val, 1000L); + +	/* +	 * For simplicity, convert fan data to milli-units +	 * before calculating the exponent. +	 */ +	if (sensor->class == PSC_FAN) +		val = val * 1000; + +	/* Reduce large mantissa until it fits into 10 bit */ +	while (val >= MAX_MANTISSA && exponent < 15) { +		exponent++; +		val >>= 1; +	} +	/* Increase small mantissa to improve precision */ +	while (val < MIN_MANTISSA && exponent > -15) { +		exponent--; +		val <<= 1; +	} + +	/* Convert mantissa from milli-units to units */ +	mantissa = DIV_ROUND_CLOSEST(val, 1000); + +	/* Ensure that resulting number is within range */ +	if (mantissa > 0x3ff) +		mantissa = 0x3ff; + +	/* restore sign */ +	if (negative) +		mantissa = -mantissa; + +	/* Convert to 5 bit exponent, 11 bit mantissa */ +	return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); +} + +static u16 pmbus_data2reg_direct(struct pmbus_data *data, +				 struct pmbus_sensor *sensor, long val) +{ +	long m, b, R; + +	m = data->info->m[sensor->class]; +	b = data->info->b[sensor->class]; +	R = data->info->R[sensor->class]; + +	/* Power is in uW. Adjust R and b. */ +	if (sensor->class == PSC_POWER) { +		R -= 3; +		b *= 1000; +	} + +	/* Calculate Y = (m * X + b) * 10^R */ +	if (sensor->class != PSC_FAN) { +		R -= 3;		/* Adjust R and b for data in milli-units */ +		b *= 1000; +	} +	val = val * m + b; + +	while (R > 0) { +		val *= 10; +		R--; +	} +	while (R < 0) { +		val = DIV_ROUND_CLOSEST(val, 10); +		R++; +	} + +	return val; +} + +static u16 pmbus_data2reg_vid(struct pmbus_data *data, +			      struct pmbus_sensor *sensor, long val) +{ +	val = clamp_val(val, 500, 1600); + +	return 2 + DIV_ROUND_CLOSEST((1600 - val) * 100, 625); +} + +static u16 pmbus_data2reg(struct pmbus_data *data, +			  struct pmbus_sensor *sensor, long val) +{ +	u16 regval; + +	switch (data->info->format[sensor->class]) { +	case direct: +		regval = pmbus_data2reg_direct(data, sensor, val); +		break; +	case vid: +		regval = pmbus_data2reg_vid(data, sensor, val); +		break; +	case linear: +	default: +		regval = pmbus_data2reg_linear(data, sensor, val); +		break; +	} +	return regval; +} + +/* + * Return boolean calculated from converted data. + * <index> defines a status register index and mask. + * The mask is in the lower 8 bits, the register index is in bits 8..23. + * + * The associated pmbus_boolean structure contains optional pointers to two + * sensor attributes. If specified, those attributes are compared against each + * other to determine if a limit has been exceeded. + * + * If the sensor attribute pointers are NULL, the function returns true if + * (status[reg] & mask) is true. + * + * If sensor attribute pointers are provided, a comparison against a specified + * limit has to be performed to determine the boolean result. + * In this case, the function returns true if v1 >= v2 (where v1 and v2 are + * sensor values referenced by sensor attribute pointers s1 and s2). + * + * To determine if an object exceeds upper limits, specify <s1,s2> = <v,limit>. + * To determine if an object exceeds lower limits, specify <s1,s2> = <limit,v>. + * + * If a negative value is stored in any of the referenced registers, this value + * reflects an error code which will be returned. + */ +static int pmbus_get_boolean(struct pmbus_data *data, struct pmbus_boolean *b, +			     int index) +{ +	struct pmbus_sensor *s1 = b->s1; +	struct pmbus_sensor *s2 = b->s2; +	u16 reg = (index >> 8) & 0xffff; +	u8 mask = index & 0xff; +	int ret, status; +	u8 regval; + +	status = data->status[reg]; +	if (status < 0) +		return status; + +	regval = status & mask; +	if (!s1 && !s2) { +		ret = !!regval; +	} else if (!s1 || !s2) { +		WARN(1, "Bad boolean descriptor %p: s1=%p, s2=%p\n", b, s1, s2); +		return 0; +	} else { +		long v1, v2; + +		if (s1->data < 0) +			return s1->data; +		if (s2->data < 0) +			return s2->data; + +		v1 = pmbus_reg2data(data, s1); +		v2 = pmbus_reg2data(data, s2); +		ret = !!(regval && v1 >= v2); +	} +	return ret; +} + +static ssize_t pmbus_show_boolean(struct device *dev, +				  struct device_attribute *da, char *buf) +{ +	struct sensor_device_attribute *attr = to_sensor_dev_attr(da); +	struct pmbus_boolean *boolean = to_pmbus_boolean(attr); +	struct pmbus_data *data = pmbus_update_device(dev); +	int val; + +	val = pmbus_get_boolean(data, boolean, attr->index); +	if (val < 0) +		return val; +	return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t pmbus_show_sensor(struct device *dev, +				 struct device_attribute *devattr, char *buf) +{ +	struct pmbus_data *data = pmbus_update_device(dev); +	struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); + +	if (sensor->data < 0) +		return sensor->data; + +	return snprintf(buf, PAGE_SIZE, "%ld\n", pmbus_reg2data(data, sensor)); +} + +static ssize_t pmbus_set_sensor(struct device *dev, +				struct device_attribute *devattr, +				const char *buf, size_t count) +{ +	struct i2c_client *client = to_i2c_client(dev->parent); +	struct pmbus_data *data = i2c_get_clientdata(client); +	struct pmbus_sensor *sensor = to_pmbus_sensor(devattr); +	ssize_t rv = count; +	long val = 0; +	int ret; +	u16 regval; + +	if (kstrtol(buf, 10, &val) < 0) +		return -EINVAL; + +	mutex_lock(&data->update_lock); +	regval = pmbus_data2reg(data, sensor, val); +	ret = _pmbus_write_word_data(client, sensor->page, sensor->reg, regval); +	if (ret < 0) +		rv = ret; +	else +		sensor->data = regval; +	mutex_unlock(&data->update_lock); +	return rv; +} + +static ssize_t pmbus_show_label(struct device *dev, +				struct device_attribute *da, char *buf) +{ +	struct pmbus_label *label = to_pmbus_label(da); + +	return snprintf(buf, PAGE_SIZE, "%s\n", label->label); +} + +static int pmbus_add_attribute(struct pmbus_data *data, struct attribute *attr) +{ +	if (data->num_attributes >= data->max_attributes - 1) { +		int new_max_attrs = data->max_attributes + PMBUS_ATTR_ALLOC_SIZE; +		void *new_attrs = krealloc(data->group.attrs, +					   new_max_attrs * sizeof(void *), +					   GFP_KERNEL); +		if (!new_attrs) +			return -ENOMEM; +		data->group.attrs = new_attrs; +		data->max_attributes = new_max_attrs; +	} + +	data->group.attrs[data->num_attributes++] = attr; +	data->group.attrs[data->num_attributes] = NULL; +	return 0; +} + +static void pmbus_dev_attr_init(struct device_attribute *dev_attr, +				const char *name, +				umode_t mode, +				ssize_t (*show)(struct device *dev, +						struct device_attribute *attr, +						char *buf), +				ssize_t (*store)(struct device *dev, +						 struct device_attribute *attr, +						 const char *buf, size_t count)) +{ +	sysfs_attr_init(&dev_attr->attr); +	dev_attr->attr.name = name; +	dev_attr->attr.mode = mode; +	dev_attr->show = show; +	dev_attr->store = store; +} + +static void pmbus_attr_init(struct sensor_device_attribute *a, +			    const char *name, +			    umode_t mode, +			    ssize_t (*show)(struct device *dev, +					    struct device_attribute *attr, +					    char *buf), +			    ssize_t (*store)(struct device *dev, +					     struct device_attribute *attr, +					     const char *buf, size_t count), +			    int idx) +{ +	pmbus_dev_attr_init(&a->dev_attr, name, mode, show, store); +	a->index = idx; +} + +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) +{ +	struct pmbus_boolean *boolean; +	struct sensor_device_attribute *a; + +	boolean = devm_kzalloc(data->dev, sizeof(*boolean), GFP_KERNEL); +	if (!boolean) +		return -ENOMEM; + +	a = &boolean->attribute; + +	snprintf(boolean->name, sizeof(boolean->name), "%s%d_%s", +		 name, seq, type); +	boolean->s1 = s1; +	boolean->s2 = s2; +	pmbus_attr_init(a, boolean->name, S_IRUGO, pmbus_show_boolean, NULL, +			(reg << 8) | mask); + +	return pmbus_add_attribute(data, &a->dev_attr.attr); +} + +static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, +					     const char *name, const char *type, +					     int seq, int page, int reg, +					     enum pmbus_sensor_classes class, +					     bool update, bool readonly) +{ +	struct pmbus_sensor *sensor; +	struct device_attribute *a; + +	sensor = devm_kzalloc(data->dev, sizeof(*sensor), GFP_KERNEL); +	if (!sensor) +		return NULL; +	a = &sensor->attribute; + +	snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s", +		 name, seq, type); +	sensor->page = page; +	sensor->reg = reg; +	sensor->class = class; +	sensor->update = update; +	pmbus_dev_attr_init(a, sensor->name, +			    readonly ? S_IRUGO : S_IRUGO | S_IWUSR, +			    pmbus_show_sensor, pmbus_set_sensor); + +	if (pmbus_add_attribute(data, &a->attr)) +		return NULL; + +	sensor->next = data->sensors; +	data->sensors = sensor; + +	return sensor; +} + +static int pmbus_add_label(struct pmbus_data *data, +			   const char *name, int seq, +			   const char *lstring, int index) +{ +	struct pmbus_label *label; +	struct device_attribute *a; + +	label = devm_kzalloc(data->dev, sizeof(*label), GFP_KERNEL); +	if (!label) +		return -ENOMEM; + +	a = &label->attribute; + +	snprintf(label->name, sizeof(label->name), "%s%d_label", name, seq); +	if (!index) +		strncpy(label->label, lstring, sizeof(label->label) - 1); +	else +		snprintf(label->label, sizeof(label->label), "%s%d", lstring, +			 index); + +	pmbus_dev_attr_init(a, label->name, S_IRUGO, pmbus_show_label, NULL); +	return pmbus_add_attribute(data, &a->attr); +} + +/* + * Search for attributes. Allocate sensors, booleans, and labels as needed. + */ + +/* + * The pmbus_limit_attr structure describes a single limit attribute + * and its associated alarm attribute. + */ +struct pmbus_limit_attr { +	u16 reg;		/* Limit register */ +	u16 sbit;		/* Alarm attribute status bit */ +	bool update;		/* True if register needs updates */ +	bool low;		/* True if low limit; for limits with compare +				   functions only */ +	const char *attr;	/* Attribute name */ +	const char *alarm;	/* Alarm attribute name */ +}; + +/* + * The pmbus_sensor_attr structure describes one sensor attribute. This + * description includes a reference to the associated limit attributes. + */ +struct pmbus_sensor_attr { +	u16 reg;			/* sensor register */ +	u8 gbit;			/* generic status bit */ +	u8 nlimit;			/* # of limit registers */ +	enum pmbus_sensor_classes class;/* sensor class */ +	const char *label;		/* sensor label */ +	bool paged;			/* true if paged sensor */ +	bool update;			/* true if update needed */ +	bool compare;			/* true if compare function needed */ +	u32 func;			/* sensor mask */ +	u32 sfunc;			/* sensor status mask */ +	int sbase;			/* status base register */ +	const struct pmbus_limit_attr *limit;/* limit registers */ +}; + +/* + * Add a set of limit attributes and, if supported, the associated + * alarm attributes. + * returns 0 if no alarm register found, 1 if an alarm register was found, + * < 0 on errors. + */ +static int pmbus_add_limit_attrs(struct i2c_client *client, +				 struct pmbus_data *data, +				 const struct pmbus_driver_info *info, +				 const char *name, int index, int page, +				 struct pmbus_sensor *base, +				 const struct pmbus_sensor_attr *attr) +{ +	const struct pmbus_limit_attr *l = attr->limit; +	int nlimit = attr->nlimit; +	int have_alarm = 0; +	int i, ret; +	struct pmbus_sensor *curr; + +	for (i = 0; i < nlimit; i++) { +		if (pmbus_check_word_register(client, page, l->reg)) { +			curr = pmbus_add_sensor(data, name, l->attr, index, +						page, l->reg, attr->class, +						attr->update || l->update, +						false); +			if (!curr) +				return -ENOMEM; +			if (l->sbit && (info->func[page] & attr->sfunc)) { +				ret = pmbus_add_boolean(data, name, +					l->alarm, index, +					attr->compare ?  l->low ? curr : base +						      : NULL, +					attr->compare ? l->low ? base : curr +						      : NULL, +					attr->sbase + page, l->sbit); +				if (ret) +					return ret; +				have_alarm = 1; +			} +		} +		l++; +	} +	return have_alarm; +} + +static int pmbus_add_sensor_attrs_one(struct i2c_client *client, +				      struct pmbus_data *data, +				      const struct pmbus_driver_info *info, +				      const char *name, +				      int index, int page, +				      const struct pmbus_sensor_attr *attr) +{ +	struct pmbus_sensor *base; +	int ret; + +	if (attr->label) { +		ret = pmbus_add_label(data, name, index, attr->label, +				      attr->paged ? page + 1 : 0); +		if (ret) +			return ret; +	} +	base = pmbus_add_sensor(data, name, "input", index, page, attr->reg, +				attr->class, true, true); +	if (!base) +		return -ENOMEM; +	if (attr->sfunc) { +		ret = pmbus_add_limit_attrs(client, data, info, name, +					    index, page, base, attr); +		if (ret < 0) +			return ret; +		/* +		 * 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. +		 */ +		if (!ret && attr->gbit && +		    pmbus_check_byte_register(client, page, +					      data->status_register)) { +			ret = pmbus_add_boolean(data, name, "alarm", index, +						NULL, NULL, +						PB_STATUS_BASE + page, +						attr->gbit); +			if (ret) +				return ret; +		} +	} +	return 0; +} + +static int pmbus_add_sensor_attrs(struct i2c_client *client, +				  struct pmbus_data *data, +				  const char *name, +				  const struct pmbus_sensor_attr *attrs, +				  int nattrs) +{ +	const struct pmbus_driver_info *info = data->info; +	int index, i; +	int ret; + +	index = 1; +	for (i = 0; i < nattrs; i++) { +		int page, pages; + +		pages = attrs->paged ? info->pages : 1; +		for (page = 0; page < pages; page++) { +			if (!(info->func[page] & attrs->func)) +				continue; +			ret = pmbus_add_sensor_attrs_one(client, data, info, +							 name, index, page, +							 attrs); +			if (ret) +				return ret; +			index++; +		} +		attrs++; +	} +	return 0; +} + +static const struct pmbus_limit_attr vin_limit_attrs[] = { +	{ +		.reg = PMBUS_VIN_UV_WARN_LIMIT, +		.attr = "min", +		.alarm = "min_alarm", +		.sbit = PB_VOLTAGE_UV_WARNING, +	}, { +		.reg = PMBUS_VIN_UV_FAULT_LIMIT, +		.attr = "lcrit", +		.alarm = "lcrit_alarm", +		.sbit = PB_VOLTAGE_UV_FAULT, +	}, { +		.reg = PMBUS_VIN_OV_WARN_LIMIT, +		.attr = "max", +		.alarm = "max_alarm", +		.sbit = PB_VOLTAGE_OV_WARNING, +	}, { +		.reg = PMBUS_VIN_OV_FAULT_LIMIT, +		.attr = "crit", +		.alarm = "crit_alarm", +		.sbit = PB_VOLTAGE_OV_FAULT, +	}, { +		.reg = PMBUS_VIRT_READ_VIN_AVG, +		.update = true, +		.attr = "average", +	}, { +		.reg = PMBUS_VIRT_READ_VIN_MIN, +		.update = true, +		.attr = "lowest", +	}, { +		.reg = PMBUS_VIRT_READ_VIN_MAX, +		.update = true, +		.attr = "highest", +	}, { +		.reg = PMBUS_VIRT_RESET_VIN_HISTORY, +		.attr = "reset_history", +	}, +}; + +static const struct pmbus_limit_attr vmon_limit_attrs[] = { +	{ +		.reg = PMBUS_VIRT_VMON_UV_WARN_LIMIT, +		.attr = "min", +		.alarm = "min_alarm", +		.sbit = PB_VOLTAGE_UV_WARNING, +	}, { +		.reg = PMBUS_VIRT_VMON_UV_FAULT_LIMIT, +		.attr = "lcrit", +		.alarm = "lcrit_alarm", +		.sbit = PB_VOLTAGE_UV_FAULT, +	}, { +		.reg = PMBUS_VIRT_VMON_OV_WARN_LIMIT, +		.attr = "max", +		.alarm = "max_alarm", +		.sbit = PB_VOLTAGE_OV_WARNING, +	}, { +		.reg = PMBUS_VIRT_VMON_OV_FAULT_LIMIT, +		.attr = "crit", +		.alarm = "crit_alarm", +		.sbit = PB_VOLTAGE_OV_FAULT, +	} +}; + +static const struct pmbus_limit_attr vout_limit_attrs[] = { +	{ +		.reg = PMBUS_VOUT_UV_WARN_LIMIT, +		.attr = "min", +		.alarm = "min_alarm", +		.sbit = PB_VOLTAGE_UV_WARNING, +	}, { +		.reg = PMBUS_VOUT_UV_FAULT_LIMIT, +		.attr = "lcrit", +		.alarm = "lcrit_alarm", +		.sbit = PB_VOLTAGE_UV_FAULT, +	}, { +		.reg = PMBUS_VOUT_OV_WARN_LIMIT, +		.attr = "max", +		.alarm = "max_alarm", +		.sbit = PB_VOLTAGE_OV_WARNING, +	}, { +		.reg = PMBUS_VOUT_OV_FAULT_LIMIT, +		.attr = "crit", +		.alarm = "crit_alarm", +		.sbit = PB_VOLTAGE_OV_FAULT, +	}, { +		.reg = PMBUS_VIRT_READ_VOUT_AVG, +		.update = true, +		.attr = "average", +	}, { +		.reg = PMBUS_VIRT_READ_VOUT_MIN, +		.update = true, +		.attr = "lowest", +	}, { +		.reg = PMBUS_VIRT_READ_VOUT_MAX, +		.update = true, +		.attr = "highest", +	}, { +		.reg = PMBUS_VIRT_RESET_VOUT_HISTORY, +		.attr = "reset_history", +	} +}; + +static const struct pmbus_sensor_attr voltage_attributes[] = { +	{ +		.reg = PMBUS_READ_VIN, +		.class = PSC_VOLTAGE_IN, +		.label = "vin", +		.func = PMBUS_HAVE_VIN, +		.sfunc = PMBUS_HAVE_STATUS_INPUT, +		.sbase = PB_STATUS_INPUT_BASE, +		.gbit = PB_STATUS_VIN_UV, +		.limit = vin_limit_attrs, +		.nlimit = ARRAY_SIZE(vin_limit_attrs), +	}, { +		.reg = PMBUS_VIRT_READ_VMON, +		.class = PSC_VOLTAGE_IN, +		.label = "vmon", +		.func = PMBUS_HAVE_VMON, +		.sfunc = PMBUS_HAVE_STATUS_VMON, +		.sbase = PB_STATUS_VMON_BASE, +		.limit = vmon_limit_attrs, +		.nlimit = ARRAY_SIZE(vmon_limit_attrs), +	}, { +		.reg = PMBUS_READ_VCAP, +		.class = PSC_VOLTAGE_IN, +		.label = "vcap", +		.func = PMBUS_HAVE_VCAP, +	}, { +		.reg = PMBUS_READ_VOUT, +		.class = PSC_VOLTAGE_OUT, +		.label = "vout", +		.paged = true, +		.func = PMBUS_HAVE_VOUT, +		.sfunc = PMBUS_HAVE_STATUS_VOUT, +		.sbase = PB_STATUS_VOUT_BASE, +		.gbit = PB_STATUS_VOUT_OV, +		.limit = vout_limit_attrs, +		.nlimit = ARRAY_SIZE(vout_limit_attrs), +	} +}; + +/* Current attributes */ + +static const struct pmbus_limit_attr iin_limit_attrs[] = { +	{ +		.reg = PMBUS_IIN_OC_WARN_LIMIT, +		.attr = "max", +		.alarm = "max_alarm", +		.sbit = PB_IIN_OC_WARNING, +	}, { +		.reg = PMBUS_IIN_OC_FAULT_LIMIT, +		.attr = "crit", +		.alarm = "crit_alarm", +		.sbit = PB_IIN_OC_FAULT, +	}, { +		.reg = PMBUS_VIRT_READ_IIN_AVG, +		.update = true, +		.attr = "average", +	}, { +		.reg = PMBUS_VIRT_READ_IIN_MIN, +		.update = true, +		.attr = "lowest", +	}, { +		.reg = PMBUS_VIRT_READ_IIN_MAX, +		.update = true, +		.attr = "highest", +	}, { +		.reg = PMBUS_VIRT_RESET_IIN_HISTORY, +		.attr = "reset_history", +	} +}; + +static const struct pmbus_limit_attr iout_limit_attrs[] = { +	{ +		.reg = PMBUS_IOUT_OC_WARN_LIMIT, +		.attr = "max", +		.alarm = "max_alarm", +		.sbit = PB_IOUT_OC_WARNING, +	}, { +		.reg = PMBUS_IOUT_UC_FAULT_LIMIT, +		.attr = "lcrit", +		.alarm = "lcrit_alarm", +		.sbit = PB_IOUT_UC_FAULT, +	}, { +		.reg = PMBUS_IOUT_OC_FAULT_LIMIT, +		.attr = "crit", +		.alarm = "crit_alarm", +		.sbit = PB_IOUT_OC_FAULT, +	}, { +		.reg = PMBUS_VIRT_READ_IOUT_AVG, +		.update = true, +		.attr = "average", +	}, { +		.reg = PMBUS_VIRT_READ_IOUT_MIN, +		.update = true, +		.attr = "lowest", +	}, { +		.reg = PMBUS_VIRT_READ_IOUT_MAX, +		.update = true, +		.attr = "highest", +	}, { +		.reg = PMBUS_VIRT_RESET_IOUT_HISTORY, +		.attr = "reset_history", +	} +}; + +static const struct pmbus_sensor_attr current_attributes[] = { +	{ +		.reg = PMBUS_READ_IIN, +		.class = PSC_CURRENT_IN, +		.label = "iin", +		.func = PMBUS_HAVE_IIN, +		.sfunc = PMBUS_HAVE_STATUS_INPUT, +		.sbase = PB_STATUS_INPUT_BASE, +		.limit = iin_limit_attrs, +		.nlimit = ARRAY_SIZE(iin_limit_attrs), +	}, { +		.reg = PMBUS_READ_IOUT, +		.class = PSC_CURRENT_OUT, +		.label = "iout", +		.paged = true, +		.func = PMBUS_HAVE_IOUT, +		.sfunc = PMBUS_HAVE_STATUS_IOUT, +		.sbase = PB_STATUS_IOUT_BASE, +		.gbit = PB_STATUS_IOUT_OC, +		.limit = iout_limit_attrs, +		.nlimit = ARRAY_SIZE(iout_limit_attrs), +	} +}; + +/* Power attributes */ + +static const struct pmbus_limit_attr pin_limit_attrs[] = { +	{ +		.reg = PMBUS_PIN_OP_WARN_LIMIT, +		.attr = "max", +		.alarm = "alarm", +		.sbit = PB_PIN_OP_WARNING, +	}, { +		.reg = PMBUS_VIRT_READ_PIN_AVG, +		.update = true, +		.attr = "average", +	}, { +		.reg = PMBUS_VIRT_READ_PIN_MAX, +		.update = true, +		.attr = "input_highest", +	}, { +		.reg = PMBUS_VIRT_RESET_PIN_HISTORY, +		.attr = "reset_history", +	} +}; + +static const struct pmbus_limit_attr pout_limit_attrs[] = { +	{ +		.reg = PMBUS_POUT_MAX, +		.attr = "cap", +		.alarm = "cap_alarm", +		.sbit = PB_POWER_LIMITING, +	}, { +		.reg = PMBUS_POUT_OP_WARN_LIMIT, +		.attr = "max", +		.alarm = "max_alarm", +		.sbit = PB_POUT_OP_WARNING, +	}, { +		.reg = PMBUS_POUT_OP_FAULT_LIMIT, +		.attr = "crit", +		.alarm = "crit_alarm", +		.sbit = PB_POUT_OP_FAULT, +	}, { +		.reg = PMBUS_VIRT_READ_POUT_AVG, +		.update = true, +		.attr = "average", +	}, { +		.reg = PMBUS_VIRT_READ_POUT_MAX, +		.update = true, +		.attr = "input_highest", +	}, { +		.reg = PMBUS_VIRT_RESET_POUT_HISTORY, +		.attr = "reset_history", +	} +}; + +static const struct pmbus_sensor_attr power_attributes[] = { +	{ +		.reg = PMBUS_READ_PIN, +		.class = PSC_POWER, +		.label = "pin", +		.func = PMBUS_HAVE_PIN, +		.sfunc = PMBUS_HAVE_STATUS_INPUT, +		.sbase = PB_STATUS_INPUT_BASE, +		.limit = pin_limit_attrs, +		.nlimit = ARRAY_SIZE(pin_limit_attrs), +	}, { +		.reg = PMBUS_READ_POUT, +		.class = PSC_POWER, +		.label = "pout", +		.paged = true, +		.func = PMBUS_HAVE_POUT, +		.sfunc = PMBUS_HAVE_STATUS_IOUT, +		.sbase = PB_STATUS_IOUT_BASE, +		.limit = pout_limit_attrs, +		.nlimit = ARRAY_SIZE(pout_limit_attrs), +	} +}; + +/* Temperature atributes */ + +static const struct pmbus_limit_attr temp_limit_attrs[] = { +	{ +		.reg = PMBUS_UT_WARN_LIMIT, +		.low = true, +		.attr = "min", +		.alarm = "min_alarm", +		.sbit = PB_TEMP_UT_WARNING, +	}, { +		.reg = PMBUS_UT_FAULT_LIMIT, +		.low = true, +		.attr = "lcrit", +		.alarm = "lcrit_alarm", +		.sbit = PB_TEMP_UT_FAULT, +	}, { +		.reg = PMBUS_OT_WARN_LIMIT, +		.attr = "max", +		.alarm = "max_alarm", +		.sbit = PB_TEMP_OT_WARNING, +	}, { +		.reg = PMBUS_OT_FAULT_LIMIT, +		.attr = "crit", +		.alarm = "crit_alarm", +		.sbit = PB_TEMP_OT_FAULT, +	}, { +		.reg = PMBUS_VIRT_READ_TEMP_MIN, +		.attr = "lowest", +	}, { +		.reg = PMBUS_VIRT_READ_TEMP_AVG, +		.attr = "average", +	}, { +		.reg = PMBUS_VIRT_READ_TEMP_MAX, +		.attr = "highest", +	}, { +		.reg = PMBUS_VIRT_RESET_TEMP_HISTORY, +		.attr = "reset_history", +	} +}; + +static const struct pmbus_limit_attr temp_limit_attrs2[] = { +	{ +		.reg = PMBUS_UT_WARN_LIMIT, +		.low = true, +		.attr = "min", +		.alarm = "min_alarm", +		.sbit = PB_TEMP_UT_WARNING, +	}, { +		.reg = PMBUS_UT_FAULT_LIMIT, +		.low = true, +		.attr = "lcrit", +		.alarm = "lcrit_alarm", +		.sbit = PB_TEMP_UT_FAULT, +	}, { +		.reg = PMBUS_OT_WARN_LIMIT, +		.attr = "max", +		.alarm = "max_alarm", +		.sbit = PB_TEMP_OT_WARNING, +	}, { +		.reg = PMBUS_OT_FAULT_LIMIT, +		.attr = "crit", +		.alarm = "crit_alarm", +		.sbit = PB_TEMP_OT_FAULT, +	}, { +		.reg = PMBUS_VIRT_READ_TEMP2_MIN, +		.attr = "lowest", +	}, { +		.reg = PMBUS_VIRT_READ_TEMP2_AVG, +		.attr = "average", +	}, { +		.reg = PMBUS_VIRT_READ_TEMP2_MAX, +		.attr = "highest", +	}, { +		.reg = PMBUS_VIRT_RESET_TEMP2_HISTORY, +		.attr = "reset_history", +	} +}; + +static const struct pmbus_limit_attr temp_limit_attrs3[] = { +	{ +		.reg = PMBUS_UT_WARN_LIMIT, +		.low = true, +		.attr = "min", +		.alarm = "min_alarm", +		.sbit = PB_TEMP_UT_WARNING, +	}, { +		.reg = PMBUS_UT_FAULT_LIMIT, +		.low = true, +		.attr = "lcrit", +		.alarm = "lcrit_alarm", +		.sbit = PB_TEMP_UT_FAULT, +	}, { +		.reg = PMBUS_OT_WARN_LIMIT, +		.attr = "max", +		.alarm = "max_alarm", +		.sbit = PB_TEMP_OT_WARNING, +	}, { +		.reg = PMBUS_OT_FAULT_LIMIT, +		.attr = "crit", +		.alarm = "crit_alarm", +		.sbit = PB_TEMP_OT_FAULT, +	} +}; + +static const struct pmbus_sensor_attr temp_attributes[] = { +	{ +		.reg = PMBUS_READ_TEMPERATURE_1, +		.class = PSC_TEMPERATURE, +		.paged = true, +		.update = true, +		.compare = true, +		.func = PMBUS_HAVE_TEMP, +		.sfunc = PMBUS_HAVE_STATUS_TEMP, +		.sbase = PB_STATUS_TEMP_BASE, +		.gbit = PB_STATUS_TEMPERATURE, +		.limit = temp_limit_attrs, +		.nlimit = ARRAY_SIZE(temp_limit_attrs), +	}, { +		.reg = PMBUS_READ_TEMPERATURE_2, +		.class = PSC_TEMPERATURE, +		.paged = true, +		.update = true, +		.compare = true, +		.func = PMBUS_HAVE_TEMP2, +		.sfunc = PMBUS_HAVE_STATUS_TEMP, +		.sbase = PB_STATUS_TEMP_BASE, +		.gbit = PB_STATUS_TEMPERATURE, +		.limit = temp_limit_attrs2, +		.nlimit = ARRAY_SIZE(temp_limit_attrs2), +	}, { +		.reg = PMBUS_READ_TEMPERATURE_3, +		.class = PSC_TEMPERATURE, +		.paged = true, +		.update = true, +		.compare = true, +		.func = PMBUS_HAVE_TEMP3, +		.sfunc = PMBUS_HAVE_STATUS_TEMP, +		.sbase = PB_STATUS_TEMP_BASE, +		.gbit = PB_STATUS_TEMPERATURE, +		.limit = temp_limit_attrs3, +		.nlimit = ARRAY_SIZE(temp_limit_attrs3), +	} +}; + +static const int pmbus_fan_registers[] = { +	PMBUS_READ_FAN_SPEED_1, +	PMBUS_READ_FAN_SPEED_2, +	PMBUS_READ_FAN_SPEED_3, +	PMBUS_READ_FAN_SPEED_4 +}; + +static const int pmbus_fan_config_registers[] = { +	PMBUS_FAN_CONFIG_12, +	PMBUS_FAN_CONFIG_12, +	PMBUS_FAN_CONFIG_34, +	PMBUS_FAN_CONFIG_34 +}; + +static const int pmbus_fan_status_registers[] = { +	PMBUS_STATUS_FAN_12, +	PMBUS_STATUS_FAN_12, +	PMBUS_STATUS_FAN_34, +	PMBUS_STATUS_FAN_34 +}; + +static const u32 pmbus_fan_flags[] = { +	PMBUS_HAVE_FAN12, +	PMBUS_HAVE_FAN12, +	PMBUS_HAVE_FAN34, +	PMBUS_HAVE_FAN34 +}; + +static const u32 pmbus_fan_status_flags[] = { +	PMBUS_HAVE_STATUS_FAN12, +	PMBUS_HAVE_STATUS_FAN12, +	PMBUS_HAVE_STATUS_FAN34, +	PMBUS_HAVE_STATUS_FAN34 +}; + +/* Fans */ +static int pmbus_add_fan_attributes(struct i2c_client *client, +				    struct pmbus_data *data) +{ +	const struct pmbus_driver_info *info = data->info; +	int index = 1; +	int page; +	int ret; + +	for (page = 0; page < info->pages; page++) { +		int f; + +		for (f = 0; f < ARRAY_SIZE(pmbus_fan_registers); f++) { +			int regval; + +			if (!(info->func[page] & pmbus_fan_flags[f])) +				break; + +			if (!pmbus_check_word_register(client, page, +						       pmbus_fan_registers[f])) +				break; + +			/* +			 * Skip fan if not installed. +			 * Each fan configuration register covers multiple fans, +			 * so we have to do some magic. +			 */ +			regval = _pmbus_read_byte_data(client, page, +				pmbus_fan_config_registers[f]); +			if (regval < 0 || +			    (!(regval & (PB_FAN_1_INSTALLED >> ((f & 1) * 4))))) +				continue; + +			if (pmbus_add_sensor(data, "fan", "input", index, +					     page, pmbus_fan_registers[f], +					     PSC_FAN, true, true) == NULL) +				return -ENOMEM; + +			/* +			 * Each fan status register covers multiple fans, +			 * so we have to do some magic. +			 */ +			if ((info->func[page] & pmbus_fan_status_flags[f]) && +			    pmbus_check_byte_register(client, +					page, pmbus_fan_status_registers[f])) { +				int base; + +				if (f > 1)	/* fan 3, 4 */ +					base = PB_STATUS_FAN34_BASE + page; +				else +					base = PB_STATUS_FAN_BASE + page; +				ret = pmbus_add_boolean(data, "fan", +					"alarm", index, NULL, NULL, base, +					PB_FAN_FAN1_WARNING >> (f & 1)); +				if (ret) +					return ret; +				ret = pmbus_add_boolean(data, "fan", +					"fault", index, NULL, NULL, base, +					PB_FAN_FAN1_FAULT >> (f & 1)); +				if (ret) +					return ret; +			} +			index++; +		} +	} +	return 0; +} + +static int pmbus_find_attributes(struct i2c_client *client, +				 struct pmbus_data *data) +{ +	int ret; + +	/* Voltage sensors */ +	ret = pmbus_add_sensor_attrs(client, data, "in", voltage_attributes, +				     ARRAY_SIZE(voltage_attributes)); +	if (ret) +		return ret; + +	/* Current sensors */ +	ret = pmbus_add_sensor_attrs(client, data, "curr", current_attributes, +				     ARRAY_SIZE(current_attributes)); +	if (ret) +		return ret; + +	/* Power sensors */ +	ret = pmbus_add_sensor_attrs(client, data, "power", power_attributes, +				     ARRAY_SIZE(power_attributes)); +	if (ret) +		return ret; + +	/* Temperature sensors */ +	ret = pmbus_add_sensor_attrs(client, data, "temp", temp_attributes, +				     ARRAY_SIZE(temp_attributes)); +	if (ret) +		return ret; + +	/* Fans */ +	ret = pmbus_add_fan_attributes(client, data); +	return ret; +} + +/* + * Identify chip parameters. + * This function is called for all chips. + */ +static int pmbus_identify_common(struct i2c_client *client, +				 struct pmbus_data *data, int page) +{ +	int vout_mode = -1; + +	if (pmbus_check_byte_register(client, page, PMBUS_VOUT_MODE)) +		vout_mode = _pmbus_read_byte_data(client, page, +						  PMBUS_VOUT_MODE); +	if (vout_mode >= 0 && vout_mode != 0xff) { +		/* +		 * Not all chips support the VOUT_MODE command, +		 * so a failure to read it is not an error. +		 */ +		switch (vout_mode >> 5) { +		case 0:	/* linear mode      */ +			if (data->info->format[PSC_VOLTAGE_OUT] != linear) +				return -ENODEV; + +			data->exponent[page] = ((s8)(vout_mode << 3)) >> 3; +			break; +		case 1: /* VID mode         */ +			if (data->info->format[PSC_VOLTAGE_OUT] != vid) +				return -ENODEV; +			break; +		case 2:	/* direct mode      */ +			if (data->info->format[PSC_VOLTAGE_OUT] != direct) +				return -ENODEV; +			break; +		default: +			return -ENODEV; +		} +	} + +	pmbus_clear_fault_page(client, page); +	return 0; +} + +static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, +			     struct pmbus_driver_info *info) +{ +	struct device *dev = &client->dev; +	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. +	 * 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) { +			dev_err(dev, "PMBus status register not found\n"); +			return -ENODEV; +		} +	} + +	pmbus_clear_faults(client); + +	if (info->identify) { +		ret = (*info->identify)(client, info); +		if (ret < 0) { +			dev_err(dev, "Chip identification failed\n"); +			return ret; +		} +	} + +	if (info->pages <= 0 || info->pages > PMBUS_PAGES) { +		dev_err(dev, "Bad number of PMBus pages: %d\n", info->pages); +		return -ENODEV; +	} + +	for (page = 0; page < info->pages; page++) { +		ret = pmbus_identify_common(client, data, page); +		if (ret < 0) { +			dev_err(dev, "Failed to identify chip capabilities\n"); +			return ret; +		} +	} +	return 0; +} + +int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, +		   struct pmbus_driver_info *info) +{ +	struct device *dev = &client->dev; +	const struct pmbus_platform_data *pdata = dev_get_platdata(dev); +	struct pmbus_data *data; +	int ret; + +	if (!info) +		return -ENODEV; + +	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_BYTE +				     | I2C_FUNC_SMBUS_BYTE_DATA +				     | I2C_FUNC_SMBUS_WORD_DATA)) +		return -ENODEV; + +	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); +	if (!data) +		return -ENOMEM; + +	i2c_set_clientdata(client, data); +	mutex_init(&data->update_lock); +	data->dev = dev; + +	if (pdata) +		data->flags = pdata->flags; +	data->info = info; + +	ret = pmbus_init_common(client, data, info); +	if (ret < 0) +		return ret; + +	ret = pmbus_find_attributes(client, data); +	if (ret) +		goto out_kfree; + +	/* +	 * If there are no attributes, something is wrong. +	 * Bail out instead of trying to register nothing. +	 */ +	if (!data->num_attributes) { +		dev_err(dev, "No attributes found\n"); +		ret = -ENODEV; +		goto out_kfree; +	} + +	data->groups[0] = &data->group; +	data->hwmon_dev = hwmon_device_register_with_groups(dev, client->name, +							    data, data->groups); +	if (IS_ERR(data->hwmon_dev)) { +		ret = PTR_ERR(data->hwmon_dev); +		dev_err(dev, "Failed to register hwmon device\n"); +		goto out_kfree; +	} +	return 0; + +out_kfree: +	kfree(data->group.attrs); +	return ret; +} +EXPORT_SYMBOL_GPL(pmbus_do_probe); + +int pmbus_do_remove(struct i2c_client *client) +{ +	struct pmbus_data *data = i2c_get_clientdata(client); +	hwmon_device_unregister(data->hwmon_dev); +	kfree(data->group.attrs); +	return 0; +} +EXPORT_SYMBOL_GPL(pmbus_do_remove); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/ucd9000.c b/drivers/hwmon/pmbus/ucd9000.c new file mode 100644 index 00000000000..fbb1479d3ad --- /dev/null +++ b/drivers/hwmon/pmbus/ucd9000.c @@ -0,0 +1,246 @@ +/* + * Hardware monitoring driver for UCD90xxx Sequencer and System Health + * Controller series + * + * Copyright (C) 2011 Ericsson AB. + * + * 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/i2c/pmbus.h> +#include "pmbus.h" + +enum chips { ucd9000, ucd90120, ucd90124, ucd9090, ucd90910 }; + +#define UCD9000_MONITOR_CONFIG		0xd5 +#define UCD9000_NUM_PAGES		0xd6 +#define UCD9000_FAN_CONFIG_INDEX	0xe7 +#define UCD9000_FAN_CONFIG		0xe8 +#define UCD9000_DEVICE_ID		0xfd + +#define UCD9000_MON_TYPE(x)	(((x) >> 5) & 0x07) +#define UCD9000_MON_PAGE(x)	((x) & 0x0f) + +#define UCD9000_MON_VOLTAGE	1 +#define UCD9000_MON_TEMPERATURE	2 +#define UCD9000_MON_CURRENT	3 +#define UCD9000_MON_VOLTAGE_HW	4 + +#define UCD9000_NUM_FAN		4 + +struct ucd9000_data { +	u8 fan_data[UCD9000_NUM_FAN][I2C_SMBUS_BLOCK_MAX]; +	struct pmbus_driver_info info; +}; +#define to_ucd9000_data(_info) container_of(_info, struct ucd9000_data, info) + +static int ucd9000_get_fan_config(struct i2c_client *client, int fan) +{ +	int fan_config = 0; +	struct ucd9000_data *data +	  = to_ucd9000_data(pmbus_get_driver_info(client)); + +	if (data->fan_data[fan][3] & 1) +		fan_config |= PB_FAN_2_INSTALLED;   /* Use lower bit position */ + +	/* Pulses/revolution */ +	fan_config |= (data->fan_data[fan][3] & 0x06) >> 1; + +	return fan_config; +} + +static int ucd9000_read_byte_data(struct i2c_client *client, int page, int reg) +{ +	int ret = 0; +	int fan_config; + +	switch (reg) { +	case PMBUS_FAN_CONFIG_12: +		if (page > 0) +			return -ENXIO; + +		ret = ucd9000_get_fan_config(client, 0); +		if (ret < 0) +			return ret; +		fan_config = ret << 4; +		ret = ucd9000_get_fan_config(client, 1); +		if (ret < 0) +			return ret; +		fan_config |= ret; +		ret = fan_config; +		break; +	case PMBUS_FAN_CONFIG_34: +		if (page > 0) +			return -ENXIO; + +		ret = ucd9000_get_fan_config(client, 2); +		if (ret < 0) +			return ret; +		fan_config = ret << 4; +		ret = ucd9000_get_fan_config(client, 3); +		if (ret < 0) +			return ret; +		fan_config |= ret; +		ret = fan_config; +		break; +	default: +		ret = -ENODATA; +		break; +	} +	return ret; +} + +static const struct i2c_device_id ucd9000_id[] = { +	{"ucd9000", ucd9000}, +	{"ucd90120", ucd90120}, +	{"ucd90124", ucd90124}, +	{"ucd9090", ucd9090}, +	{"ucd90910", ucd90910}, +	{} +}; +MODULE_DEVICE_TABLE(i2c, ucd9000_id); + +static int ucd9000_probe(struct i2c_client *client, +			 const struct i2c_device_id *id) +{ +	u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; +	struct ucd9000_data *data; +	struct pmbus_driver_info *info; +	const struct i2c_device_id *mid; +	int i, ret; + +	if (!i2c_check_functionality(client->adapter, +				     I2C_FUNC_SMBUS_BYTE_DATA | +				     I2C_FUNC_SMBUS_BLOCK_DATA)) +		return -ENODEV; + +	ret = i2c_smbus_read_block_data(client, UCD9000_DEVICE_ID, +					block_buffer); +	if (ret < 0) { +		dev_err(&client->dev, "Failed to read device ID\n"); +		return ret; +	} +	block_buffer[ret] = '\0'; +	dev_info(&client->dev, "Device ID %s\n", block_buffer); + +	for (mid = ucd9000_id; mid->name[0]; mid++) { +		if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) +			break; +	} +	if (!mid->name[0]) { +		dev_err(&client->dev, "Unsupported device\n"); +		return -ENODEV; +	} + +	if (id->driver_data != ucd9000 && id->driver_data != mid->driver_data) +		dev_notice(&client->dev, +			   "Device mismatch: Configured %s, detected %s\n", +			   id->name, mid->name); + +	data = devm_kzalloc(&client->dev, sizeof(struct ucd9000_data), +			    GFP_KERNEL); +	if (!data) +		return -ENOMEM; +	info = &data->info; + +	ret = i2c_smbus_read_byte_data(client, UCD9000_NUM_PAGES); +	if (ret < 0) { +		dev_err(&client->dev, +			"Failed to read number of active pages\n"); +		return ret; +	} +	info->pages = ret; +	if (!info->pages) { +		dev_err(&client->dev, "No pages configured\n"); +		return -ENODEV; +	} + +	/* The internal temperature sensor is always active */ +	info->func[0] = PMBUS_HAVE_TEMP; + +	/* Everything else is configurable */ +	ret = i2c_smbus_read_block_data(client, UCD9000_MONITOR_CONFIG, +					block_buffer); +	if (ret <= 0) { +		dev_err(&client->dev, "Failed to read configuration data\n"); +		return -ENODEV; +	} +	for (i = 0; i < ret; i++) { +		int page = UCD9000_MON_PAGE(block_buffer[i]); + +		if (page >= info->pages) +			continue; + +		switch (UCD9000_MON_TYPE(block_buffer[i])) { +		case UCD9000_MON_VOLTAGE: +		case UCD9000_MON_VOLTAGE_HW: +			info->func[page] |= PMBUS_HAVE_VOUT +			  | PMBUS_HAVE_STATUS_VOUT; +			break; +		case UCD9000_MON_TEMPERATURE: +			info->func[page] |= PMBUS_HAVE_TEMP2 +			  | PMBUS_HAVE_STATUS_TEMP; +			break; +		case UCD9000_MON_CURRENT: +			info->func[page] |= PMBUS_HAVE_IOUT +			  | PMBUS_HAVE_STATUS_IOUT; +			break; +		default: +			break; +		} +	} + +	/* Fan configuration */ +	if (mid->driver_data == ucd90124) { +		for (i = 0; i < UCD9000_NUM_FAN; i++) { +			i2c_smbus_write_byte_data(client, +						  UCD9000_FAN_CONFIG_INDEX, i); +			ret = i2c_smbus_read_block_data(client, +							UCD9000_FAN_CONFIG, +							data->fan_data[i]); +			if (ret < 0) +				return ret; +		} +		i2c_smbus_write_byte_data(client, UCD9000_FAN_CONFIG_INDEX, 0); + +		info->read_byte_data = ucd9000_read_byte_data; +		info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12 +		  | PMBUS_HAVE_FAN34 | PMBUS_HAVE_STATUS_FAN34; +	} + +	return pmbus_do_probe(client, mid, info); +} + +/* This is the driver that will be inserted */ +static struct i2c_driver ucd9000_driver = { +	.driver = { +		.name = "ucd9000", +	}, +	.probe = ucd9000_probe, +	.remove = pmbus_do_remove, +	.id_table = ucd9000_id, +}; + +module_i2c_driver(ucd9000_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for TI UCD90xxx"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/ucd9200.c b/drivers/hwmon/pmbus/ucd9200.c new file mode 100644 index 00000000000..033d6aca47d --- /dev/null +++ b/drivers/hwmon/pmbus/ucd9200.c @@ -0,0 +1,180 @@ +/* + * Hardware monitoring driver for ucd9200 series Digital PWM System Controllers + * + * Copyright (C) 2011 Ericsson AB. + * + * 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/i2c/pmbus.h> +#include "pmbus.h" + +#define UCD9200_PHASE_INFO	0xd2 +#define UCD9200_DEVICE_ID	0xfd + +enum chips { ucd9200, ucd9220, ucd9222, ucd9224, ucd9240, ucd9244, ucd9246, +	     ucd9248 }; + +static const struct i2c_device_id ucd9200_id[] = { +	{"ucd9200", ucd9200}, +	{"ucd9220", ucd9220}, +	{"ucd9222", ucd9222}, +	{"ucd9224", ucd9224}, +	{"ucd9240", ucd9240}, +	{"ucd9244", ucd9244}, +	{"ucd9246", ucd9246}, +	{"ucd9248", ucd9248}, +	{} +}; +MODULE_DEVICE_TABLE(i2c, ucd9200_id); + +static int ucd9200_probe(struct i2c_client *client, +			 const struct i2c_device_id *id) +{ +	u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; +	struct pmbus_driver_info *info; +	const struct i2c_device_id *mid; +	int i, j, ret; + +	if (!i2c_check_functionality(client->adapter, +				     I2C_FUNC_SMBUS_BYTE_DATA | +				     I2C_FUNC_SMBUS_BLOCK_DATA)) +		return -ENODEV; + +	ret = i2c_smbus_read_block_data(client, UCD9200_DEVICE_ID, +					block_buffer); +	if (ret < 0) { +		dev_err(&client->dev, "Failed to read device ID\n"); +		return ret; +	} +	block_buffer[ret] = '\0'; +	dev_info(&client->dev, "Device ID %s\n", block_buffer); + +	for (mid = ucd9200_id; mid->name[0]; mid++) { +		if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) +			break; +	} +	if (!mid->name[0]) { +		dev_err(&client->dev, "Unsupported device\n"); +		return -ENODEV; +	} +	if (id->driver_data != ucd9200 && id->driver_data != mid->driver_data) +		dev_notice(&client->dev, +			   "Device mismatch: Configured %s, detected %s\n", +			   id->name, mid->name); + +	info = devm_kzalloc(&client->dev, sizeof(struct pmbus_driver_info), +			    GFP_KERNEL); +	if (!info) +		return -ENOMEM; + +	ret = i2c_smbus_read_block_data(client, UCD9200_PHASE_INFO, +					block_buffer); +	if (ret < 0) { +		dev_err(&client->dev, "Failed to read phase information\n"); +		return ret; +	} + +	/* +	 * Calculate number of configured pages (rails) from PHASE_INFO +	 * register. +	 * Rails have to be sequential, so we can abort after finding +	 * the first unconfigured rail. +	 */ +	info->pages = 0; +	for (i = 0; i < ret; i++) { +		if (!block_buffer[i]) +			break; +		info->pages++; +	} +	if (!info->pages) { +		dev_err(&client->dev, "No rails configured\n"); +		return -ENODEV; +	} +	dev_info(&client->dev, "%d rails configured\n", info->pages); + +	/* +	 * Set PHASE registers on all pages to 0xff to ensure that phase +	 * specific commands will apply to all phases of a given page (rail). +	 * This only affects the READ_IOUT and READ_TEMPERATURE2 registers. +	 * READ_IOUT will return the sum of currents of all phases of a rail, +	 * and READ_TEMPERATURE2 will return the maximum temperature detected +	 * for the the phases of the rail. +	 */ +	for (i = 0; i < info->pages; i++) { +		/* +		 * Setting PAGE & PHASE fails once in a while for no obvious +		 * reason, so we need to retry a couple of times. +		 */ +		for (j = 0; j < 3; j++) { +			ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, i); +			if (ret < 0) +				continue; +			ret = i2c_smbus_write_byte_data(client, PMBUS_PHASE, +							0xff); +			if (ret < 0) +				continue; +			break; +		} +		if (ret < 0) { +			dev_err(&client->dev, +				"Failed to initialize PHASE registers\n"); +			return ret; +		} +	} +	if (info->pages > 1) +		i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + +	info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT | +			PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | +			PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | +			PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | +			PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | +			PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + +	for (i = 1; i < info->pages; i++) +		info->func[i] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | +			PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | +			PMBUS_HAVE_POUT | +			PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP; + +	/* ucd9240 supports a single fan */ +	if (mid->driver_data == ucd9240) +		info->func[0] |= PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12; + +	return pmbus_do_probe(client, mid, info); +} + +/* This is the driver that will be inserted */ +static struct i2c_driver ucd9200_driver = { +	.driver = { +		.name = "ucd9200", +	}, +	.probe = ucd9200_probe, +	.remove = pmbus_do_remove, +	.id_table = ucd9200_id, +}; + +module_i2c_driver(ucd9200_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for TI UCD922x, UCD924x"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/pmbus/zl6100.c b/drivers/hwmon/pmbus/zl6100.c new file mode 100644 index 00000000000..81964412125 --- /dev/null +++ b/drivers/hwmon/pmbus/zl6100.c @@ -0,0 +1,419 @@ +/* + * Hardware monitoring driver for ZL6100 and compatibles + * + * Copyright (c) 2011 Ericsson AB. + * Copyright (c) 2012 Guenter Roeck + * + * 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/ktime.h> +#include <linux/delay.h> +#include "pmbus.h" + +enum chips { zl2004, zl2005, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105, +	     zl9101, zl9117 }; + +struct zl6100_data { +	int id; +	ktime_t access;		/* chip access time */ +	int delay;		/* Delay between chip accesses in uS */ +	struct pmbus_driver_info info; +}; + +#define to_zl6100_data(x)  container_of(x, struct zl6100_data, info) + +#define ZL6100_MFR_CONFIG		0xd0 +#define ZL6100_DEVICE_ID		0xe4 + +#define ZL6100_MFR_XTEMP_ENABLE		(1 << 7) + +#define MFR_VMON_OV_FAULT_LIMIT		0xf5 +#define MFR_VMON_UV_FAULT_LIMIT		0xf6 +#define MFR_READ_VMON			0xf7 + +#define VMON_UV_WARNING			(1 << 5) +#define VMON_OV_WARNING			(1 << 4) +#define VMON_UV_FAULT			(1 << 1) +#define VMON_OV_FAULT			(1 << 0) + +#define ZL6100_WAIT_TIME		1000	/* uS	*/ + +static ushort delay = ZL6100_WAIT_TIME; +module_param(delay, ushort, 0644); +MODULE_PARM_DESC(delay, "Delay between chip accesses in uS"); + +/* Convert linear sensor value to milli-units */ +static long zl6100_l2d(s16 l) +{ +	s16 exponent; +	s32 mantissa; +	long val; + +	exponent = l >> 11; +	mantissa = ((s16)((l & 0x7ff) << 5)) >> 5; + +	val = mantissa; + +	/* scale result to milli-units */ +	val = val * 1000L; + +	if (exponent >= 0) +		val <<= exponent; +	else +		val >>= -exponent; + +	return val; +} + +#define MAX_MANTISSA	(1023 * 1000) +#define MIN_MANTISSA	(511 * 1000) + +static u16 zl6100_d2l(long val) +{ +	s16 exponent = 0, mantissa; +	bool negative = false; + +	/* simple case */ +	if (val == 0) +		return 0; + +	if (val < 0) { +		negative = true; +		val = -val; +	} + +	/* Reduce large mantissa until it fits into 10 bit */ +	while (val >= MAX_MANTISSA && exponent < 15) { +		exponent++; +		val >>= 1; +	} +	/* Increase small mantissa to improve precision */ +	while (val < MIN_MANTISSA && exponent > -15) { +		exponent--; +		val <<= 1; +	} + +	/* Convert mantissa from milli-units to units */ +	mantissa = DIV_ROUND_CLOSEST(val, 1000); + +	/* Ensure that resulting number is within range */ +	if (mantissa > 0x3ff) +		mantissa = 0x3ff; + +	/* restore sign */ +	if (negative) +		mantissa = -mantissa; + +	/* Convert to 5 bit exponent, 11 bit mantissa */ +	return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); +} + +/* Some chips need a delay between accesses */ +static inline void zl6100_wait(const struct zl6100_data *data) +{ +	if (data->delay) { +		s64 delta = ktime_us_delta(ktime_get(), data->access); +		if (delta < data->delay) +			udelay(data->delay - delta); +	} +} + +static int zl6100_read_word_data(struct i2c_client *client, int page, int reg) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	struct zl6100_data *data = to_zl6100_data(info); +	int ret, vreg; + +	if (page > 0) +		return -ENXIO; + +	if (data->id == zl2005) { +		/* +		 * Limit register detection is not reliable on ZL2005. +		 * Make sure registers are not erroneously detected. +		 */ +		switch (reg) { +		case PMBUS_VOUT_OV_WARN_LIMIT: +		case PMBUS_VOUT_UV_WARN_LIMIT: +		case PMBUS_IOUT_OC_WARN_LIMIT: +			return -ENXIO; +		} +	} + +	switch (reg) { +	case PMBUS_VIRT_READ_VMON: +		vreg = MFR_READ_VMON; +		break; +	case PMBUS_VIRT_VMON_OV_WARN_LIMIT: +	case PMBUS_VIRT_VMON_OV_FAULT_LIMIT: +		vreg = MFR_VMON_OV_FAULT_LIMIT; +		break; +	case PMBUS_VIRT_VMON_UV_WARN_LIMIT: +	case PMBUS_VIRT_VMON_UV_FAULT_LIMIT: +		vreg = MFR_VMON_UV_FAULT_LIMIT; +		break; +	default: +		if (reg >= PMBUS_VIRT_BASE) +			return -ENXIO; +		vreg = reg; +		break; +	} + +	zl6100_wait(data); +	ret = pmbus_read_word_data(client, page, vreg); +	data->access = ktime_get(); +	if (ret < 0) +		return ret; + +	switch (reg) { +	case PMBUS_VIRT_VMON_OV_WARN_LIMIT: +		ret = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(ret) * 9, 10)); +		break; +	case PMBUS_VIRT_VMON_UV_WARN_LIMIT: +		ret = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(ret) * 11, 10)); +		break; +	} + +	return ret; +} + +static int zl6100_read_byte_data(struct i2c_client *client, int page, int reg) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	struct zl6100_data *data = to_zl6100_data(info); +	int ret, status; + +	if (page > 0) +		return -ENXIO; + +	zl6100_wait(data); + +	switch (reg) { +	case PMBUS_VIRT_STATUS_VMON: +		ret = pmbus_read_byte_data(client, 0, +					   PMBUS_STATUS_MFR_SPECIFIC); +		if (ret < 0) +			break; + +		status = 0; +		if (ret & VMON_UV_WARNING) +			status |= PB_VOLTAGE_UV_WARNING; +		if (ret & VMON_OV_WARNING) +			status |= PB_VOLTAGE_OV_WARNING; +		if (ret & VMON_UV_FAULT) +			status |= PB_VOLTAGE_UV_FAULT; +		if (ret & VMON_OV_FAULT) +			status |= PB_VOLTAGE_OV_FAULT; +		ret = status; +		break; +	default: +		ret = pmbus_read_byte_data(client, page, reg); +		break; +	} +	data->access = ktime_get(); + +	return ret; +} + +static int zl6100_write_word_data(struct i2c_client *client, int page, int reg, +				  u16 word) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	struct zl6100_data *data = to_zl6100_data(info); +	int ret, vreg; + +	if (page > 0) +		return -ENXIO; + +	switch (reg) { +	case PMBUS_VIRT_VMON_OV_WARN_LIMIT: +		word = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(word) * 10, 9)); +		vreg = MFR_VMON_OV_FAULT_LIMIT; +		pmbus_clear_cache(client); +		break; +	case PMBUS_VIRT_VMON_OV_FAULT_LIMIT: +		vreg = MFR_VMON_OV_FAULT_LIMIT; +		pmbus_clear_cache(client); +		break; +	case PMBUS_VIRT_VMON_UV_WARN_LIMIT: +		word = zl6100_d2l(DIV_ROUND_CLOSEST(zl6100_l2d(word) * 10, 11)); +		vreg = MFR_VMON_UV_FAULT_LIMIT; +		pmbus_clear_cache(client); +		break; +	case PMBUS_VIRT_VMON_UV_FAULT_LIMIT: +		vreg = MFR_VMON_UV_FAULT_LIMIT; +		pmbus_clear_cache(client); +		break; +	default: +		if (reg >= PMBUS_VIRT_BASE) +			return -ENXIO; +		vreg = reg; +	} + +	zl6100_wait(data); +	ret = pmbus_write_word_data(client, page, vreg, word); +	data->access = ktime_get(); + +	return ret; +} + +static int zl6100_write_byte(struct i2c_client *client, int page, u8 value) +{ +	const struct pmbus_driver_info *info = pmbus_get_driver_info(client); +	struct zl6100_data *data = to_zl6100_data(info); +	int ret; + +	if (page > 0) +		return -ENXIO; + +	zl6100_wait(data); +	ret = pmbus_write_byte(client, page, value); +	data->access = ktime_get(); + +	return ret; +} + +static const struct i2c_device_id zl6100_id[] = { +	{"bmr450", zl2005}, +	{"bmr451", zl2005}, +	{"bmr462", zl2008}, +	{"bmr463", zl2008}, +	{"bmr464", zl2008}, +	{"zl2004", zl2004}, +	{"zl2005", zl2005}, +	{"zl2006", zl2006}, +	{"zl2008", zl2008}, +	{"zl2105", zl2105}, +	{"zl2106", zl2106}, +	{"zl6100", zl6100}, +	{"zl6105", zl6105}, +	{"zl9101", zl9101}, +	{"zl9117", zl9117}, +	{ } +}; +MODULE_DEVICE_TABLE(i2c, zl6100_id); + +static int zl6100_probe(struct i2c_client *client, +			const struct i2c_device_id *id) +{ +	int ret; +	struct zl6100_data *data; +	struct pmbus_driver_info *info; +	u8 device_id[I2C_SMBUS_BLOCK_MAX + 1]; +	const struct i2c_device_id *mid; + +	if (!i2c_check_functionality(client->adapter, +				     I2C_FUNC_SMBUS_READ_WORD_DATA +				     | I2C_FUNC_SMBUS_READ_BLOCK_DATA)) +		return -ENODEV; + +	ret = i2c_smbus_read_block_data(client, ZL6100_DEVICE_ID, +					device_id); +	if (ret < 0) { +		dev_err(&client->dev, "Failed to read device ID\n"); +		return ret; +	} +	device_id[ret] = '\0'; +	dev_info(&client->dev, "Device ID %s\n", device_id); + +	mid = NULL; +	for (mid = zl6100_id; mid->name[0]; mid++) { +		if (!strncasecmp(mid->name, device_id, strlen(mid->name))) +			break; +	} +	if (!mid->name[0]) { +		dev_err(&client->dev, "Unsupported device\n"); +		return -ENODEV; +	} +	if (id->driver_data != mid->driver_data) +		dev_notice(&client->dev, +			   "Device mismatch: Configured %s, detected %s\n", +			   id->name, mid->name); + +	data = devm_kzalloc(&client->dev, sizeof(struct zl6100_data), +			    GFP_KERNEL); +	if (!data) +		return -ENOMEM; + +	data->id = mid->driver_data; + +	/* +	 * According to information from the chip vendor, all currently +	 * supported chips are known to require a wait time between I2C +	 * accesses. +	 */ +	data->delay = delay; + +	/* +	 * Since there was a direct I2C device access above, wait before +	 * accessing the chip again. +	 */ +	data->access = ktime_get(); +	zl6100_wait(data); + +	info = &data->info; + +	info->pages = 1; +	info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT +	  | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT +	  | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT +	  | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; + +	/* +	 * ZL2004, ZL9101M, and ZL9117M support monitoring an extra voltage +	 * (VMON for ZL2004, VDRV for ZL9101M and ZL9117M). Report it as vmon. +	 */ +	if (data->id == zl2004 || data->id == zl9101 || data->id == zl9117) +		info->func[0] |= PMBUS_HAVE_VMON | PMBUS_HAVE_STATUS_VMON; + +	ret = i2c_smbus_read_word_data(client, ZL6100_MFR_CONFIG); +	if (ret < 0) +		return ret; + +	if (ret & ZL6100_MFR_XTEMP_ENABLE) +		info->func[0] |= PMBUS_HAVE_TEMP2; + +	data->access = ktime_get(); +	zl6100_wait(data); + +	info->read_word_data = zl6100_read_word_data; +	info->read_byte_data = zl6100_read_byte_data; +	info->write_word_data = zl6100_write_word_data; +	info->write_byte = zl6100_write_byte; + +	return pmbus_do_probe(client, mid, info); +} + +static struct i2c_driver zl6100_driver = { +	.driver = { +		   .name = "zl6100", +		   }, +	.probe = zl6100_probe, +	.remove = pmbus_do_remove, +	.id_table = zl6100_id, +}; + +module_i2c_driver(zl6100_driver); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for ZL6100 and compatibles"); +MODULE_LICENSE("GPL");  | 
