| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Hardware monitoring driver for ZL6100 and compatibles | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (c) 2011 Ericsson AB. | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  |  * Copyright (c) 2012 Guenter Roeck | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  |  * | 
					
						
							|  |  |  |  * 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"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-28 13:18:47 -08:00
										 |  |  | enum chips { zl2004, zl2005, zl2006, zl2008, zl2105, zl2106, zl6100, zl6105, | 
					
						
							|  |  |  | 	     zl9101, zl9117 }; | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | struct zl6100_data { | 
					
						
							|  |  |  | 	int id; | 
					
						
							|  |  |  | 	ktime_t access;		/* chip access time */ | 
					
						
							| 
									
										
										
										
											2012-03-07 03:58:55 -08:00
										 |  |  | 	int delay;		/* Delay between chip accesses in uS */ | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 	struct pmbus_driver_info info; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define to_zl6100_data(x)  container_of(x, struct zl6100_data, info)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-10-04 17:26:04 -07:00
										 |  |  | #define ZL6100_MFR_CONFIG		0xd0
 | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | #define ZL6100_DEVICE_ID		0xe4
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-10-04 17:26:04 -07:00
										 |  |  | #define ZL6100_MFR_XTEMP_ENABLE		(1 << 7)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | #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)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | #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"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | /* 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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | /* Some chips need a delay between accesses */ | 
					
						
							|  |  |  | static inline void zl6100_wait(const struct zl6100_data *data) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2012-03-07 03:58:55 -08:00
										 |  |  | 	if (data->delay) { | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 		s64 delta = ktime_us_delta(ktime_get(), data->access); | 
					
						
							| 
									
										
										
										
											2012-03-07 03:58:55 -08:00
										 |  |  | 		if (delta < data->delay) | 
					
						
							|  |  |  | 			udelay(data->delay - delta); | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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); | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | 	int ret, vreg; | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | 	if (page > 0) | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 		return -ENXIO; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-10-01 16:50:36 -07:00
										 |  |  | 	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; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | 	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; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 	zl6100_wait(data); | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | 	ret = pmbus_read_word_data(client, page, vreg); | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 	data->access = ktime_get(); | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | 	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; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	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); | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | 	int ret, status; | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if (page > 0) | 
					
						
							|  |  |  | 		return -ENXIO; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	zl6100_wait(data); | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	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; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 	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); | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | 	int ret, vreg; | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | 	if (page > 0) | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 		return -ENXIO; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | 	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; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 	zl6100_wait(data); | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | 	ret = pmbus_write_word_data(client, page, vreg, word); | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 	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[] = { | 
					
						
							| 
									
										
										
										
											2011-10-01 17:35:44 -07:00
										 |  |  | 	{"bmr450", zl2005}, | 
					
						
							|  |  |  | 	{"bmr451", zl2005}, | 
					
						
							|  |  |  | 	{"bmr462", zl2008}, | 
					
						
							|  |  |  | 	{"bmr463", zl2008}, | 
					
						
							|  |  |  | 	{"bmr464", zl2008}, | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 	{"zl2004", zl2004}, | 
					
						
							| 
									
										
										
										
											2011-10-01 16:50:36 -07:00
										 |  |  | 	{"zl2005", zl2005}, | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 	{"zl2006", zl2006}, | 
					
						
							|  |  |  | 	{"zl2008", zl2008}, | 
					
						
							|  |  |  | 	{"zl2105", zl2105}, | 
					
						
							|  |  |  | 	{"zl2106", zl2106}, | 
					
						
							|  |  |  | 	{"zl6100", zl6100}, | 
					
						
							|  |  |  | 	{"zl6105", zl6105}, | 
					
						
							| 
									
										
										
										
											2012-02-28 13:18:47 -08:00
										 |  |  | 	{"zl9101", zl9101}, | 
					
						
							|  |  |  | 	{"zl9117", zl9117}, | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 	{ } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 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, | 
					
						
							| 
									
										
										
										
											2011-10-04 17:26:04 -07:00
										 |  |  | 				     I2C_FUNC_SMBUS_READ_WORD_DATA | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 				     | 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); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-22 08:56:43 -08:00
										 |  |  | 	data = devm_kzalloc(&client->dev, sizeof(struct zl6100_data), | 
					
						
							|  |  |  | 			    GFP_KERNEL); | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 	if (!data) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	data->id = mid->driver_data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							| 
									
										
										
										
											2012-03-13 09:05:14 -07:00
										 |  |  | 	 * According to information from the chip vendor, all currently | 
					
						
							|  |  |  | 	 * supported chips are known to require a wait time between I2C | 
					
						
							|  |  |  | 	 * accesses. | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2012-03-07 03:58:55 -08:00
										 |  |  | 	data->delay = delay; | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * 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 | 
					
						
							| 
									
										
										
										
											2011-10-04 17:26:04 -07:00
										 |  |  | 	  | PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-03-07 03:54:50 -08:00
										 |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * 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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-10-04 17:26:04 -07:00
										 |  |  | 	ret = i2c_smbus_read_word_data(client, ZL6100_MFR_CONFIG); | 
					
						
							|  |  |  | 	if (ret < 0) | 
					
						
							| 
									
										
										
										
											2012-02-22 08:56:43 -08:00
										 |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-10-04 17:26:04 -07:00
										 |  |  | 	if (ret & ZL6100_MFR_XTEMP_ENABLE) | 
					
						
							|  |  |  | 		info->func[0] |= PMBUS_HAVE_TEMP2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	data->access = ktime_get(); | 
					
						
							|  |  |  | 	zl6100_wait(data); | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-22 08:56:43 -08:00
										 |  |  | 	return pmbus_do_probe(client, mid, info); | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct i2c_driver zl6100_driver = { | 
					
						
							|  |  |  | 	.driver = { | 
					
						
							|  |  |  | 		   .name = "zl6100", | 
					
						
							|  |  |  | 		   }, | 
					
						
							|  |  |  | 	.probe = zl6100_probe, | 
					
						
							| 
									
										
										
										
											2012-02-22 08:56:44 -08:00
										 |  |  | 	.remove = pmbus_do_remove, | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 	.id_table = zl6100_id, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-01-20 15:38:18 +08:00
										 |  |  | module_i2c_driver(zl6100_driver); | 
					
						
							| 
									
										
										
										
											2011-07-29 22:21:53 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | MODULE_AUTHOR("Guenter Roeck"); | 
					
						
							|  |  |  | MODULE_DESCRIPTION("PMBus driver for ZL6100 and compatibles"); | 
					
						
							|  |  |  | MODULE_LICENSE("GPL"); |