diff options
Diffstat (limited to 'drivers/misc/cros_ec_i2c.c')
| -rw-r--r-- | drivers/misc/cros_ec_i2c.c | 199 | 
1 files changed, 199 insertions, 0 deletions
| diff --git a/drivers/misc/cros_ec_i2c.c b/drivers/misc/cros_ec_i2c.c new file mode 100644 index 000000000..b0060ac19 --- /dev/null +++ b/drivers/misc/cros_ec_i2c.c @@ -0,0 +1,199 @@ +/* + * Chromium OS cros_ec driver - I2C interface + * + * Copyright (c) 2012 The Chromium OS Authors. + * See file CREDITS for list of people who contributed to this + * project. + * + * 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +/* + * The Matrix Keyboard Protocol driver handles talking to the keyboard + * controller chip. Mostly this is for keyboard functions, but some other + * things have slipped in, so we provide generic services to talk to the + * KBC. + */ + +#include <common.h> +#include <i2c.h> +#include <cros_ec.h> + +#ifdef DEBUG_TRACE +#define debug_trace(fmt, b...)	debug(fmt, #b) +#else +#define debug_trace(fmt, b...) +#endif + +int cros_ec_i2c_command(struct cros_ec_dev *dev, uint8_t cmd, int cmd_version, +		     const uint8_t *dout, int dout_len, +		     uint8_t **dinp, int din_len) +{ +	int old_bus = 0; +	/* version8, cmd8, arglen8, out8[dout_len], csum8 */ +	int out_bytes = dout_len + 4; +	/* response8, arglen8, in8[din_len], checksum8 */ +	int in_bytes = din_len + 3; +	uint8_t *ptr; +	/* Receive input data, so that args will be dword aligned */ +	uint8_t *in_ptr; +	int ret; + +	old_bus = i2c_get_bus_num(); + +	/* +	 * Sanity-check I/O sizes given transaction overhead in internal +	 * buffers. +	 */ +	if (out_bytes > sizeof(dev->dout)) { +		debug("%s: Cannot send %d bytes\n", __func__, dout_len); +		return -1; +	} +	if (in_bytes > sizeof(dev->din)) { +		debug("%s: Cannot receive %d bytes\n", __func__, din_len); +		return -1; +	} +	assert(dout_len >= 0); +	assert(dinp); + +	/* +	 * Copy command and data into output buffer so we can do a single I2C +	 * burst transaction. +	 */ +	ptr = dev->dout; + +	/* +	 * in_ptr starts of pointing to a dword-aligned input data buffer. +	 * We decrement it back by the number of header bytes we expect to +	 * receive, so that the first parameter of the resulting input data +	 * will be dword aligned. +	 */ +	in_ptr = dev->din + sizeof(int64_t); +	if (!dev->cmd_version_is_supported) { +		/* Send an old-style command */ +		*ptr++ = cmd; +		out_bytes = dout_len + 1; +		in_bytes = din_len + 2; +		in_ptr--;	/* Expect just a status byte */ +	} else { +		*ptr++ = EC_CMD_VERSION0 + cmd_version; +		*ptr++ = cmd; +		*ptr++ = dout_len; +		in_ptr -= 2;	/* Expect status, length bytes */ +	} +	memcpy(ptr, dout, dout_len); +	ptr += dout_len; + +	if (dev->cmd_version_is_supported) +		*ptr++ = (uint8_t) +			 cros_ec_calc_checksum(dev->dout, dout_len + 3); + +	/* Set to the proper i2c bus */ +	if (i2c_set_bus_num(dev->bus_num)) { +		debug("%s: Cannot change to I2C bus %d\n", __func__, +			dev->bus_num); +		return -1; +	} + +	/* Send output data */ +	cros_ec_dump_data("out", -1, dev->dout, out_bytes); +	ret = i2c_write(dev->addr, 0, 0, dev->dout, out_bytes); +	if (ret) { +		debug("%s: Cannot complete I2C write to 0x%x\n", +			__func__, dev->addr); +		ret = -1; +	} + +	if (!ret) { +		ret = i2c_read(dev->addr, 0, 0, in_ptr, in_bytes); +		if (ret) { +			debug("%s: Cannot complete I2C read from 0x%x\n", +				__func__, dev->addr); +			ret = -1; +		} +	} + +	/* Return to original bus number */ +	i2c_set_bus_num(old_bus); +	if (ret) +		return ret; + +	if (*in_ptr != EC_RES_SUCCESS) { +		debug("%s: Received bad result code %d\n", __func__, *in_ptr); +		return -(int)*in_ptr; +	} + +	if (dev->cmd_version_is_supported) { +		int len, csum; + +		len = in_ptr[1]; +		if (len + 3 > sizeof(dev->din)) { +			debug("%s: Received length %#02x too large\n", +			      __func__, len); +			return -1; +		} +		csum = cros_ec_calc_checksum(in_ptr, 2 + len); +		if (csum != in_ptr[2 + len]) { +			debug("%s: Invalid checksum rx %#02x, calced %#02x\n", +			      __func__, in_ptr[2 + din_len], csum); +			return -1; +		} +		din_len = min(din_len, len); +		cros_ec_dump_data("in", -1, in_ptr, din_len + 3); +	} else { +		cros_ec_dump_data("in (old)", -1, in_ptr, in_bytes); +	} + +	/* Return pointer to dword-aligned input data, if any */ +	*dinp = dev->din + sizeof(int64_t); + +	return din_len; +} + +int cros_ec_i2c_decode_fdt(struct cros_ec_dev *dev, const void *blob) +{ +	/* Decode interface-specific FDT params */ +	dev->max_frequency = fdtdec_get_int(blob, dev->node, +					    "i2c-max-frequency", 100000); +	dev->bus_num = i2c_get_bus_num_fdt(dev->parent_node); +	if (dev->bus_num == -1) { +		debug("%s: Failed to read bus number\n", __func__); +		return -1; +	} +	dev->addr = fdtdec_get_int(blob, dev->node, "reg", -1); +	if (dev->addr == -1) { +		debug("%s: Failed to read device address\n", __func__); +		return -1; +	} + +	return 0; +} + +/** + * Initialize I2C protocol. + * + * @param dev		CROS_EC device + * @param blob		Device tree blob + * @return 0 if ok, -1 on error + */ +int cros_ec_i2c_init(struct cros_ec_dev *dev, const void *blob) +{ +	i2c_init(dev->max_frequency, dev->addr); + +	dev->cmd_version_is_supported = 0; + +	return 0; +} |