| /* | 
 |  * Hardware monitoring driver for LTC3815 | 
 |  * | 
 |  * Copyright (c) 2015 Linear Technology | 
 |  * Copyright (c) 2015 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/err.h> | 
 | #include <linux/i2c.h> | 
 | #include <linux/init.h> | 
 | #include <linux/jiffies.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/module.h> | 
 | #include "pmbus.h" | 
 |  | 
 | #define LTC3815_MFR_IOUT_PEAK	0xd7 | 
 | #define LTC3815_MFR_VOUT_PEAK	0xdd | 
 | #define LTC3815_MFR_VIN_PEAK	0xde | 
 | #define LTC3815_MFR_TEMP_PEAK	0xdf | 
 | #define LTC3815_MFR_IIN_PEAK	0xe1 | 
 | #define LTC3815_MFR_SPECIAL_ID	0xe7 | 
 |  | 
 | #define LTC3815_ID		0x8000 | 
 | #define LTC3815_ID_MASK		0xff00 | 
 |  | 
 | static int ltc3815_read_byte_data(struct i2c_client *client, int page, int reg) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	switch (reg) { | 
 | 	case PMBUS_VOUT_MODE: | 
 | 		/* | 
 | 		 * The chip returns 0x3e, suggesting VID mode with manufacturer | 
 | 		 * specific VID codes. Since the output voltage is reported | 
 | 		 * with a LSB of 0.5mV, override and report direct mode with | 
 | 		 * appropriate coefficients. | 
 | 		 */ | 
 | 		ret = 0x40; | 
 | 		break; | 
 | 	default: | 
 | 		ret = -ENODATA; | 
 | 		break; | 
 | 	} | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int ltc3815_write_byte(struct i2c_client *client, int page, u8 reg) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	switch (reg) { | 
 | 	case PMBUS_CLEAR_FAULTS: | 
 | 		/* | 
 | 		 * LTC3815 does not support the CLEAR_FAULTS command. | 
 | 		 * Emulate it by clearing the status register. | 
 | 		 */ | 
 | 		ret = pmbus_read_word_data(client, 0, PMBUS_STATUS_WORD); | 
 | 		if (ret > 0) { | 
 | 			pmbus_write_word_data(client, 0, PMBUS_STATUS_WORD, | 
 | 					      ret); | 
 | 			ret = 0; | 
 | 		} | 
 | 		break; | 
 | 	default: | 
 | 		ret = -ENODATA; | 
 | 		break; | 
 | 	} | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int ltc3815_read_word_data(struct i2c_client *client, int page, int reg) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	switch (reg) { | 
 | 	case PMBUS_VIRT_READ_VIN_MAX: | 
 | 		ret = pmbus_read_word_data(client, page, LTC3815_MFR_VIN_PEAK); | 
 | 		break; | 
 | 	case PMBUS_VIRT_READ_VOUT_MAX: | 
 | 		ret = pmbus_read_word_data(client, page, LTC3815_MFR_VOUT_PEAK); | 
 | 		break; | 
 | 	case PMBUS_VIRT_READ_TEMP_MAX: | 
 | 		ret = pmbus_read_word_data(client, page, LTC3815_MFR_TEMP_PEAK); | 
 | 		break; | 
 | 	case PMBUS_VIRT_READ_IOUT_MAX: | 
 | 		ret = pmbus_read_word_data(client, page, LTC3815_MFR_IOUT_PEAK); | 
 | 		break; | 
 | 	case PMBUS_VIRT_READ_IIN_MAX: | 
 | 		ret = pmbus_read_word_data(client, page, LTC3815_MFR_IIN_PEAK); | 
 | 		break; | 
 | 	case PMBUS_VIRT_RESET_VOUT_HISTORY: | 
 | 	case PMBUS_VIRT_RESET_VIN_HISTORY: | 
 | 	case PMBUS_VIRT_RESET_TEMP_HISTORY: | 
 | 	case PMBUS_VIRT_RESET_IOUT_HISTORY: | 
 | 	case PMBUS_VIRT_RESET_IIN_HISTORY: | 
 | 		ret = 0; | 
 | 		break; | 
 | 	default: | 
 | 		ret = -ENODATA; | 
 | 		break; | 
 | 	} | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int ltc3815_write_word_data(struct i2c_client *client, int page, | 
 | 				   int reg, u16 word) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	switch (reg) { | 
 | 	case PMBUS_VIRT_RESET_IIN_HISTORY: | 
 | 		ret = pmbus_write_word_data(client, page, | 
 | 					    LTC3815_MFR_IIN_PEAK, 0); | 
 | 		break; | 
 | 	case PMBUS_VIRT_RESET_IOUT_HISTORY: | 
 | 		ret = pmbus_write_word_data(client, page, | 
 | 					    LTC3815_MFR_IOUT_PEAK, 0); | 
 | 		break; | 
 | 	case PMBUS_VIRT_RESET_VOUT_HISTORY: | 
 | 		ret = pmbus_write_word_data(client, page, | 
 | 					    LTC3815_MFR_VOUT_PEAK, 0); | 
 | 		break; | 
 | 	case PMBUS_VIRT_RESET_VIN_HISTORY: | 
 | 		ret = pmbus_write_word_data(client, page, | 
 | 					    LTC3815_MFR_VIN_PEAK, 0); | 
 | 		break; | 
 | 	case PMBUS_VIRT_RESET_TEMP_HISTORY: | 
 | 		ret = pmbus_write_word_data(client, page, | 
 | 					    LTC3815_MFR_TEMP_PEAK, 0); | 
 | 		break; | 
 | 	default: | 
 | 		ret = -ENODATA; | 
 | 		break; | 
 | 	} | 
 | 	return ret; | 
 | } | 
 |  | 
 | static const struct i2c_device_id ltc3815_id[] = { | 
 | 	{"ltc3815", 0}, | 
 | 	{ } | 
 | }; | 
 | MODULE_DEVICE_TABLE(i2c, ltc3815_id); | 
 |  | 
 | static struct pmbus_driver_info ltc3815_info = { | 
 | 	.pages = 1, | 
 | 	.format[PSC_VOLTAGE_IN] = direct, | 
 | 	.format[PSC_VOLTAGE_OUT] = direct, | 
 | 	.format[PSC_CURRENT_IN] = direct, | 
 | 	.format[PSC_CURRENT_OUT] = direct, | 
 | 	.format[PSC_TEMPERATURE] = direct, | 
 | 	.m[PSC_VOLTAGE_IN] = 250, | 
 | 	.b[PSC_VOLTAGE_IN] = 0, | 
 | 	.R[PSC_VOLTAGE_IN] = 0, | 
 | 	.m[PSC_VOLTAGE_OUT] = 2, | 
 | 	.b[PSC_VOLTAGE_OUT] = 0, | 
 | 	.R[PSC_VOLTAGE_OUT] = 3, | 
 | 	.m[PSC_CURRENT_IN] = 1, | 
 | 	.b[PSC_CURRENT_IN] = 0, | 
 | 	.R[PSC_CURRENT_IN] = 2, | 
 | 	.m[PSC_CURRENT_OUT] = 1, | 
 | 	.b[PSC_CURRENT_OUT] = 0, | 
 | 	.R[PSC_CURRENT_OUT] = 2, | 
 | 	.m[PSC_TEMPERATURE] = 1, | 
 | 	.b[PSC_TEMPERATURE] = 0, | 
 | 	.R[PSC_TEMPERATURE] = 0, | 
 | 	.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN | PMBUS_HAVE_VOUT | | 
 | 		PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP, | 
 | 	.read_byte_data = ltc3815_read_byte_data, | 
 | 	.read_word_data = ltc3815_read_word_data, | 
 | 	.write_byte = ltc3815_write_byte, | 
 | 	.write_word_data = ltc3815_write_word_data, | 
 | }; | 
 |  | 
 | static int ltc3815_probe(struct i2c_client *client, | 
 | 			 const struct i2c_device_id *id) | 
 | { | 
 | 	int chip_id; | 
 |  | 
 | 	if (!i2c_check_functionality(client->adapter, | 
 | 				     I2C_FUNC_SMBUS_READ_WORD_DATA)) | 
 | 		return -ENODEV; | 
 |  | 
 | 	chip_id = i2c_smbus_read_word_data(client, LTC3815_MFR_SPECIAL_ID); | 
 | 	if (chip_id < 0) | 
 | 		return chip_id; | 
 | 	if ((chip_id & LTC3815_ID_MASK) != LTC3815_ID) | 
 | 		return -ENODEV; | 
 |  | 
 | 	return pmbus_do_probe(client, id, <c3815_info); | 
 | } | 
 |  | 
 | static struct i2c_driver ltc3815_driver = { | 
 | 	.driver = { | 
 | 		   .name = "ltc3815", | 
 | 		   }, | 
 | 	.probe = ltc3815_probe, | 
 | 	.remove = pmbus_do_remove, | 
 | 	.id_table = ltc3815_id, | 
 | }; | 
 |  | 
 | module_i2c_driver(ltc3815_driver); | 
 |  | 
 | MODULE_AUTHOR("Guenter Roeck"); | 
 | MODULE_DESCRIPTION("PMBus driver for LTC3815"); | 
 | MODULE_LICENSE("GPL"); |