Add C shared library code to make I2C calls from user space

See README for detailed notes.

BUG=chrome-os-partner:5172
TEST=manual (tested functionality of this API from Python test code)

Change-Id: I7dbc8356de744f4cf72160299affeafae193c395
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..69b4855
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8d57e72
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,32 @@
+# Copyright (c) 2011 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.
+
+CC?=cc
+CFLAGS+=-g -Iinclude/ -Wimplicit -fPIC -Wall
+LDFLAGS+=-luuid
+EXEC_NAME=smogcheck
+SONAME=libsmogcheck.so
+
+all: binary
+
+libs:
+	$(CC) $(CFLAGS) -o lib/lib_smogcheck.o -c lib/lib_smogcheck.c
+
+shared_libs: libs
+	$(CC) -shared -fPIC -o ${SONAME} lib/lib_smogcheck.o -W1,-h${SONAME} \
+	 -lpthread -lrt -lbase
+
+binary: shared_libs
+	$(CC) $(CFLAGS) -o smogcheck.o -c smogcheck.c
+	$(CC) $(CFLAGS) -o smogcheck smogcheck.o lib/lib_smogcheck.o -luuid
+
+install:
+	install -D ${EXEC_NAME} $(DESTDIR)/usr/local/sbin/${EXEC_NAME}
+	install -D -m 0644 $(SONAME) $(DESTDIR)/usr/lib/$(SONAME)
+	ln -s $(SONAME) $(DESTDIR)/usr/lib/$(SONAME).0
+
+.PHONY: clean cov all
+
+clean:
+	rm -f *.o lib/*.o ${EXEC_NAME} ${SONAME}
diff --git a/README b/README
index 3395a42..e12d7b6 100644
--- a/README
+++ b/README
@@ -1,3 +1,18 @@
-Experiment C code to communicate with I2C bus directly.
+C library to communicate with I2C bus directly.
 
-TODO(tgao): fill in details.
+Notes:
+[1] The goal of this module is to enable direct I/O operations on I2C bus
+    with minimal overhead. Although i2c-tools has been made available in
+    Chromium OS, it still carries much overhead (as revealed by strace).
+
+[2] The original source code was posted to Linux Kernel Documentation
+    (http://www.mjmwired.net/kernel/Documentation/i2c/dev-interface) and
+    has been modified and adopted here. According to this source, there are
+    two versions of i2c-dev.h, one distributed with the Linux kernel and the
+    other with i2c-tools. This module depends on the kernel version.
+
+[3] The included Makefile creates a C shared library object (.so file), which
+    can then be imported into Python via ctypes for developing new Autotest
+    test cases for I2C devices.
+
+Source for i2c-tools: http://www.lm-sensors.org/wiki/I2CTools
diff --git a/include/lib/lib_smogcheck.h b/include/lib/lib_smogcheck.h
new file mode 100644
index 0000000..6020591
--- /dev/null
+++ b/include/lib/lib_smogcheck.h
@@ -0,0 +1,17 @@
+// Copyright (c) 2011 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.
+
+#ifndef __LIB_SMOGCHECK_H__
+#define __LIB_SMOGCHECK_H__
+
+#include <linux/types.h>
+
+int GetDeviceFile(int adapter_nr);
+int SetSlaveAddress(int fd, int addr);
+int WriteByte(int fd, __u8 reg, __u8 byte_val);
+int ReadByte(int fd, __u8 reg);
+int WriteWord(int fd, __u8 reg, __u16 word_val);
+int ReadWord(int fd, __u8 reg);
+
+#endif  // __LIB_SMOGCHECK_H__
diff --git a/lib/lib_smogcheck.c b/lib/lib_smogcheck.c
new file mode 100644
index 0000000..9928ee9
--- /dev/null
+++ b/lib/lib_smogcheck.c
@@ -0,0 +1,206 @@
+// Copyright (c) 2011 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.
+//
+// lib_smogcheck.c: C code to make direct I2C calls using ioctl().
+//
+// Adapted and modified from:
+//   http://www.mjmwired.net/kernel/Documentation/i2c/dev-interface
+
+#include "lib/lib_smogcheck.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#define arraysize(x) (sizeof(x)/sizeof(x[0]))
+
+// Opens a device file for I/O operations.
+//
+// Args:
+//   adapter_nr: an integer, adapater's number address.
+//
+// Returns:
+//   a file descriptor (non-negative integer), ready to use. Or -1 if error.
+int GetDeviceFile(int adapter_nr) {
+  int file;
+  char filename[20] = {0};
+
+  snprintf(filename, arraysize(filename) - 1, "/dev/i2c-%d", adapter_nr);
+  printf("Attempt to open device %s\n", filename);
+  file = open(filename, O_RDWR);
+  if (file < 0) {
+    printf("Error opening file %d: %s\n", errno, strerror(errno));
+    return -1;
+  }
+  printf("Successfully opened device %s\n", filename);
+  return file;
+}
+
+// Sets I2C device address to communicate with.
+//
+// Args:
+//   fd: an integer, open device file descriptor.
+//   addr: an integer, I2C slave address to set.
+//
+// Returns:
+//   0 if success, -1 if error.
+int SetSlaveAddress(int fd, int addr) {
+  if (fd < 0) {
+    printf("Error: invalid file descriptor %d. Expect integer >= 0\n", fd);
+    return -1;
+  } else if (addr < 0x08 || addr > 0x77) {
+    printf("Error: invalid 7-bit I2C slave address %d. Expect range is " \
+           "an integer in [8, 112]\n", addr);
+    return -1;
+  }
+
+  if (ioctl(fd, I2C_SLAVE, addr) < 0) {
+    printf("Error communicating to slave address: %s\n", strerror(errno));
+    return -1;
+  }
+  return 0;
+}
+
+// Precondition checks.
+//
+// Args:
+//   fd: a non-negative integer, open device file descriptor.
+//   reg: an 8-bit unsigned integer, non-negative register number.
+//
+// Returns:
+//   ret: a boolean, true if preconditions are met and false if error.
+bool PreconditionCheck(int fd, __u8 reg) {
+  bool ret = false;
+  if (fd < 0) {
+    printf("Error: invalid file descriptor %d. Expect integer >= 0\n", fd);
+  } else if (reg < 0 || reg > 255) {
+    printf("Error: invalid register number %d. Expect integer in 0~255\n", reg);
+  } else {
+    ret = true;
+  }
+  return ret;
+}
+
+// Writes a byte to specified register address.
+//
+// Args:
+//   fd: an integer, open device file descriptor.
+//   reg: an 8-bit unsigned integer, non-negative register number.
+//   byte_val: an 8-bit unsigned integer, value to write.
+//
+// Returns:
+//   0 if success, -1 if error.
+int WriteByte(int fd, __u8 reg, __u8 byte_val) {
+  union i2c_smbus_data data;
+  data.byte = byte_val;
+  struct i2c_smbus_ioctl_data args;
+  args.read_write = I2C_SMBUS_WRITE;
+  args.command = reg;
+  args.size = I2C_SMBUS_BYTE_DATA;
+  args.data = &data;
+
+  if (PreconditionCheck(fd, reg) != true) {
+    return -1;
+  }
+
+  if (ioctl(fd, I2C_SMBUS, &args)) {
+    printf("Error writing to register: %s\n", strerror(errno));
+    return -1;
+  }
+  printf("Wrote to register %d: 0x%x\n", reg, byte_val);
+  return 0;
+}
+
+// Reads a byte value from a specified register address.
+//
+// Args:
+//   fd: an integer, open device file descriptor.
+//   reg: an 8-bit unsigned integer, non-negative register number.
+//
+// Returns:
+//   byte value read if success, -1 if error.
+int ReadByte(int fd, __u8 reg) {
+  union i2c_smbus_data data;
+  struct i2c_smbus_ioctl_data args;
+  args.read_write = I2C_SMBUS_READ;
+  args.command = reg;
+  args.size = I2C_SMBUS_BYTE_DATA;
+  args.data = &data;
+
+  if (PreconditionCheck(fd, reg) != true) {
+    return -1;
+  }
+
+  if (ioctl(fd, I2C_SMBUS, &args)) {
+    printf("Error reading from bus: %s\n", strerror(errno));
+    return -1;
+  }
+  printf("Read back from bus: 0x%x\n", data.byte);
+  return data.byte;
+}
+
+// Writes a word to specified register address.
+//
+// Args:
+//   fd: an integer, open device file descriptor.
+//   reg: an 8-bit unsigned integer, register number.
+//   byte_val: a 16-bit unsigned integer, value to write.
+//
+// Returns:
+//   0 if success, -1 if error.
+int WriteWord(int fd, __u8 reg, __u16 word_val) {
+  union i2c_smbus_data data;
+  data.byte = word_val;
+  struct i2c_smbus_ioctl_data args;
+  args.read_write = I2C_SMBUS_WRITE;
+  args.command = reg;
+  args.size = I2C_SMBUS_WORD_DATA;
+  args.data = &data;
+
+  if (PreconditionCheck(fd, reg) != true) {
+    return -1;
+  }
+
+  if (ioctl(fd, I2C_SMBUS, &args)) {
+    printf("Error writing to register: %s\n", strerror(errno));
+    return -1;
+  }
+  printf("Wrote to register %d: 0x%x\n", reg, word_val);
+  return 0;
+}
+
+// Reads a word from a specified register address.
+//
+// Args:
+//   fd: an integer, open device file descriptor.
+//   reg: an 8-bit unsigned integer, register number.
+//
+// Returns:
+//   word value read if success, -1 if error.
+int ReadWord(int fd, __u8 reg) {
+  union i2c_smbus_data data;
+  struct i2c_smbus_ioctl_data args;
+  args.read_write = I2C_SMBUS_READ;
+  args.command = reg;
+  args.size = I2C_SMBUS_WORD_DATA;
+  args.data = &data;
+
+  if (PreconditionCheck(fd, reg) != true) {
+    return -1;
+  }
+
+  if (ioctl(fd, I2C_SMBUS, &args)) {
+    printf("Error reading from bus: %s\n", strerror(errno));
+    return -1;
+  }
+  printf("Read back from bus: 0x%x\n", data.word);
+  return data.word;
+}
diff --git a/smogcheck.c b/smogcheck.c
new file mode 100644
index 0000000..0aba2de
--- /dev/null
+++ b/smogcheck.c
@@ -0,0 +1,47 @@
+// Copyright (c) 2011 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.
+//
+// smogcheck.c: demonstrate use of lib_smogcheck.c
+
+#include <stdlib.h>
+#include "lib/lib_smogcheck.h"
+
+void ExitOnError(int val) {
+  if (val < 0) {
+    exit(EXIT_FAILURE);
+  }
+}
+
+int main() {
+  int fd = -1;
+  int ret = -1;
+  const int kPcaSlaveAddress = 0x27;
+  const int kI2cBusAddress = 0x2;
+  const int kInaSlaveAddress = 0x40;
+  const __u8 kLedRegister = 0x3;
+  const __u8 kVoltageRegister = 0x2;
+  const __u8 kTurnOnLedOne = 0xfe;
+
+  fd = GetDeviceFile(kI2cBusAddress);
+  ExitOnError(fd);
+
+  // Enable LED 1
+  ret = SetSlaveAddress(fd, kPcaSlaveAddress);
+  ExitOnError(ret);
+
+  ret = WriteByte(fd, kLedRegister, kTurnOnLedOne);
+  ExitOnError(ret);
+
+  ret = ReadByte(fd, kLedRegister);
+  ExitOnError(ret);
+
+  // Read Backup Power voltage
+  ret = SetSlaveAddress(fd, kInaSlaveAddress);
+  ExitOnError(ret);
+
+  ret = ReadWord(fd, kVoltageRegister);
+  ExitOnError(ret);
+
+  return EXIT_SUCCESS;
+}