]> git.hungrycats.org Git - linux/commitdiff
[PATCH] I2C: add eeprom i2c chip driver.
authorGreg Kroah-Hartman <greg@kroah.com>
Tue, 23 Sep 2003 09:23:08 +0000 (02:23 -0700)
committerGreg Kroah-Hartman <greg@kroah.com>
Tue, 23 Sep 2003 09:23:08 +0000 (02:23 -0700)
This is based on the i2c cvs driver, but ported to 2.6 and rewritten
to use the sysfs binary file interface.

Documentation/i2c/sysfs-interface
drivers/i2c/chips/Kconfig
drivers/i2c/chips/Makefile
drivers/i2c/chips/eeprom.c [new file with mode: 0644]

index a62f15ecaf3e79ab125a063bae0bce1ad16687db..d601296ce5a9539a27a00097922b62943ec4c48f 100644 (file)
@@ -77,7 +77,10 @@ curr_min[1-n]        Current min or hysteresis value.
 curr_input[1-n]        Current input value
                Fixed point XXXXX, divide by 1000 to get Amps.
                Read only.
-               
+
+eeprom         Raw EEPROM data in binary form.
+               Read only.
+
 fan_min[1-3]   Fan minimum value
                Integer value indicating RPM
                Read/Write.
index 7c0db14084290add2a314cda2a2efe8417fd2253..7e8db5a4bfb0d16d352818f96189b375be760187 100644 (file)
@@ -21,6 +21,18 @@ config SENSORS_ADM1021
          This driver can also be built as a module.  If so, the module
          will be called adm1021.
 
+config SENSORS_EEPROM
+       tristate "EEPROM (DIMM) reader"
+       depends on I2C && EXPERIMENTAL
+       select I2C_SENSOR
+       help
+         If you say yes here you get read-only access to the EEPROM data
+         available on modern memory DIMMs, and which could theoretically
+         also be available on other devices.
+
+         This driver can also be built as a module.  If so, the module
+         will be called eeprom.
+
 config SENSORS_IT87
        tristate "National Semiconductors IT87 and compatibles"
        depends on I2C && EXPERIMENTAL
index 7c244c8b5cd0dee2f494bfef25e9b36411b1dc95..e4896edfae26a556078e84160f39f271531e699c 100644 (file)
@@ -6,6 +6,7 @@
 obj-$(CONFIG_SENSORS_W83781D)  += w83781d.o
 
 obj-$(CONFIG_SENSORS_ADM1021)  += adm1021.o
+obj-$(CONFIG_SENSORS_EEPROM)   += eeprom.o
 obj-$(CONFIG_SENSORS_IT87)     += it87.o
 obj-$(CONFIG_SENSORS_LM75)     += lm75.o
 obj-$(CONFIG_SENSORS_LM78)     += lm78.o
diff --git a/drivers/i2c/chips/eeprom.c b/drivers/i2c/chips/eeprom.c
new file mode 100644 (file)
index 0000000..83baadf
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+    eeprom.c - Part of lm_sensors, Linux kernel modules for hardware
+               monitoring
+    Copyright (C) 1998, 1999  Frodo Looijaard <frodol@dds.nl> and
+                              Philip Edelbrock <phil@netroedge.com>
+    Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
+    Copyright (C) 2003 IBM Corp.
+
+    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.
+*/
+
+/* #define DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/i2c-sensor.h>
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = { I2C_CLIENT_END };
+static unsigned short normal_i2c_range[] = { 0x50, 0x57, I2C_CLIENT_END };
+static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
+static unsigned int normal_isa_range[] = { I2C_CLIENT_ISA_END };
+
+/* Insmod parameters */
+SENSORS_INSMOD_1(eeprom);
+
+static int checksum = 0;
+MODULE_PARM(checksum, "i");
+MODULE_PARM_DESC(checksum, "Only accept eeproms whose checksum is correct");
+
+
+/* EEPROM registers */
+#define EEPROM_REG_CHECKSUM    0x3f
+
+/* EEPROM memory types: */
+#define ONE_K                  1
+#define TWO_K                  2
+#define FOUR_K                 3
+#define EIGHT_K                        4
+#define SIXTEEN_K              5
+
+/* Size of EEPROM in bytes */
+#define EEPROM_SIZE            256
+
+/* possible types of eeprom devices */
+enum eeprom_nature {
+       UNKNOWN,
+       VAIO,
+};
+
+/* Each client has this additional data */
+struct eeprom_data {
+       struct semaphore update_lock;
+       char valid;                     /* !=0 if following fields are valid */
+       unsigned long last_updated;     /* In jiffies */
+       u8 data[EEPROM_SIZE];           /* Register values */
+};
+
+
+static int eeprom_attach_adapter(struct i2c_adapter *adapter);
+static int eeprom_detect(struct i2c_adapter *adapter, int address, int kind);
+static int eeprom_detach_client(struct i2c_client *client);
+
+/* This is the driver that will be inserted */
+static struct i2c_driver eeprom_driver = {
+       .owner          = THIS_MODULE,
+       .name           = "eeprom",
+       .id             = I2C_DRIVERID_EEPROM,
+       .flags          = I2C_DF_NOTIFY,
+       .attach_adapter = eeprom_attach_adapter,
+       .detach_client  = eeprom_detach_client,
+};
+
+static int eeprom_id = 0;
+
+static void eeprom_update_client(struct i2c_client *client)
+{
+       struct eeprom_data *data = i2c_get_clientdata(client);
+       int i, j;
+
+       down(&data->update_lock);
+
+       if ((jiffies - data->last_updated > 300 * HZ) |
+           (jiffies < data->last_updated) || !data->valid) {
+               dev_dbg(&client->dev, "Starting eeprom update\n");
+
+               if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
+                       for (i=0; i < EEPROM_SIZE; i += I2C_SMBUS_I2C_BLOCK_MAX)
+                               if (i2c_smbus_read_i2c_block_data(client, i, data->data + i) != I2C_SMBUS_I2C_BLOCK_MAX)
+                                       goto exit;
+               } else {
+                       if (i2c_smbus_write_byte(client, 0)) {
+                               dev_dbg(&client->dev, "eeprom read start has failed!\n");
+                               goto exit;
+                       }
+                       for (i = 0; i < EEPROM_SIZE; i++) {
+                               j = i2c_smbus_read_byte(client);
+                               if (j < 0)
+                                       goto exit;
+                               data->data[i] = (u8) j;
+                       }
+               }
+               data->last_updated = jiffies;
+               data->valid = 1;
+       }
+exit:
+       up(&data->update_lock);
+}
+
+static ssize_t eeprom_read(struct kobject *kobj, char *buf, loff_t off, size_t count)
+{
+       struct i2c_client *client = to_i2c_client(container_of(kobj, struct device, kobj));
+       struct eeprom_data *data = i2c_get_clientdata(client);
+
+       eeprom_update_client(client);
+
+       if (off > EEPROM_SIZE)
+               return 0;
+       if (off + count > EEPROM_SIZE)
+               count = EEPROM_SIZE - off;
+
+       memcpy(buf, &data->data[off], count);
+       return count;
+}
+
+static struct bin_attribute eeprom_attr = {
+       .attr = {
+               .name = "eeprom",
+               .mode = S_IRUGO,
+       },
+       .size = EEPROM_SIZE,
+       .read = eeprom_read,
+};
+
+static int eeprom_attach_adapter(struct i2c_adapter *adapter)
+{
+       return i2c_detect(adapter, &addr_data, eeprom_detect);
+}
+
+/* This function is called by i2c_detect */
+int eeprom_detect(struct i2c_adapter *adapter, int address, int kind)
+{
+       int i, cs;
+       struct i2c_client *new_client;
+       struct eeprom_data *data;
+       enum eeprom_nature nature = UNKNOWN;
+       int err = 0;
+
+       /* Make sure we aren't probing the ISA bus!! This is just a safety check
+          at this moment; i2c_detect really won't call us. */
+#ifdef DEBUG
+       if (i2c_is_isa_adapter(adapter)) {
+               dev_dbg(&adapter->dev, " eeprom_detect called for an ISA bus adapter?!?\n");
+               return 0;
+       }
+#endif
+
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+               goto exit;
+
+       /* OK. For now, we presume we have a valid client. We now create the
+          client structure, even though we cannot fill it completely yet.
+          But it allows us to access eeprom_{read,write}_value. */
+       if (!(new_client = kmalloc(sizeof(struct i2c_client) +
+                                  sizeof(struct eeprom_data),
+                                  GFP_KERNEL))) {
+               err = -ENOMEM;
+               goto exit;
+       }
+       memset(new_client, 0x00, sizeof(struct i2c_client) +
+                                sizeof(struct eeprom_data));
+
+       data = (struct eeprom_data *) (new_client + 1);
+       memset(data, 0xff, EEPROM_SIZE);
+       i2c_set_clientdata(new_client, data);
+       new_client->addr = address;
+       new_client->adapter = adapter;
+       new_client->driver = &eeprom_driver;
+       new_client->flags = 0;
+
+       /* Now, we do the remaining detection. It is not there, unless you force
+          the checksum to work out. */
+       if (checksum) {
+               /* prevent 24RF08 corruption */
+               i2c_smbus_write_quick(new_client, 0);
+               cs = 0;
+               for (i = 0; i <= 0x3e; i++)
+                       cs += i2c_smbus_read_byte_data(new_client, i);
+               cs &= 0xff;
+               if (i2c_smbus_read_byte_data (new_client, EEPROM_REG_CHECKSUM) != cs)
+                       goto exit_kfree;
+       }
+
+       /* Detect the Vaio nature of EEPROMs.
+          We use the "PCG-" prefix as the signature. */
+       if (address == 0x57) {
+               if (i2c_smbus_read_byte_data(new_client, 0x80) == 'P' && 
+                   i2c_smbus_read_byte_data(new_client, 0x81) == 'C' && 
+                   i2c_smbus_read_byte_data(new_client, 0x82) == 'G' &&
+                   i2c_smbus_read_byte_data(new_client, 0x83) == '-')
+                       nature = VAIO;
+       }
+
+       /* If this is a VIAO, then we only allow root to read from this file,
+          as BIOS passwords can be present here in plaintext */
+       switch (nature) {
+       case VAIO:
+               eeprom_attr.attr.mode = S_IRUSR;
+               break;
+       default:
+               eeprom_attr.attr.mode = S_IRUGO;
+       }
+
+       /* Fill in the remaining client fields */
+       strncpy(new_client->name, "eeprom", I2C_NAME_SIZE);
+       new_client->id = eeprom_id++;
+       data->valid = 0;
+       init_MUTEX(&data->update_lock);
+
+       /* Tell the I2C layer a new client has arrived */
+       if ((err = i2c_attach_client(new_client)))
+               goto exit_kfree;
+
+       /* create the sysfs eeprom file */
+       sysfs_create_bin_file(&new_client->dev.kobj, &eeprom_attr);
+
+       return 0;
+
+exit_kfree:
+       kfree(new_client);
+exit:
+       return err;
+}
+
+static int eeprom_detach_client(struct i2c_client *client)
+{
+       int err;
+
+       err = i2c_detach_client(client);
+       if (err) {
+               dev_err(&client->dev, "Client deregistration failed, client not detached.\n");
+               return err;
+       }
+
+       kfree(client);
+
+       return 0;
+}
+
+static int __init eeprom_init(void)
+{
+       return i2c_add_driver(&eeprom_driver);
+}
+
+static void __exit eeprom_exit(void)
+{
+       i2c_del_driver(&eeprom_driver);
+}
+
+
+MODULE_AUTHOR("Frodo Looijaard <frodol@dds.nl> and "
+               "Philip Edelbrock <phil@netroedge.com> and "
+               "Greg Kroah-Hartman <greg@kroah.com>");
+MODULE_DESCRIPTION("I2C EEPROM driver");
+MODULE_LICENSE("GPL");
+
+module_init(eeprom_init);
+module_exit(eeprom_exit);