eeprom.c 7.3 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
/*
    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.

    2004-01-16  Jean Delvare <khali@linux-fr.org>
    Divide the eeprom in 32-byte (arbitrary) slices. This significantly
    speeds sensors up, as well as various scripts using the eeprom
    module.

    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.
*/

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
35
#include <linux/mutex.h>
L
Linus Torvalds 已提交
36 37 38 39 40 41

/* Addresses to scan */
static unsigned short normal_i2c[] = { 0x50, 0x51, 0x52, 0x53, 0x54,
					0x55, 0x56, 0x57, I2C_CLIENT_END };

/* Insmod parameters */
42
I2C_CLIENT_INSMOD_1(eeprom);
L
Linus Torvalds 已提交
43 44 45 46 47 48 49 50 51 52 53 54 55 56


/* 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 i2c_client client;
57
	struct mutex update_lock;
L
Linus Torvalds 已提交
58 59 60 61 62 63 64 65 66 67 68 69 70
	u8 valid;			/* bitfield, bit!=0 if slice is valid */
	unsigned long last_updated[8];	/* In jiffies, 8 slices */
	u8 data[EEPROM_SIZE];		/* Register values */
	enum eeprom_nature nature;
};


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 = {
71 72 73
	.driver = {
		.name	= "eeprom",
	},
L
Linus Torvalds 已提交
74 75 76 77 78 79 80 81 82 83
	.id		= I2C_DRIVERID_EEPROM,
	.attach_adapter	= eeprom_attach_adapter,
	.detach_client	= eeprom_detach_client,
};

static void eeprom_update_client(struct i2c_client *client, u8 slice)
{
	struct eeprom_data *data = i2c_get_clientdata(client);
	int i, j;

84
	mutex_lock(&data->update_lock);
L
Linus Torvalds 已提交
85 86 87 88 89 90

	if (!(data->valid & (1 << slice)) ||
	    time_after(jiffies, data->last_updated[slice] + 300 * HZ)) {
		dev_dbg(&client->dev, "Starting eeprom update, slice %u\n", slice);

		if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
91 92 93 94
			for (i = slice << 5; i < (slice + 1) << 5; i += 32)
				if (i2c_smbus_read_i2c_block_data(client, i,
							32, data->data + i)
							!= 32)
L
Linus Torvalds 已提交
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
					goto exit;
		} else {
			if (i2c_smbus_write_byte(client, slice << 5)) {
				dev_dbg(&client->dev, "eeprom read start has failed!\n");
				goto exit;
			}
			for (i = slice << 5; i < (slice + 1) << 5; i++) {
				j = i2c_smbus_read_byte(client);
				if (j < 0)
					goto exit;
				data->data[i] = (u8) j;
			}
		}
		data->last_updated[slice] = jiffies;
		data->valid |= (1 << slice);
	}
exit:
112
	mutex_unlock(&data->update_lock);
L
Linus Torvalds 已提交
113 114
}

115 116
static ssize_t eeprom_read(struct kobject *kobj, struct bin_attribute *bin_attr,
			   char *buf, loff_t off, size_t count)
L
Linus Torvalds 已提交
117 118 119 120 121 122 123 124 125 126 127 128 129 130
{
	struct i2c_client *client = to_i2c_client(container_of(kobj, struct device, kobj));
	struct eeprom_data *data = i2c_get_clientdata(client);
	u8 slice;

	if (off > EEPROM_SIZE)
		return 0;
	if (off + count > EEPROM_SIZE)
		count = EEPROM_SIZE - off;

	/* Only refresh slices which contain requested bytes */
	for (slice = off >> 5; slice <= (off + count - 1) >> 5; slice++)
		eeprom_update_client(client, slice);

131 132 133 134 135 136 137 138 139 140 141 142 143 144
	/* Hide Vaio private settings to regular users:
	   - BIOS passwords: bytes 0x00 to 0x0f
	   - UUID: bytes 0x10 to 0x1f
	   - Serial number: 0xc0 to 0xdf */
	if (data->nature == VAIO && !capable(CAP_SYS_ADMIN)) {
		int i;

		for (i = 0; i < count; i++) {
			if ((off + i <= 0x1f) ||
			    (off + i >= 0xc0 && off + i <= 0xdf))
				buf[i] = 0;
			else
				buf[i] = data->data[off + i];
		}
L
Linus Torvalds 已提交
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
	} else {
		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)
{
163
	return i2c_probe(adapter, &addr_data, eeprom_detect);
L
Linus Torvalds 已提交
164 165
}

166
/* This function is called by i2c_probe */
167
static int eeprom_detect(struct i2c_adapter *adapter, int address, int kind)
L
Linus Torvalds 已提交
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
{
	struct i2c_client *new_client;
	struct eeprom_data *data;
	int err = 0;

	/* There are three ways we can read the EEPROM data:
	   (1) I2C block reads (faster, but unsupported by most adapters)
	   (2) Consecutive byte reads (100% overhead)
	   (3) Regular byte data reads (200% overhead)
	   The third method is not implemented by this driver because all
	   known adapters support at least the second. */
	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA
					    | I2C_FUNC_SMBUS_BYTE))
		goto exit;

183
	if (!(data = kzalloc(sizeof(struct eeprom_data), GFP_KERNEL))) {
L
Linus Torvalds 已提交
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
		err = -ENOMEM;
		goto exit;
	}

	new_client = &data->client;
	memset(data->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;

	/* Fill in the remaining client fields */
	strlcpy(new_client->name, "eeprom", I2C_NAME_SIZE);
	data->valid = 0;
199
	mutex_init(&data->update_lock);
L
Linus Torvalds 已提交
200 201 202 203 204 205 206
	data->nature = UNKNOWN;

	/* Tell the I2C layer a new client has arrived */
	if ((err = i2c_attach_client(new_client)))
		goto exit_kfree;

	/* Detect the Vaio nature of EEPROMs.
207
	   We use the "PCG-" or "VGN-" prefix as the signature. */
L
Linus Torvalds 已提交
208
	if (address == 0x57) {
209 210 211 212 213 214 215 216
		char name[4];

		name[0] = i2c_smbus_read_byte_data(new_client, 0x80);
		name[1] = i2c_smbus_read_byte(new_client);
		name[2] = i2c_smbus_read_byte(new_client);
		name[3] = i2c_smbus_read_byte(new_client);

		if (!memcmp(name, "PCG-", 4) || !memcmp(name, "VGN-", 4)) {
L
Linus Torvalds 已提交
217
			dev_info(&new_client->dev, "Vaio EEPROM detected, "
218
				 "enabling privacy protection\n");
L
Linus Torvalds 已提交
219 220 221 222 223
			data->nature = VAIO;
		}
	}

	/* create the sysfs eeprom file */
224 225 226
	err = sysfs_create_bin_file(&new_client->dev.kobj, &eeprom_attr);
	if (err)
		goto exit_detach;
L
Linus Torvalds 已提交
227 228 229

	return 0;

230 231
exit_detach:
	i2c_detach_client(new_client);
L
Linus Torvalds 已提交
232 233 234 235 236 237 238 239 240 241
exit_kfree:
	kfree(data);
exit:
	return err;
}

static int eeprom_detach_client(struct i2c_client *client)
{
	int err;

242 243
	sysfs_remove_bin_file(&client->dev.kobj, &eeprom_attr);

L
Linus Torvalds 已提交
244
	err = i2c_detach_client(client);
245
	if (err)
L
Linus Torvalds 已提交
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
		return err;

	kfree(i2c_get_clientdata(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);