text: /* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #define _GNU_SOURCE /* for asprintf */
  
  #include <errno.h>
  #include <fcntl.h>
  #include <linux/i2c.h>
  #include <linux/i2c-dev.h>
  #include <stdint.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/ioctl.h>
  #include <sys/stat.h>
  #include <sys/types.h>
  
  #include "comm-host.h"
  
  #define EC_I2C_ADDR 0x1e
  
  #define I2C_ADAPTER_NODE "/sys/class/i2c-adapter/i2c-%d/%d-%04x/name"
  #define I2C_ADAPTER_NAME "cros-ec-i2c"
  #define I2C_MAX_ADAPTER  32
  #define I2C_NODE "/dev/i2c-%d"
  
  #ifdef DEBUG
  #define debug(format, arg...) printf(format, ##arg)
  #else
  #define debug(...)
  #endif
  
  static int i2c_fd = -1;
  
  /*
   * Sends a command to the EC (protocol v2).  Returns the command status code, or
   * -1 if other error.
   *
   * Returns >= 0 for success, or negative if error.
   *
   */
  static int ec_command_i2c(int command, int version,
  			  const void *outdata, int outsize,
  			  void *indata, int insize)
  {
  	struct i2c_rdwr_ioctl_data data;
  	int ret = -1;
  	int i;
  	int req_len;
  	uint8_t *req_buf = NULL;
  	int resp_len;
  	uint8_t *resp_buf = NULL;
  	const uint8_t *c;
  	uint8_t *d;
  	uint8_t sum;
  	struct i2c_msg i2c_msg[2];
  
  	if (version > 1) {
  		fprintf(stderr, "Command versions >1 unsupported.\n");
  		return -EC_RES_ERROR;
  	}
  
  	if (i2c_fd < 0) {
  		fprintf(stderr, "i2c_fd is negative: %d\n", i2c_fd);
  		return -EC_RES_ERROR;
  	}
  
  	if (ioctl(i2c_fd, I2C_SLAVE, EC_I2C_ADDR) < 0) {
  		fprintf(stderr, "Cannot set I2C slave address\n");
  		return -EC_RES_ERROR;
  	}
  
  	i2c_msg[0].addr = EC_I2C_ADDR;
  	i2c_msg[0].flags = 0;
  	i2c_msg[1].addr = EC_I2C_ADDR;
  	i2c_msg[1].flags = I2C_M_RD;
  	data.msgs = i2c_msg;
  	data.nmsgs = 2;
  
  	/*
  	 * allocate larger packet
  	 * (version, command, size, ..., checksum)
  	 */
  	req_len = outsize + EC_PROTO2_REQUEST_OVERHEAD;
  	req_buf = calloc(1, req_len);
  	if (!req_buf)
  		goto done;
  	i2c_msg[0].len = req_len;
  	i2c_msg[0].buf = (char *)req_buf;
  	req_buf[0] = version + EC_CMD_VERSION0;
  	req_buf[1] = command;
  	req_buf[2] = outsize;
  
  	debug("i2c req %02x:", command);
  	sum = req_buf[0] + req_buf[1] + req_buf[2];
  	/* copy message payload and compute checksum */
  	for (i = 0, c = outdata; i < outsize; i++, c++) {
  		req_buf[i + 3] = *c;
  		sum += *c;
  		debug(" %02x", *c);
  	}
  	debug(", sum=%02x\n", sum);
  	req_buf[req_len - 1] = sum;
  
  	/*
  	 * allocate larger packet
  	 * (result, size, ..., checksum)
  	 */
  	resp_len = insize + EC_PROTO2_RESPONSE_OVERHEAD;
  	resp_buf = calloc(1, resp_len);
  	if (!resp_buf)
  		goto done;
  	i2c_msg[1].len = resp_len;
  	i2c_msg[1].buf = (char *)resp_buf;
  
  	/* send command to EC and read answer */
  	ret = ioctl(i2c_fd, I2C_RDWR, &data);
  	if (ret < 0) {
  		fprintf(stderr, "i2c transfer failed: %d (err: %d)\n",
  			ret, errno);
  		ret = -EC_RES_ERROR;
  		goto done;
  	}
  
  	/* check response error code */
  	ret = resp_buf[0];
  
  	/* TODO(crosbug.com/p/23824): handle EC_RES_IN_PROGRESS case. */
  
  	resp_len = resp_buf[1];
  	if (resp_len > insize) {
  		fprintf(stderr, "response size is too large %d > %d\n",
  				resp_len, insize);
  		ret = -EC_RES_ERROR;
  		goto done;
  	}
  
  	if (ret) {
  		debug("command 0x%02x returned an error %d\n",
  			 command, i2c_msg[1].buf[0]);
  		/* Translate ERROR to -ERROR and offset */
  		ret = -EECRESULT - ret;
  	} else if (insize) {
  		debug("i2c resp  :");
  		/* copy response packet payload and compute checksum */
  		sum = resp_buf[0] + resp_buf[1];
  		for (i = 0, d = indata; i < resp_len; i++, d++) {
  			*d = resp_buf[i + 2];
  			sum += *d;
  			debug(" %02x", *d);
  		}
  		debug(", sum=%02x\n", sum);
  
  		if (sum != resp_buf[resp_len + 2]) {
  			fprintf(stderr, "bad packet checksum\n");
  			ret = -EC_RES_ERROR;
  			goto done;
  		}
  
  		/* Return output buffer size */
  		ret = resp_len;
  	}
  done:
  	if (resp_buf)
  		free(resp_buf);
  	if (req_buf)
  		free(req_buf);
  	return ret;
  }
  
  int comm_init_i2c(void)
  {
  	char *file_path;
  	char buffer[64];
  	FILE *f;
  	int i;
  
  	/* find the device number based on the adapter name */
  	for (i = 0; i < I2C_MAX_ADAPTER; i++) {
  		if (asprintf(&file_path, I2C_ADAPTER_NODE,
  			     i, i, EC_I2C_ADDR) < 0)
  			return -1;
  		f = fopen(file_path, "r");
  		if (f) {
  			if (fgets(buffer, sizeof(buffer), f) &&
  			    !strncmp(buffer, I2C_ADAPTER_NAME, 6)) {
  				free(file_path);
  				break;
  			}
  			fclose(f);
  		}
  		free(file_path);
  	}
  	if (i == I2C_MAX_ADAPTER) {
  		fprintf(stderr, "Cannot find I2C adapter\n");
  		return -1;
  	}
  
  	if (asprintf(&file_path, I2C_NODE, i) < 0)
  		return -1;
  	debug("using I2C adapter %s\n", file_path);
  	i2c_fd = open(file_path, O_RDWR);
  	if (i2c_fd < 0)
  		fprintf(stderr, "Cannot open %s : %d\n", file_path, errno);
  
  	free(file_path);
  
  	ec_command_proto = ec_command_i2c;
  	ec_max_outsize = ec_max_insize = EC_PROTO2_MAX_PARAM_SIZE;
  
  	return 0;
  }
  