[U-Boot-Users] [Patch 4/9]U-boot-V2:I2C: Introduce i2c-dev
Menon, Nishanth
x0nishan at ti.com
Thu Jun 19 17:12:06 CEST 2008
I2C by default does not expose a device interface
for the adapters registering in the system. This
is usually not needed unless we need to debug
something. in which case, this is a handy little
layer when used in conjunction with the i2c
commands.
Signed-off-by: Nishanth Menon <x0nishan at ti.com>
---
drivers/i2c/Kconfig | 10 +
drivers/i2c/Makefile | 1
drivers/i2c/i2c-core.c | 12 +
drivers/i2c/i2c-core.h | 4
drivers/i2c/i2c-dev.c | 439 ++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/i2c-dev.h | 71 +++++++
6 files changed, 537 insertions(+)
Index: u-boot-v2.git/include/linux/i2c-dev.h
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ u-boot-v2.git/include/linux/i2c-dev.h 2008-06-19 08:42:58.000000000 -0500
@@ -0,0 +1,71 @@
+/**
+ * @file
+ * @brief i2c-bus driver, "char device" interface
+ *
+ * FileName: include/linux/i2c-dev.h
+ *
+ */
+/* Originally from Linux kernel. Ported to U-boot-v2.
+ * (C) Copyright 2008
+ * Texas Instruments, <www.ti.com>
+ * Nishanth Menon <x0nishan at ti.com>
+ */
+/*
+ Copyright (C) 1995-97 Simon G. Vogl
+ Copyright (C) 1998-99 Frodo Looijaard <frodol at dds.nl>
+
+ 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.
+*/
+
+#ifndef _LINUX_I2C_DEV_H
+#define _LINUX_I2C_DEV_H
+
+#include <linux/types.h>
+
+/* /dev/i2c-X ioctl commands. The ioctl's parameter is always an
+ * unsigned long, except for:
+ * - I2C_FUNCS, takes pointer to an unsigned long
+ * - I2C_RDWR, takes pointer to struct i2c_rdwr_ioctl_data
+ * - I2C_SMBUS, takes pointer to struct i2c_smbus_ioctl_data
+ */
+#define I2C_RETRIES 0x0701 /* number of times a device address should
+ be polled when not acknowledging */
+#define I2C_TIMEOUT 0x0702 /* set timeout in jiffies - call with int */
+
+/* NOTE: Slave address is 7 or 10 bits, but 10-bit addresses
+ * are NOT supported! (due to code brokenness)
+ */
+#define I2C_SLAVE 0x0703 /* Use this slave address */
+#define I2C_SLAVE_FORCE 0x0706 /* Use this slave address, even if it
+ is already in use by a driver! */
+#define I2C_TENBIT 0x0704 /* 0 for 7 bit addrs, != 0 for 10 bit */
+
+#define I2C_FUNCS 0x0705 /* Get the adapter functionality mask */
+
+#define I2C_RDWR 0x0707 /* Combined R/W transfer (one STOP only) */
+
+#define I2C_PEC 0x0708 /* != 0 to use PEC with SMBus */
+
+/* This is the structure as used in the I2C_RDWR ioctl call */
+struct i2c_rdwr_ioctl_data {
+ struct i2c_msg *msgs; /* pointers to i2c_msgs */
+ u32 nmsgs; /* number of i2c_msgs */
+};
+
+#define I2C_RDRW_IOCTL_MAX_MSGS 42
+
+#define I2C_DEV_NAME "i2cd"
+
+#endif /* _LINUX_I2C_DEV_H */
Index: u-boot-v2.git/drivers/i2c/i2c-dev.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ u-boot-v2.git/drivers/i2c/i2c-dev.c 2008-06-19 08:42:58.000000000 -0500
@@ -0,0 +1,439 @@
+/**
+ * @file
+ * @brief i2c-bus driver, "char device" interface
+ *
+ * FileName: drivers/i2c/i2c-dev.c
+ *
+ */
+/* Originally from Linux kernel. Ported to U-boot-v2.
+ * (C) Copyright 2008
+ * Texas Instruments, <www.ti.com>
+ * Nishanth Menon <x0nishan at ti.com>
+ */
+/*
+ Copyright (C) 1995-97 Simon G. Vogl
+ Copyright (C) 1998-99 Frodo Looijaard <frodol at dds.nl>
+ Copyright (C) 2003 Greg Kroah-Hartman <greg at kroah.com>
+
+ 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.
+*/
+
+/* Note that this is a complete rewrite of Simon Vogl's i2c-dev module.
+ But I have used so much of his original code and ideas that it seems
+ only fair to recognize him as co-author -- Frodo */
+
+/* The I2C_RDWR ioctl code is written by Kolja Waschk <waschk at telos.de> */
+
+#include <common.h>
+#include <errno.h>
+#include <list.h>
+#include <init.h>
+#include <driver.h>
+#include <malloc.h>
+#include <string.h>
+#include <linux/i2c.h>
+#include <linux/i2c-dev.h>
+
+#include "i2c-core.h"
+
+#ifdef CONFIG_I2C_DEBUG_DEV
+#define DBG_MODULE_NAME "i2cdev"
+#define dev_dbg(ARGS...) fprintf(stdout, ARGS);
+#define dev_warn(ARGS...) fprintf(stderr, ARGS);
+#define dbg_entry(FORMAT, ARGS...) fprintf(stdout,\
+ DBG_MODULE_NAME"%s:%d:Entry:"FORMAT"\n",\
+ __func__, __LINE__, ARGS)
+#define dbg_exit(FORMAT, ARGS...) fprintf(stdout,\
+ DBG_MODULE_NAME"%s:%d:Exit:"FORMAT"\n",\
+ __func__, __LINE__, ARGS)
+#else
+#define dev_dbg(ARGS...)
+#define dev_warn(ARGS...)
+#define dbg_entry(FORMAT, ARGS...)
+#define dbg_exit(FORMAT, ARGS...)
+#endif
+#define dev_err(ARGS...) fprintf(stderr, ARGS);
+
+static struct i2c_driver i2cdev_driver;
+
+/*
+ * An i2c_dev represents an i2c_adapter ... an I2C or SMBus master, not a
+ * slave (i2c_client) with which messages will be exchanged. It's coupled
+ * with a character special file which is accessed by user mode drivers.
+ *
+ * The list of i2c_dev structures is parallel to the i2c_adapter lists
+ * maintained by the driver model, and is updated using notifications
+ * delivered to the i2cdev_driver.
+ */
+struct i2c_dev {
+ struct list_head list;
+ struct i2c_adapter *adap;
+ struct device_d dev;
+};
+
+#define I2C_MINORS 256
+static LIST_HEAD(i2c_dev_list);
+
+static struct i2c_dev *i2c_dev_get_by_minor(unsigned index)
+{
+ struct i2c_dev *i2c_dev;
+
+ dbg_entry("index=%x", index);
+
+ list_for_each_entry(i2c_dev, &i2c_dev_list, list) {
+ if (i2c_dev->adap->nr == index)
+ goto found;
+ }
+ i2c_dev = NULL;
+found:
+ return i2c_dev;
+}
+
+static struct i2c_dev *get_free_i2c_dev(struct i2c_adapter *adap)
+{
+ struct i2c_dev *i2c_dev;
+ dbg_entry("adap=%x", (u32) adap);
+
+ if (adap->nr >= I2C_MINORS) {
+ dev_err("i2c-dev: Out of device minors (%d)\n", adap->nr);
+ return NULL;
+ }
+
+ i2c_dev = calloc(1, sizeof(*i2c_dev));
+ if (!i2c_dev)
+ return NULL;
+ i2c_dev->adap = adap;
+
+ list_add_tail(&i2c_dev->list, &i2c_dev_list);
+ return i2c_dev;
+}
+
+static void return_i2c_dev(struct i2c_dev *i2c_dev)
+{
+ dbg_entry("i2c_dev=%x", (u32) i2c_dev);
+ list_del(&i2c_dev->list);
+ free(i2c_dev);
+}
+
+/* ------------------------------------------------------------------------- */
+
+/*
+ * After opening an instance of this character special file, a file
+ * descriptor starts out associated only with an i2c_adapter (and bus).
+ *
+ * Using the I2C_RDWR ioctl(), you can then *immediately* issue i2c_msg
+ * traffic to any devices on the bus used by that adapter. That's because
+ * the i2c_msg vectors embed all the addressing information they need, and
+ * are submitted directly to an i2c_adapter. However, SMBus-only adapters
+ * don't support that interface.
+ *
+ * To use read()/write() system calls on that file descriptor, or to use
+ * SMBus interfaces (and work with SMBus-only hosts!), you must first issue
+ * an I2C_SLAVE (or I2C_SLAVE_FORCE) ioctl. That configures an anonymous
+ * (never registered) i2c_client so it holds the addressing information
+ * needed by those system calls and by this SMBus interface.
+ */
+
+static ssize_t i2cdev_read(struct device_d *dev, void *buffer, size_t count,
+ ulong offset, ulong flags)
+{
+ struct i2c_client *client = (struct i2c_client *)dev->priv;
+
+ dbg_entry("dev=%x buffer=%x count=%x offset=%x flags=%x", (u32) dev,
+ (u32) buffer, (u32) count, (u32) offset, (u32) flags);
+ return i2c_master_recv(client, buffer, count);
+}
+
+static ssize_t i2cdev_write(struct device_d *dev, const void *buffer,
+ size_t count, ulong offset, ulong flags)
+{
+ struct i2c_client *client = (struct i2c_client *)dev->priv;
+
+ dbg_entry("dev=%x buffer=%x count=%x offset=%x flags=%x", (u32) dev,
+ (u32) buffer, (u32) count, (u32) offset, (u32) flags);
+ return i2c_master_send(client, buffer, count);
+}
+
+static int i2cdev_check(struct device_d *dev, void *addrp)
+{
+ struct i2c_client *client = i2c_verify_client(dev);
+
+ dbg_entry("dev=%x addrp=%x", (u32) dev, (u32) addrp);
+ if (!client || client->addr != *(unsigned int *)addrp)
+ return 0;
+
+ return dev->driver ? -EBUSY : 0;
+}
+
+/* This address checking function differs from the one in i2c-core
+ in that it considers an address with a registered device, but no
+ driver bound to it, as NOT busy. */
+static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr)
+{
+ int ret = 0;
+ struct device_d *child;
+ dbg_entry("adapter=%x addr=%x", (u32) adapter, (u32) addr);
+ device_for_each_child((&(adapter->dev)), child) {
+ ret = i2cdev_check(child, &addr);
+ if (ret)
+ break;
+ }
+ return ret;
+}
+
+static int i2cdev_ioctl_rdrw(struct i2c_client *client, unsigned long arg)
+{
+ struct i2c_rdwr_ioctl_data rdwr_arg;
+ struct i2c_msg *rdwr_pa;
+ u8 **data_ptrs;
+ int i, res;
+
+ dbg_entry("client=%x arg=%x", (u32) client, (u32) arg);
+ memcpy(&rdwr_arg, (struct i2c_rdwr_ioctl_data *)arg, sizeof(rdwr_arg));
+
+ /* Put an arbitrary limit on the number of messages that can
+ * be sent at once */
+ if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)
+ return -EINVAL;
+
+ rdwr_pa = (struct i2c_msg *)
+ malloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg));
+ if (!rdwr_pa)
+ return -ENOMEM;
+
+ memcpy(rdwr_pa, rdwr_arg.msgs, rdwr_arg.nmsgs * sizeof(struct i2c_msg));
+
+ data_ptrs = malloc(rdwr_arg.nmsgs * sizeof(u8 *));
+ if (data_ptrs == NULL) {
+ free(rdwr_pa);
+ return -ENOMEM;
+ }
+
+ res = 0;
+ for (i = 0; i < rdwr_arg.nmsgs; i++) {
+ /* Limit the size of the message to a sane amount;
+ * and don't let length change either. */
+ if ((rdwr_pa[i].len > 8192) ||
+ (rdwr_pa[i].flags & I2C_M_RECV_LEN)) {
+ res = -EINVAL;
+ break;
+ }
+ data_ptrs[i] = (u8 *) rdwr_pa[i].buf;
+ rdwr_pa[i].buf = malloc(rdwr_pa[i].len);
+ if (rdwr_pa[i].buf == NULL) {
+ res = -ENOMEM;
+ break;
+ }
+ memcpy(rdwr_pa[i].buf, data_ptrs[i], rdwr_pa[i].len);
+ }
+ if (res < 0) {
+ int j;
+ for (j = 0; j < i; ++j)
+ free(rdwr_pa[j].buf);
+ free(data_ptrs);
+ free(rdwr_pa);
+ return res;
+ }
+
+ res = i2c_transfer(client->adapter, rdwr_pa, rdwr_arg.nmsgs);
+ while (i-- > 0) {
+ if (res >= 0 && (rdwr_pa[i].flags & I2C_M_RD))
+ memcpy(data_ptrs[i], rdwr_pa[i].buf, rdwr_pa[i].len);
+ free(rdwr_pa[i].buf);
+ }
+ free(data_ptrs);
+ free(rdwr_pa);
+ return res;
+}
+
+
+static int i2cdev_ioctl(struct device_d *dev, int cmd, void *argument)
+{
+ struct i2c_client *client = (struct i2c_client *)dev->priv;
+ unsigned long funcs;
+ unsigned long arg = (unsigned long)argument;
+
+ dbg_entry("dev=%x cmd=%x arg=%x", (u32) dev, (u32) cmd, (u32) arg);
+ dev_dbg("ioctl, cmd=0x%02x, arg=0x%04x\n", cmd, arg);
+
+ switch (cmd) {
+ case I2C_SLAVE:
+ case I2C_SLAVE_FORCE:
+ if ((arg > 0x3ff) ||
+ (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
+ return -EINVAL;
+ if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
+ return -EBUSY;
+ /* REVISIT: address could become busy later */
+ client->addr = arg;
+ return 0;
+ case I2C_TENBIT:
+ if (arg)
+ client->flags |= I2C_M_TEN;
+ else
+ client->flags &= ~I2C_M_TEN;
+ return 0;
+ case I2C_PEC:
+ if (arg)
+ client->flags |= I2C_CLIENT_PEC;
+ else
+ client->flags &= ~I2C_CLIENT_PEC;
+ return 0;
+ case I2C_FUNCS:
+ funcs = i2c_get_functionality(client->adapter);
+ if (!arg)
+ return -ENOMEM;
+
+ *((unsigned long *)arg) = funcs;
+ return 0;
+
+ case I2C_RDWR:
+ return i2cdev_ioctl_rdrw(client, arg);
+
+ case I2C_RETRIES:
+ client->adapter->retries = arg;
+ break;
+ case I2C_TIMEOUT:
+ client->adapter->timeout = arg;
+ break;
+ default:
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+static int i2cdev_open(struct device_d *dev, struct filep *file)
+{
+ struct i2c_client *client;
+ struct i2c_adapter *adap;
+ struct i2c_dev *i2c_dev;
+
+ dbg_entry("dev=%x file=%x", (u32) dev, (u32) file);
+ i2c_dev = i2c_dev_get_by_minor(dev->map_base);
+ if (!i2c_dev)
+ return -ENODEV;
+
+ adap = i2c_get_adapter(i2c_dev->adap->nr);
+ if (!adap)
+ return -ENODEV;
+
+ /* This creates an anonymous i2c_client, which may later be
+ * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
+ *
+ * This client is ** NEVER REGISTERED ** with the driver model
+ * or I2C core code!! It just holds private copies of addressing
+ * information and maybe a PEC flag.
+ */
+ client = calloc(1, sizeof(*client));
+ if (!client) {
+ i2c_put_adapter(adap);
+ return -ENOMEM;
+ }
+ sprintf(client->name, "i2c-dev %d", adap->nr);
+ client->driver = &i2cdev_driver;
+
+ client->adapter = adap;
+ dev->priv = client;
+
+ return 0;
+}
+
+static int i2cdev_release(struct device_d *dev, struct filep *file)
+{
+ struct i2c_client *client = dev->priv;
+
+ dbg_entry("dev=%x file=%x", (u32) dev, (u32) file);
+ i2c_put_adapter(client->adapter);
+ free(client);
+ dev->priv = NULL;
+
+ return 0;
+}
+
+static struct driver_d i2cdev_fops = {
+ .name = I2C_DEV_NAME,
+ .read = i2cdev_read,
+ .probe = dummy_probe,
+ .write = i2cdev_write,
+ .ioctl = i2cdev_ioctl,
+ .open = i2cdev_open,
+ .close = i2cdev_release,
+ .type = DEVICE_TYPE_I2CDEV,
+};
+
+static int i2c_dev_init(void)
+{
+
+ int res = 0;
+ dbg_entry("%s", "none");
+ res = register_driver(&i2cdev_fops);
+ dbg_exit("res=%d", res);
+ return res;
+}
+
+device_initcall(i2c_dev_init);
+
+/* ------------------------------------------------------------------------- */
+
+int i2cdev_attach_adapter(struct i2c_adapter *adap)
+{
+ struct device_d *dev;
+ struct i2c_dev *i2c_dev;
+ int res;
+ dbg_entry("adap=%x", (u32) adap);
+ i2c_dev = get_free_i2c_dev(adap);
+ if (!i2c_dev) {
+ dev_err("Did not get free dev-adap %d\n", adap->nr);
+ return -ENOMEM;
+ }
+ dev = &i2c_dev->dev;
+ /* Store my adapter number in mapbase for retrieval */
+ dev->map_base = adap->nr;
+ /* my i2cdev type */
+ sprintf((char *)&dev->name, "%s", I2C_DEV_NAME);
+ sprintf((char *)&dev->id, "%s%d", I2C_DEV_NAME, adap->nr);
+ dev->type = DEVICE_TYPE_I2CDEV;
+
+ res = register_device(dev);
+ if (res < 0) {
+ dev_err("Device %d registration failed %d\n", adap->nr, res);
+ perror(dev->id);
+ return_i2c_dev(i2c_dev);
+ return res;
+ }
+ return res;
+}
+EXPORT_SYMBOL(i2cdev_attach_adapter);
+
+int i2cdev_detach_adapter(struct i2c_adapter *adap)
+{
+ struct i2c_dev *i2c_dev;
+ int ret = 0;
+ dbg_entry("adap=%x", (u32) adap);
+ i2c_dev = i2c_dev_get_by_minor(adap->nr);
+ if (!i2c_dev) {
+ dev_err("not registered adapter %d with me!\n", adap->nr);
+ return -ENODEV;
+ }
+ ret = unregister_device(&(i2c_dev->dev));
+ if (ret) {
+ dev_err("Adapter %d dev is in use(%d)?\n", adap->nr, ret);
+ return ret;
+ }
+ if (i2c_dev)
+ return_i2c_dev(i2c_dev);
+ return ret;
+}
+EXPORT_SYMBOL(i2cdev_attach_adapter);
Index: u-boot-v2.git/drivers/i2c/Kconfig
===================================================================
--- u-boot-v2.git.orig/drivers/i2c/Kconfig 2008-06-19 08:42:46.000000000 -0500
+++ u-boot-v2.git/drivers/i2c/Kconfig 2008-06-19 08:42:58.000000000 -0500
@@ -19,6 +19,16 @@
if I2C
+config I2C_CHARDEV
+ tristate "I2C device interface"
+ help
+ Say Y here to use i2c-* device files, usually found in the /dev
+ directory on your system. They make it possible to have applications
+ to use the I2C bus.
+
+ This support is also available as a module. If so, the module
+ will be called i2c-dev.
+
source drivers/i2c/busses/Kconfig
config I2C_DEBUG_CORE
Index: u-boot-v2.git/drivers/i2c/Makefile
===================================================================
--- u-boot-v2.git.orig/drivers/i2c/Makefile 2008-06-19 08:42:46.000000000 -0500
+++ u-boot-v2.git/drivers/i2c/Makefile 2008-06-19 08:42:58.000000000 -0500
@@ -3,6 +3,7 @@
#
obj-$(CONFIG_I2C) += i2c-core.o
+obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o
obj-y += busses/
ifeq ($(CONFIG_I2C_DEBUG_CORE),y)
Index: u-boot-v2.git/drivers/i2c/i2c-core.c
===================================================================
--- u-boot-v2.git.orig/drivers/i2c/i2c-core.c 2008-06-19 08:42:46.000000000 -0500
+++ u-boot-v2.git/drivers/i2c/i2c-core.c 2008-06-19 08:42:58.000000000 -0500
@@ -102,6 +102,10 @@
dev_dbg("adapter [%s] registered\n", adap->name);
+#ifdef CONFIG_I2C_CHARDEV
+ i2cdev_attach_adapter(adap);
+#endif
+
out_unlock:
return res;
@@ -222,6 +226,14 @@
goto out_unlock;
}
+#ifdef CONFIG_I2C_CHARDEV
+ res = i2cdev_detach_adapter(adap);
+ if (res) {
+ dev_err("adapter in use\n");
+ goto out_unlock;
+ }
+#endif
+
/* detach any active clients. This must be done first, because
* it can fail; in which case we give up. */
list_for_each_safe(item, _n, &adap->clients) {
Index: u-boot-v2.git/drivers/i2c/i2c-core.h
===================================================================
--- u-boot-v2.git.orig/drivers/i2c/i2c-core.h 2008-06-19 08:42:46.000000000 -0500
+++ u-boot-v2.git/drivers/i2c/i2c-core.h 2008-06-19 08:42:58.000000000 -0500
@@ -34,4 +34,8 @@
extern struct list_head __i2c_board_list;
extern int __i2c_first_dynamic_bus_num;
+/* Automatically called by i2c-Core..*/
+int i2cdev_attach_adapter(struct i2c_adapter *adap);
+int i2cdev_detach_adapter(struct i2c_adapter *adap);
+
#endif /* __i2C_CORE_H */
More information about the U-Boot
mailing list