mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
112
drivers/misc/eeprom/Kconfig
Normal file
112
drivers/misc/eeprom/Kconfig
Normal file
|
@ -0,0 +1,112 @@
|
|||
menu "EEPROM support"
|
||||
|
||||
config EEPROM_AT24
|
||||
tristate "I2C EEPROMs / RAMs / ROMs from most vendors"
|
||||
depends on I2C && SYSFS
|
||||
help
|
||||
Enable this driver to get read/write support to most I2C EEPROMs
|
||||
and compatible devices like FRAMs, SRAMs, ROMs etc. After you
|
||||
configure the driver to know about each chip on your target
|
||||
board. Use these generic chip names, instead of vendor-specific
|
||||
ones like at24c64, 24lc02 or fm24c04:
|
||||
|
||||
24c00, 24c01, 24c02, spd (readonly 24c02), 24c04, 24c08,
|
||||
24c16, 24c32, 24c64, 24c128, 24c256, 24c512, 24c1024
|
||||
|
||||
Unless you like data loss puzzles, always be sure that any chip
|
||||
you configure as a 24c32 (32 kbit) or larger is NOT really a
|
||||
24c16 (16 kbit) or smaller, and vice versa. Marking the chip
|
||||
as read-only won't help recover from this. Also, if your chip
|
||||
has any software write-protect mechanism you may want to review the
|
||||
code to make sure this driver won't turn it on by accident.
|
||||
|
||||
If you use this with an SMBus adapter instead of an I2C adapter,
|
||||
full functionality is not available. Only smaller devices are
|
||||
supported (24c16 and below, max 4 kByte).
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called at24.
|
||||
|
||||
config EEPROM_AT25
|
||||
tristate "SPI EEPROMs from most vendors"
|
||||
depends on SPI && SYSFS
|
||||
help
|
||||
Enable this driver to get read/write support to most SPI EEPROMs,
|
||||
after you configure the board init code to know about each eeprom
|
||||
on your target board.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called at25.
|
||||
|
||||
config EEPROM_LEGACY
|
||||
tristate "Old I2C EEPROM reader"
|
||||
depends on I2C && SYSFS
|
||||
help
|
||||
If you say yes here you get read-only access to the EEPROM data
|
||||
available on modern memory DIMMs and Sony Vaio laptops via I2C. Such
|
||||
EEPROMs could theoretically be available on other devices as well.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called eeprom.
|
||||
|
||||
config EEPROM_MAX6875
|
||||
tristate "Maxim MAX6874/5 power supply supervisor"
|
||||
depends on I2C
|
||||
help
|
||||
If you say yes here you get read-only support for the user EEPROM of
|
||||
the Maxim MAX6874/5 EEPROM-programmable, quad power-supply
|
||||
sequencer/supervisor.
|
||||
|
||||
All other features of this chip should be accessed via i2c-dev.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called max6875.
|
||||
|
||||
|
||||
config EEPROM_93CX6
|
||||
tristate "EEPROM 93CX6 support"
|
||||
help
|
||||
This is a driver for the EEPROM chipsets 93c46 and 93c66.
|
||||
The driver supports both read as well as write commands.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config EEPROM_93XX46
|
||||
tristate "Microwire EEPROM 93XX46 support"
|
||||
depends on SPI && SYSFS
|
||||
help
|
||||
Driver for the microwire EEPROM chipsets 93xx46x. The driver
|
||||
supports both read and write commands and also the command to
|
||||
erase the whole EEPROM.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called eeprom_93xx46.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config EEPROM_DIGSY_MTC_CFG
|
||||
bool "DigsyMTC display configuration EEPROMs device"
|
||||
depends on GPIO_MPC5200 && SPI_GPIO
|
||||
help
|
||||
This option enables access to display configuration EEPROMs
|
||||
on digsy_mtc board. You have to additionally select Microwire
|
||||
EEPROM 93XX46 driver. sysfs entries will be created for that
|
||||
EEPROM allowing to read/write the configuration data or to
|
||||
erase the whole EEPROM.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config EEPROM_SUNXI_SID
|
||||
tristate "Allwinner sunxi security ID support"
|
||||
depends on ARCH_SUNXI && SYSFS
|
||||
help
|
||||
This is a driver for the 'security ID' available on various Allwinner
|
||||
devices.
|
||||
|
||||
Due to the potential risks involved with changing e-fuses,
|
||||
this driver is read-only.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called sunxi_sid.
|
||||
|
||||
endmenu
|
8
drivers/misc/eeprom/Makefile
Normal file
8
drivers/misc/eeprom/Makefile
Normal file
|
@ -0,0 +1,8 @@
|
|||
obj-$(CONFIG_EEPROM_AT24) += at24.o
|
||||
obj-$(CONFIG_EEPROM_AT25) += at25.o
|
||||
obj-$(CONFIG_EEPROM_LEGACY) += eeprom.o
|
||||
obj-$(CONFIG_EEPROM_MAX6875) += max6875.o
|
||||
obj-$(CONFIG_EEPROM_93CX6) += eeprom_93cx6.o
|
||||
obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o
|
||||
obj-$(CONFIG_EEPROM_SUNXI_SID) += sunxi_sid.o
|
||||
obj-$(CONFIG_EEPROM_DIGSY_MTC_CFG) += digsy_mtc_eeprom.o
|
696
drivers/misc/eeprom/at24.c
Normal file
696
drivers/misc/eeprom/at24.c
Normal file
|
@ -0,0 +1,696 @@
|
|||
/*
|
||||
* at24.c - handle most I2C EEPROMs
|
||||
*
|
||||
* Copyright (C) 2005-2007 David Brownell
|
||||
* Copyright (C) 2008 Wolfram Sang, Pengutronix
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/mod_devicetable.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_data/at24.h>
|
||||
|
||||
/*
|
||||
* I2C EEPROMs from most vendors are inexpensive and mostly interchangeable.
|
||||
* Differences between different vendor product lines (like Atmel AT24C or
|
||||
* MicroChip 24LC, etc) won't much matter for typical read/write access.
|
||||
* There are also I2C RAM chips, likewise interchangeable. One example
|
||||
* would be the PCF8570, which acts like a 24c02 EEPROM (256 bytes).
|
||||
*
|
||||
* However, misconfiguration can lose data. "Set 16-bit memory address"
|
||||
* to a part with 8-bit addressing will overwrite data. Writing with too
|
||||
* big a page size also loses data. And it's not safe to assume that the
|
||||
* conventional addresses 0x50..0x57 only hold eeproms; a PCF8563 RTC
|
||||
* uses 0x51, for just one example.
|
||||
*
|
||||
* Accordingly, explicit board-specific configuration data should be used
|
||||
* in almost all cases. (One partial exception is an SMBus used to access
|
||||
* "SPD" data for DRAM sticks. Those only use 24c02 EEPROMs.)
|
||||
*
|
||||
* So this driver uses "new style" I2C driver binding, expecting to be
|
||||
* told what devices exist. That may be in arch/X/mach-Y/board-Z.c or
|
||||
* similar kernel-resident tables; or, configuration data coming from
|
||||
* a bootloader.
|
||||
*
|
||||
* Other than binding model, current differences from "eeprom" driver are
|
||||
* that this one handles write access and isn't restricted to 24c02 devices.
|
||||
* It also handles larger devices (32 kbit and up) with two-byte addresses,
|
||||
* which won't work on pure SMBus systems.
|
||||
*/
|
||||
|
||||
struct at24_data {
|
||||
struct at24_platform_data chip;
|
||||
struct memory_accessor macc;
|
||||
int use_smbus;
|
||||
|
||||
/*
|
||||
* Lock protects against activities from other Linux tasks,
|
||||
* but not from changes by other I2C masters.
|
||||
*/
|
||||
struct mutex lock;
|
||||
struct bin_attribute bin;
|
||||
|
||||
u8 *writebuf;
|
||||
unsigned write_max;
|
||||
unsigned num_addresses;
|
||||
|
||||
/*
|
||||
* Some chips tie up multiple I2C addresses; dummy devices reserve
|
||||
* them for us, and we'll use them with SMBus calls.
|
||||
*/
|
||||
struct i2c_client *client[];
|
||||
};
|
||||
|
||||
/*
|
||||
* This parameter is to help this driver avoid blocking other drivers out
|
||||
* of I2C for potentially troublesome amounts of time. With a 100 kHz I2C
|
||||
* clock, one 256 byte read takes about 1/43 second which is excessive;
|
||||
* but the 1/170 second it takes at 400 kHz may be quite reasonable; and
|
||||
* at 1 MHz (Fm+) a 1/430 second delay could easily be invisible.
|
||||
*
|
||||
* This value is forced to be a power of two so that writes align on pages.
|
||||
*/
|
||||
static unsigned io_limit = 128;
|
||||
module_param(io_limit, uint, 0);
|
||||
MODULE_PARM_DESC(io_limit, "Maximum bytes per I/O (default 128)");
|
||||
|
||||
/*
|
||||
* Specs often allow 5 msec for a page write, sometimes 20 msec;
|
||||
* it's important to recover from write timeouts.
|
||||
*/
|
||||
static unsigned write_timeout = 25;
|
||||
module_param(write_timeout, uint, 0);
|
||||
MODULE_PARM_DESC(write_timeout, "Time (in ms) to try writes (default 25)");
|
||||
|
||||
#define AT24_SIZE_BYTELEN 5
|
||||
#define AT24_SIZE_FLAGS 8
|
||||
|
||||
#define AT24_BITMASK(x) (BIT(x) - 1)
|
||||
|
||||
/* create non-zero magic value for given eeprom parameters */
|
||||
#define AT24_DEVICE_MAGIC(_len, _flags) \
|
||||
((1 << AT24_SIZE_FLAGS | (_flags)) \
|
||||
<< AT24_SIZE_BYTELEN | ilog2(_len))
|
||||
|
||||
static const struct i2c_device_id at24_ids[] = {
|
||||
/* needs 8 addresses as A0-A2 are ignored */
|
||||
{ "24c00", AT24_DEVICE_MAGIC(128 / 8, AT24_FLAG_TAKE8ADDR) },
|
||||
/* old variants can't be handled with this generic entry! */
|
||||
{ "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) },
|
||||
{ "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) },
|
||||
/* spd is a 24c02 in memory DIMMs */
|
||||
{ "spd", AT24_DEVICE_MAGIC(2048 / 8,
|
||||
AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },
|
||||
{ "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },
|
||||
/* 24rf08 quirk is handled at i2c-core */
|
||||
{ "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },
|
||||
{ "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
|
||||
{ "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
|
||||
{ "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },
|
||||
{ "24c128", AT24_DEVICE_MAGIC(131072 / 8, AT24_FLAG_ADDR16) },
|
||||
{ "24c256", AT24_DEVICE_MAGIC(262144 / 8, AT24_FLAG_ADDR16) },
|
||||
{ "24c512", AT24_DEVICE_MAGIC(524288 / 8, AT24_FLAG_ADDR16) },
|
||||
{ "24c1024", AT24_DEVICE_MAGIC(1048576 / 8, AT24_FLAG_ADDR16) },
|
||||
{ "at24", 0 },
|
||||
{ /* END OF LIST */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, at24_ids);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* This routine supports chips which consume multiple I2C addresses. It
|
||||
* computes the addressing information to be used for a given r/w request.
|
||||
* Assumes that sanity checks for offset happened at sysfs-layer.
|
||||
*/
|
||||
static struct i2c_client *at24_translate_offset(struct at24_data *at24,
|
||||
unsigned *offset)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
if (at24->chip.flags & AT24_FLAG_ADDR16) {
|
||||
i = *offset >> 16;
|
||||
*offset &= 0xffff;
|
||||
} else {
|
||||
i = *offset >> 8;
|
||||
*offset &= 0xff;
|
||||
}
|
||||
|
||||
return at24->client[i];
|
||||
}
|
||||
|
||||
static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
|
||||
unsigned offset, size_t count)
|
||||
{
|
||||
struct i2c_msg msg[2];
|
||||
u8 msgbuf[2];
|
||||
struct i2c_client *client;
|
||||
unsigned long timeout, read_time;
|
||||
int status, i;
|
||||
|
||||
memset(msg, 0, sizeof(msg));
|
||||
|
||||
/*
|
||||
* REVISIT some multi-address chips don't rollover page reads to
|
||||
* the next slave address, so we may need to truncate the count.
|
||||
* Those chips might need another quirk flag.
|
||||
*
|
||||
* If the real hardware used four adjacent 24c02 chips and that
|
||||
* were misconfigured as one 24c08, that would be a similar effect:
|
||||
* one "eeprom" file not four, but larger reads would fail when
|
||||
* they crossed certain pages.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Slave address and byte offset derive from the offset. Always
|
||||
* set the byte address; on a multi-master board, another master
|
||||
* may have changed the chip's "current" address pointer.
|
||||
*/
|
||||
client = at24_translate_offset(at24, &offset);
|
||||
|
||||
if (count > io_limit)
|
||||
count = io_limit;
|
||||
|
||||
switch (at24->use_smbus) {
|
||||
case I2C_SMBUS_I2C_BLOCK_DATA:
|
||||
/* Smaller eeproms can work given some SMBus extension calls */
|
||||
if (count > I2C_SMBUS_BLOCK_MAX)
|
||||
count = I2C_SMBUS_BLOCK_MAX;
|
||||
break;
|
||||
case I2C_SMBUS_WORD_DATA:
|
||||
count = 2;
|
||||
break;
|
||||
case I2C_SMBUS_BYTE_DATA:
|
||||
count = 1;
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* When we have a better choice than SMBus calls, use a
|
||||
* combined I2C message. Write address; then read up to
|
||||
* io_limit data bytes. Note that read page rollover helps us
|
||||
* here (unlike writes). msgbuf is u8 and will cast to our
|
||||
* needs.
|
||||
*/
|
||||
i = 0;
|
||||
if (at24->chip.flags & AT24_FLAG_ADDR16)
|
||||
msgbuf[i++] = offset >> 8;
|
||||
msgbuf[i++] = offset;
|
||||
|
||||
msg[0].addr = client->addr;
|
||||
msg[0].buf = msgbuf;
|
||||
msg[0].len = i;
|
||||
|
||||
msg[1].addr = client->addr;
|
||||
msg[1].flags = I2C_M_RD;
|
||||
msg[1].buf = buf;
|
||||
msg[1].len = count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads fail if the previous write didn't complete yet. We may
|
||||
* loop a few times until this one succeeds, waiting at least
|
||||
* long enough for one entire page write to work.
|
||||
*/
|
||||
timeout = jiffies + msecs_to_jiffies(write_timeout);
|
||||
do {
|
||||
read_time = jiffies;
|
||||
switch (at24->use_smbus) {
|
||||
case I2C_SMBUS_I2C_BLOCK_DATA:
|
||||
status = i2c_smbus_read_i2c_block_data(client, offset,
|
||||
count, buf);
|
||||
break;
|
||||
case I2C_SMBUS_WORD_DATA:
|
||||
status = i2c_smbus_read_word_data(client, offset);
|
||||
if (status >= 0) {
|
||||
buf[0] = status & 0xff;
|
||||
buf[1] = status >> 8;
|
||||
status = count;
|
||||
}
|
||||
break;
|
||||
case I2C_SMBUS_BYTE_DATA:
|
||||
status = i2c_smbus_read_byte_data(client, offset);
|
||||
if (status >= 0) {
|
||||
buf[0] = status;
|
||||
status = count;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
status = i2c_transfer(client->adapter, msg, 2);
|
||||
if (status == 2)
|
||||
status = count;
|
||||
}
|
||||
dev_dbg(&client->dev, "read %zu@%d --> %d (%ld)\n",
|
||||
count, offset, status, jiffies);
|
||||
|
||||
if (status == count)
|
||||
return count;
|
||||
|
||||
/* REVISIT: at HZ=100, this is sloooow */
|
||||
msleep(1);
|
||||
} while (time_before(read_time, timeout));
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static ssize_t at24_read(struct at24_data *at24,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
ssize_t retval = 0;
|
||||
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
/*
|
||||
* Read data from chip, protecting against concurrent updates
|
||||
* from this host, but not from other I2C masters.
|
||||
*/
|
||||
mutex_lock(&at24->lock);
|
||||
|
||||
while (count) {
|
||||
ssize_t status;
|
||||
|
||||
status = at24_eeprom_read(at24, buf, off, count);
|
||||
if (status <= 0) {
|
||||
if (retval == 0)
|
||||
retval = status;
|
||||
break;
|
||||
}
|
||||
buf += status;
|
||||
off += status;
|
||||
count -= status;
|
||||
retval += status;
|
||||
}
|
||||
|
||||
mutex_unlock(&at24->lock);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static ssize_t at24_bin_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct at24_data *at24;
|
||||
|
||||
at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
|
||||
return at24_read(at24, buf, off, count);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Note that if the hardware write-protect pin is pulled high, the whole
|
||||
* chip is normally write protected. But there are plenty of product
|
||||
* variants here, including OTP fuses and partial chip protect.
|
||||
*
|
||||
* We only use page mode writes; the alternative is sloooow. This routine
|
||||
* writes at most one page.
|
||||
*/
|
||||
static ssize_t at24_eeprom_write(struct at24_data *at24, const char *buf,
|
||||
unsigned offset, size_t count)
|
||||
{
|
||||
struct i2c_client *client;
|
||||
struct i2c_msg msg;
|
||||
ssize_t status;
|
||||
unsigned long timeout, write_time;
|
||||
unsigned next_page;
|
||||
|
||||
/* Get corresponding I2C address and adjust offset */
|
||||
client = at24_translate_offset(at24, &offset);
|
||||
|
||||
/* write_max is at most a page */
|
||||
if (count > at24->write_max)
|
||||
count = at24->write_max;
|
||||
|
||||
/* Never roll over backwards, to the start of this page */
|
||||
next_page = roundup(offset + 1, at24->chip.page_size);
|
||||
if (offset + count > next_page)
|
||||
count = next_page - offset;
|
||||
|
||||
/* If we'll use I2C calls for I/O, set up the message */
|
||||
if (!at24->use_smbus) {
|
||||
int i = 0;
|
||||
|
||||
msg.addr = client->addr;
|
||||
msg.flags = 0;
|
||||
|
||||
/* msg.buf is u8 and casts will mask the values */
|
||||
msg.buf = at24->writebuf;
|
||||
if (at24->chip.flags & AT24_FLAG_ADDR16)
|
||||
msg.buf[i++] = offset >> 8;
|
||||
|
||||
msg.buf[i++] = offset;
|
||||
memcpy(&msg.buf[i], buf, count);
|
||||
msg.len = i + count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Writes fail if the previous one didn't complete yet. We may
|
||||
* loop a few times until this one succeeds, waiting at least
|
||||
* long enough for one entire page write to work.
|
||||
*/
|
||||
timeout = jiffies + msecs_to_jiffies(write_timeout);
|
||||
do {
|
||||
write_time = jiffies;
|
||||
if (at24->use_smbus) {
|
||||
status = i2c_smbus_write_i2c_block_data(client,
|
||||
offset, count, buf);
|
||||
if (status == 0)
|
||||
status = count;
|
||||
} else {
|
||||
status = i2c_transfer(client->adapter, &msg, 1);
|
||||
if (status == 1)
|
||||
status = count;
|
||||
}
|
||||
dev_dbg(&client->dev, "write %zu@%d --> %zd (%ld)\n",
|
||||
count, offset, status, jiffies);
|
||||
|
||||
if (status == count)
|
||||
return count;
|
||||
|
||||
/* REVISIT: at HZ=100, this is sloooow */
|
||||
msleep(1);
|
||||
} while (time_before(write_time, timeout));
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static ssize_t at24_write(struct at24_data *at24, const char *buf, loff_t off,
|
||||
size_t count)
|
||||
{
|
||||
ssize_t retval = 0;
|
||||
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
/*
|
||||
* Write data to chip, protecting against concurrent updates
|
||||
* from this host, but not from other I2C masters.
|
||||
*/
|
||||
mutex_lock(&at24->lock);
|
||||
|
||||
while (count) {
|
||||
ssize_t status;
|
||||
|
||||
status = at24_eeprom_write(at24, buf, off, count);
|
||||
if (status <= 0) {
|
||||
if (retval == 0)
|
||||
retval = status;
|
||||
break;
|
||||
}
|
||||
buf += status;
|
||||
off += status;
|
||||
count -= status;
|
||||
retval += status;
|
||||
}
|
||||
|
||||
mutex_unlock(&at24->lock);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static ssize_t at24_bin_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct at24_data *at24;
|
||||
|
||||
if (unlikely(off >= attr->size))
|
||||
return -EFBIG;
|
||||
|
||||
at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
|
||||
return at24_write(at24, buf, off, count);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
* This lets other kernel code access the eeprom data. For example, it
|
||||
* might hold a board's Ethernet address, or board-specific calibration
|
||||
* data generated on the manufacturing floor.
|
||||
*/
|
||||
|
||||
static ssize_t at24_macc_read(struct memory_accessor *macc, char *buf,
|
||||
off_t offset, size_t count)
|
||||
{
|
||||
struct at24_data *at24 = container_of(macc, struct at24_data, macc);
|
||||
|
||||
return at24_read(at24, buf, offset, count);
|
||||
}
|
||||
|
||||
static ssize_t at24_macc_write(struct memory_accessor *macc, const char *buf,
|
||||
off_t offset, size_t count)
|
||||
{
|
||||
struct at24_data *at24 = container_of(macc, struct at24_data, macc);
|
||||
|
||||
return at24_write(at24, buf, offset, count);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static void at24_get_ofdata(struct i2c_client *client,
|
||||
struct at24_platform_data *chip)
|
||||
{
|
||||
const __be32 *val;
|
||||
struct device_node *node = client->dev.of_node;
|
||||
|
||||
if (node) {
|
||||
if (of_get_property(node, "read-only", NULL))
|
||||
chip->flags |= AT24_FLAG_READONLY;
|
||||
val = of_get_property(node, "pagesize", NULL);
|
||||
if (val)
|
||||
chip->page_size = be32_to_cpup(val);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void at24_get_ofdata(struct i2c_client *client,
|
||||
struct at24_platform_data *chip)
|
||||
{ }
|
||||
#endif /* CONFIG_OF */
|
||||
|
||||
static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
{
|
||||
struct at24_platform_data chip;
|
||||
bool writable;
|
||||
int use_smbus = 0;
|
||||
struct at24_data *at24;
|
||||
int err;
|
||||
unsigned i, num_addresses;
|
||||
kernel_ulong_t magic;
|
||||
|
||||
if (client->dev.platform_data) {
|
||||
chip = *(struct at24_platform_data *)client->dev.platform_data;
|
||||
} else {
|
||||
if (!id->driver_data)
|
||||
return -ENODEV;
|
||||
|
||||
magic = id->driver_data;
|
||||
chip.byte_len = BIT(magic & AT24_BITMASK(AT24_SIZE_BYTELEN));
|
||||
magic >>= AT24_SIZE_BYTELEN;
|
||||
chip.flags = magic & AT24_BITMASK(AT24_SIZE_FLAGS);
|
||||
/*
|
||||
* This is slow, but we can't know all eeproms, so we better
|
||||
* play safe. Specifying custom eeprom-types via platform_data
|
||||
* is recommended anyhow.
|
||||
*/
|
||||
chip.page_size = 1;
|
||||
|
||||
/* update chipdata if OF is present */
|
||||
at24_get_ofdata(client, &chip);
|
||||
|
||||
chip.setup = NULL;
|
||||
chip.context = NULL;
|
||||
}
|
||||
|
||||
if (!is_power_of_2(chip.byte_len))
|
||||
dev_warn(&client->dev,
|
||||
"byte_len looks suspicious (no power of 2)!\n");
|
||||
if (!chip.page_size) {
|
||||
dev_err(&client->dev, "page_size must not be 0!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!is_power_of_2(chip.page_size))
|
||||
dev_warn(&client->dev,
|
||||
"page_size looks suspicious (no power of 2)!\n");
|
||||
|
||||
/* Use I2C operations unless we're stuck with SMBus extensions. */
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
||||
if (chip.flags & AT24_FLAG_ADDR16)
|
||||
return -EPFNOSUPPORT;
|
||||
|
||||
if (i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
|
||||
use_smbus = I2C_SMBUS_I2C_BLOCK_DATA;
|
||||
} else if (i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_READ_WORD_DATA)) {
|
||||
use_smbus = I2C_SMBUS_WORD_DATA;
|
||||
} else if (i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
|
||||
use_smbus = I2C_SMBUS_BYTE_DATA;
|
||||
} else {
|
||||
return -EPFNOSUPPORT;
|
||||
}
|
||||
}
|
||||
|
||||
if (chip.flags & AT24_FLAG_TAKE8ADDR)
|
||||
num_addresses = 8;
|
||||
else
|
||||
num_addresses = DIV_ROUND_UP(chip.byte_len,
|
||||
(chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256);
|
||||
|
||||
at24 = devm_kzalloc(&client->dev, sizeof(struct at24_data) +
|
||||
num_addresses * sizeof(struct i2c_client *), GFP_KERNEL);
|
||||
if (!at24)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&at24->lock);
|
||||
at24->use_smbus = use_smbus;
|
||||
at24->chip = chip;
|
||||
at24->num_addresses = num_addresses;
|
||||
|
||||
/*
|
||||
* Export the EEPROM bytes through sysfs, since that's convenient.
|
||||
* By default, only root should see the data (maybe passwords etc)
|
||||
*/
|
||||
sysfs_bin_attr_init(&at24->bin);
|
||||
at24->bin.attr.name = "eeprom";
|
||||
at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;
|
||||
at24->bin.read = at24_bin_read;
|
||||
at24->bin.size = chip.byte_len;
|
||||
|
||||
at24->macc.read = at24_macc_read;
|
||||
|
||||
writable = !(chip.flags & AT24_FLAG_READONLY);
|
||||
if (writable) {
|
||||
if (!use_smbus || i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)) {
|
||||
|
||||
unsigned write_max = chip.page_size;
|
||||
|
||||
at24->macc.write = at24_macc_write;
|
||||
|
||||
at24->bin.write = at24_bin_write;
|
||||
at24->bin.attr.mode |= S_IWUSR;
|
||||
|
||||
if (write_max > io_limit)
|
||||
write_max = io_limit;
|
||||
if (use_smbus && write_max > I2C_SMBUS_BLOCK_MAX)
|
||||
write_max = I2C_SMBUS_BLOCK_MAX;
|
||||
at24->write_max = write_max;
|
||||
|
||||
/* buffer (data + address at the beginning) */
|
||||
at24->writebuf = devm_kzalloc(&client->dev,
|
||||
write_max + 2, GFP_KERNEL);
|
||||
if (!at24->writebuf)
|
||||
return -ENOMEM;
|
||||
} else {
|
||||
dev_warn(&client->dev,
|
||||
"cannot write due to controller restrictions.");
|
||||
}
|
||||
}
|
||||
|
||||
at24->client[0] = client;
|
||||
|
||||
/* use dummy devices for multiple-address chips */
|
||||
for (i = 1; i < num_addresses; i++) {
|
||||
at24->client[i] = i2c_new_dummy(client->adapter,
|
||||
client->addr + i);
|
||||
if (!at24->client[i]) {
|
||||
dev_err(&client->dev, "address 0x%02x unavailable\n",
|
||||
client->addr + i);
|
||||
err = -EADDRINUSE;
|
||||
goto err_clients;
|
||||
}
|
||||
}
|
||||
|
||||
err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);
|
||||
if (err)
|
||||
goto err_clients;
|
||||
|
||||
i2c_set_clientdata(client, at24);
|
||||
|
||||
dev_info(&client->dev, "%zu byte %s EEPROM, %s, %u bytes/write\n",
|
||||
at24->bin.size, client->name,
|
||||
writable ? "writable" : "read-only", at24->write_max);
|
||||
if (use_smbus == I2C_SMBUS_WORD_DATA ||
|
||||
use_smbus == I2C_SMBUS_BYTE_DATA) {
|
||||
dev_notice(&client->dev, "Falling back to %s reads, "
|
||||
"performance will suffer\n", use_smbus ==
|
||||
I2C_SMBUS_WORD_DATA ? "word" : "byte");
|
||||
}
|
||||
|
||||
/* export data to kernel code */
|
||||
if (chip.setup)
|
||||
chip.setup(&at24->macc, chip.context);
|
||||
|
||||
return 0;
|
||||
|
||||
err_clients:
|
||||
for (i = 1; i < num_addresses; i++)
|
||||
if (at24->client[i])
|
||||
i2c_unregister_device(at24->client[i]);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int at24_remove(struct i2c_client *client)
|
||||
{
|
||||
struct at24_data *at24;
|
||||
int i;
|
||||
|
||||
at24 = i2c_get_clientdata(client);
|
||||
sysfs_remove_bin_file(&client->dev.kobj, &at24->bin);
|
||||
|
||||
for (i = 1; i < at24->num_addresses; i++)
|
||||
i2c_unregister_device(at24->client[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static struct i2c_driver at24_driver = {
|
||||
.driver = {
|
||||
.name = "at24",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = at24_probe,
|
||||
.remove = at24_remove,
|
||||
.id_table = at24_ids,
|
||||
};
|
||||
|
||||
static int __init at24_init(void)
|
||||
{
|
||||
if (!io_limit) {
|
||||
pr_err("at24: io_limit must not be 0!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
io_limit = rounddown_pow_of_two(io_limit);
|
||||
return i2c_add_driver(&at24_driver);
|
||||
}
|
||||
module_init(at24_init);
|
||||
|
||||
static void __exit at24_exit(void)
|
||||
{
|
||||
i2c_del_driver(&at24_driver);
|
||||
}
|
||||
module_exit(at24_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Driver for most I2C EEPROMs");
|
||||
MODULE_AUTHOR("David Brownell and Wolfram Sang");
|
||||
MODULE_LICENSE("GPL");
|
485
drivers/misc/eeprom/at25.c
Normal file
485
drivers/misc/eeprom/at25.c
Normal file
|
@ -0,0 +1,485 @@
|
|||
/*
|
||||
* at25.c -- support most SPI EEPROMs, such as Atmel AT25 models
|
||||
*
|
||||
* Copyright (C) 2006 David Brownell
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/eeprom.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
/*
|
||||
* NOTE: this is an *EEPROM* driver. The vagaries of product naming
|
||||
* mean that some AT25 products are EEPROMs, and others are FLASH.
|
||||
* Handle FLASH chips with the drivers/mtd/devices/m25p80.c driver,
|
||||
* not this one!
|
||||
*/
|
||||
|
||||
struct at25_data {
|
||||
struct spi_device *spi;
|
||||
struct memory_accessor mem;
|
||||
struct mutex lock;
|
||||
struct spi_eeprom chip;
|
||||
struct bin_attribute bin;
|
||||
unsigned addrlen;
|
||||
};
|
||||
|
||||
#define AT25_WREN 0x06 /* latch the write enable */
|
||||
#define AT25_WRDI 0x04 /* reset the write enable */
|
||||
#define AT25_RDSR 0x05 /* read status register */
|
||||
#define AT25_WRSR 0x01 /* write status register */
|
||||
#define AT25_READ 0x03 /* read byte(s) */
|
||||
#define AT25_WRITE 0x02 /* write byte(s)/sector */
|
||||
|
||||
#define AT25_SR_nRDY 0x01 /* nRDY = write-in-progress */
|
||||
#define AT25_SR_WEN 0x02 /* write enable (latched) */
|
||||
#define AT25_SR_BP0 0x04 /* BP for software writeprotect */
|
||||
#define AT25_SR_BP1 0x08
|
||||
#define AT25_SR_WPEN 0x80 /* writeprotect enable */
|
||||
|
||||
#define AT25_INSTR_BIT3 0x08 /* Additional address bit in instr */
|
||||
|
||||
#define EE_MAXADDRLEN 3 /* 24 bit addresses, up to 2 MBytes */
|
||||
|
||||
/* Specs often allow 5 msec for a page write, sometimes 20 msec;
|
||||
* it's important to recover from write timeouts.
|
||||
*/
|
||||
#define EE_TIMEOUT 25
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
#define io_limit PAGE_SIZE /* bytes */
|
||||
|
||||
static ssize_t
|
||||
at25_ee_read(
|
||||
struct at25_data *at25,
|
||||
char *buf,
|
||||
unsigned offset,
|
||||
size_t count
|
||||
)
|
||||
{
|
||||
u8 command[EE_MAXADDRLEN + 1];
|
||||
u8 *cp;
|
||||
ssize_t status;
|
||||
struct spi_transfer t[2];
|
||||
struct spi_message m;
|
||||
u8 instr;
|
||||
|
||||
if (unlikely(offset >= at25->bin.size))
|
||||
return 0;
|
||||
if ((offset + count) > at25->bin.size)
|
||||
count = at25->bin.size - offset;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
cp = command;
|
||||
|
||||
instr = AT25_READ;
|
||||
if (at25->chip.flags & EE_INSTR_BIT3_IS_ADDR)
|
||||
if (offset >= (1U << (at25->addrlen * 8)))
|
||||
instr |= AT25_INSTR_BIT3;
|
||||
*cp++ = instr;
|
||||
|
||||
/* 8/16/24-bit address is written MSB first */
|
||||
switch (at25->addrlen) {
|
||||
default: /* case 3 */
|
||||
*cp++ = offset >> 16;
|
||||
case 2:
|
||||
*cp++ = offset >> 8;
|
||||
case 1:
|
||||
case 0: /* can't happen: for better codegen */
|
||||
*cp++ = offset >> 0;
|
||||
}
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(t, 0, sizeof t);
|
||||
|
||||
t[0].tx_buf = command;
|
||||
t[0].len = at25->addrlen + 1;
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].rx_buf = buf;
|
||||
t[1].len = count;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
mutex_lock(&at25->lock);
|
||||
|
||||
/* Read it all at once.
|
||||
*
|
||||
* REVISIT that's potentially a problem with large chips, if
|
||||
* other devices on the bus need to be accessed regularly or
|
||||
* this chip is clocked very slowly
|
||||
*/
|
||||
status = spi_sync(at25->spi, &m);
|
||||
dev_dbg(&at25->spi->dev,
|
||||
"read %Zd bytes at %d --> %d\n",
|
||||
count, offset, (int) status);
|
||||
|
||||
mutex_unlock(&at25->lock);
|
||||
return status ? status : count;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
at25_bin_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct device *dev;
|
||||
struct at25_data *at25;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
at25 = dev_get_drvdata(dev);
|
||||
|
||||
return at25_ee_read(at25, buf, off, count);
|
||||
}
|
||||
|
||||
|
||||
static ssize_t
|
||||
at25_ee_write(struct at25_data *at25, const char *buf, loff_t off,
|
||||
size_t count)
|
||||
{
|
||||
ssize_t status = 0;
|
||||
unsigned written = 0;
|
||||
unsigned buf_size;
|
||||
u8 *bounce;
|
||||
|
||||
if (unlikely(off >= at25->bin.size))
|
||||
return -EFBIG;
|
||||
if ((off + count) > at25->bin.size)
|
||||
count = at25->bin.size - off;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
/* Temp buffer starts with command and address */
|
||||
buf_size = at25->chip.page_size;
|
||||
if (buf_size > io_limit)
|
||||
buf_size = io_limit;
|
||||
bounce = kmalloc(buf_size + at25->addrlen + 1, GFP_KERNEL);
|
||||
if (!bounce)
|
||||
return -ENOMEM;
|
||||
|
||||
/* For write, rollover is within the page ... so we write at
|
||||
* most one page, then manually roll over to the next page.
|
||||
*/
|
||||
mutex_lock(&at25->lock);
|
||||
do {
|
||||
unsigned long timeout, retries;
|
||||
unsigned segment;
|
||||
unsigned offset = (unsigned) off;
|
||||
u8 *cp = bounce;
|
||||
int sr;
|
||||
u8 instr;
|
||||
|
||||
*cp = AT25_WREN;
|
||||
status = spi_write(at25->spi, cp, 1);
|
||||
if (status < 0) {
|
||||
dev_dbg(&at25->spi->dev, "WREN --> %d\n",
|
||||
(int) status);
|
||||
break;
|
||||
}
|
||||
|
||||
instr = AT25_WRITE;
|
||||
if (at25->chip.flags & EE_INSTR_BIT3_IS_ADDR)
|
||||
if (offset >= (1U << (at25->addrlen * 8)))
|
||||
instr |= AT25_INSTR_BIT3;
|
||||
*cp++ = instr;
|
||||
|
||||
/* 8/16/24-bit address is written MSB first */
|
||||
switch (at25->addrlen) {
|
||||
default: /* case 3 */
|
||||
*cp++ = offset >> 16;
|
||||
case 2:
|
||||
*cp++ = offset >> 8;
|
||||
case 1:
|
||||
case 0: /* can't happen: for better codegen */
|
||||
*cp++ = offset >> 0;
|
||||
}
|
||||
|
||||
/* Write as much of a page as we can */
|
||||
segment = buf_size - (offset % buf_size);
|
||||
if (segment > count)
|
||||
segment = count;
|
||||
memcpy(cp, buf, segment);
|
||||
status = spi_write(at25->spi, bounce,
|
||||
segment + at25->addrlen + 1);
|
||||
dev_dbg(&at25->spi->dev,
|
||||
"write %u bytes at %u --> %d\n",
|
||||
segment, offset, (int) status);
|
||||
if (status < 0)
|
||||
break;
|
||||
|
||||
/* REVISIT this should detect (or prevent) failed writes
|
||||
* to readonly sections of the EEPROM...
|
||||
*/
|
||||
|
||||
/* Wait for non-busy status */
|
||||
timeout = jiffies + msecs_to_jiffies(EE_TIMEOUT);
|
||||
retries = 0;
|
||||
do {
|
||||
|
||||
sr = spi_w8r8(at25->spi, AT25_RDSR);
|
||||
if (sr < 0 || (sr & AT25_SR_nRDY)) {
|
||||
dev_dbg(&at25->spi->dev,
|
||||
"rdsr --> %d (%02x)\n", sr, sr);
|
||||
/* at HZ=100, this is sloooow */
|
||||
msleep(1);
|
||||
continue;
|
||||
}
|
||||
if (!(sr & AT25_SR_nRDY))
|
||||
break;
|
||||
} while (retries++ < 3 || time_before_eq(jiffies, timeout));
|
||||
|
||||
if ((sr < 0) || (sr & AT25_SR_nRDY)) {
|
||||
dev_err(&at25->spi->dev,
|
||||
"write %d bytes offset %d, "
|
||||
"timeout after %u msecs\n",
|
||||
segment, offset,
|
||||
jiffies_to_msecs(jiffies -
|
||||
(timeout - EE_TIMEOUT)));
|
||||
status = -ETIMEDOUT;
|
||||
break;
|
||||
}
|
||||
|
||||
off += segment;
|
||||
buf += segment;
|
||||
count -= segment;
|
||||
written += segment;
|
||||
|
||||
} while (count > 0);
|
||||
|
||||
mutex_unlock(&at25->lock);
|
||||
|
||||
kfree(bounce);
|
||||
return written ? written : status;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
at25_bin_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct device *dev;
|
||||
struct at25_data *at25;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
at25 = dev_get_drvdata(dev);
|
||||
|
||||
return at25_ee_write(at25, buf, off, count);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* Let in-kernel code access the eeprom data. */
|
||||
|
||||
static ssize_t at25_mem_read(struct memory_accessor *mem, char *buf,
|
||||
off_t offset, size_t count)
|
||||
{
|
||||
struct at25_data *at25 = container_of(mem, struct at25_data, mem);
|
||||
|
||||
return at25_ee_read(at25, buf, offset, count);
|
||||
}
|
||||
|
||||
static ssize_t at25_mem_write(struct memory_accessor *mem, const char *buf,
|
||||
off_t offset, size_t count)
|
||||
{
|
||||
struct at25_data *at25 = container_of(mem, struct at25_data, mem);
|
||||
|
||||
return at25_ee_write(at25, buf, offset, count);
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static int at25_np_to_chip(struct device *dev,
|
||||
struct device_node *np,
|
||||
struct spi_eeprom *chip)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
memset(chip, 0, sizeof(*chip));
|
||||
strncpy(chip->name, np->name, sizeof(chip->name));
|
||||
|
||||
if (of_property_read_u32(np, "size", &val) == 0 ||
|
||||
of_property_read_u32(np, "at25,byte-len", &val) == 0) {
|
||||
chip->byte_len = val;
|
||||
} else {
|
||||
dev_err(dev, "Error: missing \"size\" property\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (of_property_read_u32(np, "pagesize", &val) == 0 ||
|
||||
of_property_read_u32(np, "at25,page-size", &val) == 0) {
|
||||
chip->page_size = (u16)val;
|
||||
} else {
|
||||
dev_err(dev, "Error: missing \"pagesize\" property\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (of_property_read_u32(np, "at25,addr-mode", &val) == 0) {
|
||||
chip->flags = (u16)val;
|
||||
} else {
|
||||
if (of_property_read_u32(np, "address-width", &val)) {
|
||||
dev_err(dev,
|
||||
"Error: missing \"address-width\" property\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
switch (val) {
|
||||
case 8:
|
||||
chip->flags |= EE_ADDR1;
|
||||
break;
|
||||
case 16:
|
||||
chip->flags |= EE_ADDR2;
|
||||
break;
|
||||
case 24:
|
||||
chip->flags |= EE_ADDR3;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev,
|
||||
"Error: bad \"address-width\" property: %u\n",
|
||||
val);
|
||||
return -ENODEV;
|
||||
}
|
||||
if (of_find_property(np, "read-only", NULL))
|
||||
chip->flags |= EE_READONLY;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int at25_probe(struct spi_device *spi)
|
||||
{
|
||||
struct at25_data *at25 = NULL;
|
||||
struct spi_eeprom chip;
|
||||
struct device_node *np = spi->dev.of_node;
|
||||
int err;
|
||||
int sr;
|
||||
int addrlen;
|
||||
|
||||
/* Chip description */
|
||||
if (!spi->dev.platform_data) {
|
||||
if (np) {
|
||||
err = at25_np_to_chip(&spi->dev, np, &chip);
|
||||
if (err)
|
||||
return err;
|
||||
} else {
|
||||
dev_err(&spi->dev, "Error: no chip description\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
} else
|
||||
chip = *(struct spi_eeprom *)spi->dev.platform_data;
|
||||
|
||||
/* For now we only support 8/16/24 bit addressing */
|
||||
if (chip.flags & EE_ADDR1)
|
||||
addrlen = 1;
|
||||
else if (chip.flags & EE_ADDR2)
|
||||
addrlen = 2;
|
||||
else if (chip.flags & EE_ADDR3)
|
||||
addrlen = 3;
|
||||
else {
|
||||
dev_dbg(&spi->dev, "unsupported address type\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Ping the chip ... the status register is pretty portable,
|
||||
* unlike probing manufacturer IDs. We do expect that system
|
||||
* firmware didn't write it in the past few milliseconds!
|
||||
*/
|
||||
sr = spi_w8r8(spi, AT25_RDSR);
|
||||
if (sr < 0 || sr & AT25_SR_nRDY) {
|
||||
dev_dbg(&spi->dev, "rdsr --> %d (%02x)\n", sr, sr);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
at25 = devm_kzalloc(&spi->dev, sizeof(struct at25_data), GFP_KERNEL);
|
||||
if (!at25)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_init(&at25->lock);
|
||||
at25->chip = chip;
|
||||
at25->spi = spi_dev_get(spi);
|
||||
spi_set_drvdata(spi, at25);
|
||||
at25->addrlen = addrlen;
|
||||
|
||||
/* Export the EEPROM bytes through sysfs, since that's convenient.
|
||||
* And maybe to other kernel code; it might hold a board's Ethernet
|
||||
* address, or board-specific calibration data generated on the
|
||||
* manufacturing floor.
|
||||
*
|
||||
* Default to root-only access to the data; EEPROMs often hold data
|
||||
* that's sensitive for read and/or write, like ethernet addresses,
|
||||
* security codes, board-specific manufacturing calibrations, etc.
|
||||
*/
|
||||
sysfs_bin_attr_init(&at25->bin);
|
||||
at25->bin.attr.name = "eeprom";
|
||||
at25->bin.attr.mode = S_IRUSR;
|
||||
at25->bin.read = at25_bin_read;
|
||||
at25->mem.read = at25_mem_read;
|
||||
|
||||
at25->bin.size = at25->chip.byte_len;
|
||||
if (!(chip.flags & EE_READONLY)) {
|
||||
at25->bin.write = at25_bin_write;
|
||||
at25->bin.attr.mode |= S_IWUSR;
|
||||
at25->mem.write = at25_mem_write;
|
||||
}
|
||||
|
||||
err = sysfs_create_bin_file(&spi->dev.kobj, &at25->bin);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (chip.setup)
|
||||
chip.setup(&at25->mem, chip.context);
|
||||
|
||||
dev_info(&spi->dev, "%Zd %s %s eeprom%s, pagesize %u\n",
|
||||
(at25->bin.size < 1024)
|
||||
? at25->bin.size
|
||||
: (at25->bin.size / 1024),
|
||||
(at25->bin.size < 1024) ? "Byte" : "KByte",
|
||||
at25->chip.name,
|
||||
(chip.flags & EE_READONLY) ? " (readonly)" : "",
|
||||
at25->chip.page_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int at25_remove(struct spi_device *spi)
|
||||
{
|
||||
struct at25_data *at25;
|
||||
|
||||
at25 = spi_get_drvdata(spi);
|
||||
sysfs_remove_bin_file(&spi->dev.kobj, &at25->bin);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
static const struct of_device_id at25_of_match[] = {
|
||||
{ .compatible = "atmel,at25", },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, at25_of_match);
|
||||
|
||||
static struct spi_driver at25_driver = {
|
||||
.driver = {
|
||||
.name = "at25",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = at25_of_match,
|
||||
},
|
||||
.probe = at25_probe,
|
||||
.remove = at25_remove,
|
||||
};
|
||||
|
||||
module_spi_driver(at25_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Driver for most SPI EEPROMs");
|
||||
MODULE_AUTHOR("David Brownell");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("spi:at25");
|
85
drivers/misc/eeprom/digsy_mtc_eeprom.c
Normal file
85
drivers/misc/eeprom/digsy_mtc_eeprom.c
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* EEPROMs access control driver for display configuration EEPROMs
|
||||
* on DigsyMTC board.
|
||||
*
|
||||
* (C) 2011 DENX Software Engineering, Anatolij Gustschin <agust@denx.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi_gpio.h>
|
||||
#include <linux/eeprom_93xx46.h>
|
||||
|
||||
#define GPIO_EEPROM_CLK 216
|
||||
#define GPIO_EEPROM_CS 210
|
||||
#define GPIO_EEPROM_DI 217
|
||||
#define GPIO_EEPROM_DO 249
|
||||
#define GPIO_EEPROM_OE 255
|
||||
#define EE_SPI_BUS_NUM 1
|
||||
|
||||
static void digsy_mtc_op_prepare(void *p)
|
||||
{
|
||||
/* enable */
|
||||
gpio_set_value(GPIO_EEPROM_OE, 0);
|
||||
}
|
||||
|
||||
static void digsy_mtc_op_finish(void *p)
|
||||
{
|
||||
/* disable */
|
||||
gpio_set_value(GPIO_EEPROM_OE, 1);
|
||||
}
|
||||
|
||||
struct eeprom_93xx46_platform_data digsy_mtc_eeprom_data = {
|
||||
.flags = EE_ADDR8,
|
||||
.prepare = digsy_mtc_op_prepare,
|
||||
.finish = digsy_mtc_op_finish,
|
||||
};
|
||||
|
||||
static struct spi_gpio_platform_data eeprom_spi_gpio_data = {
|
||||
.sck = GPIO_EEPROM_CLK,
|
||||
.mosi = GPIO_EEPROM_DI,
|
||||
.miso = GPIO_EEPROM_DO,
|
||||
.num_chipselect = 1,
|
||||
};
|
||||
|
||||
static struct platform_device digsy_mtc_eeprom = {
|
||||
.name = "spi_gpio",
|
||||
.id = EE_SPI_BUS_NUM,
|
||||
.dev = {
|
||||
.platform_data = &eeprom_spi_gpio_data,
|
||||
},
|
||||
};
|
||||
|
||||
static struct spi_board_info digsy_mtc_eeprom_info[] __initdata = {
|
||||
{
|
||||
.modalias = "93xx46",
|
||||
.max_speed_hz = 1000000,
|
||||
.bus_num = EE_SPI_BUS_NUM,
|
||||
.chip_select = 0,
|
||||
.mode = SPI_MODE_0,
|
||||
.controller_data = (void *)GPIO_EEPROM_CS,
|
||||
.platform_data = &digsy_mtc_eeprom_data,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init digsy_mtc_eeprom_devices_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = gpio_request_one(GPIO_EEPROM_OE, GPIOF_OUT_INIT_HIGH,
|
||||
"93xx46 EEPROMs OE");
|
||||
if (ret) {
|
||||
pr_err("can't request gpio %d\n", GPIO_EEPROM_OE);
|
||||
return ret;
|
||||
}
|
||||
spi_register_board_info(digsy_mtc_eeprom_info,
|
||||
ARRAY_SIZE(digsy_mtc_eeprom_info));
|
||||
return platform_device_register(&digsy_mtc_eeprom);
|
||||
}
|
||||
device_initcall(digsy_mtc_eeprom_devices_init);
|
226
drivers/misc/eeprom/eeprom.c
Normal file
226
drivers/misc/eeprom/eeprom.c
Normal file
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* 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.
|
||||
* Copyright (C) 2004 Jean Delvare <jdelvare@suse.de>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
/* Addresses to scan */
|
||||
static const unsigned short normal_i2c[] = { 0x50, 0x51, 0x52, 0x53, 0x54,
|
||||
0x55, 0x56, 0x57, I2C_CLIENT_END };
|
||||
|
||||
|
||||
/* 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 mutex update_lock;
|
||||
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 void eeprom_update_client(struct i2c_client *client, u8 slice)
|
||||
{
|
||||
struct eeprom_data *data = i2c_get_clientdata(client);
|
||||
int i;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
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)) {
|
||||
for (i = slice << 5; i < (slice + 1) << 5; i += 32)
|
||||
if (i2c_smbus_read_i2c_block_data(client, i,
|
||||
32, data->data + i)
|
||||
!= 32)
|
||||
goto exit;
|
||||
} else {
|
||||
for (i = slice << 5; i < (slice + 1) << 5; i += 2) {
|
||||
int word = i2c_smbus_read_word_data(client, i);
|
||||
if (word < 0)
|
||||
goto exit;
|
||||
data->data[i] = word & 0xff;
|
||||
data->data[i + 1] = word >> 8;
|
||||
}
|
||||
}
|
||||
data->last_updated[slice] = jiffies;
|
||||
data->valid |= (1 << slice);
|
||||
}
|
||||
exit:
|
||||
mutex_unlock(&data->update_lock);
|
||||
}
|
||||
|
||||
static ssize_t eeprom_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
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);
|
||||
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);
|
||||
|
||||
/* 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];
|
||||
}
|
||||
} 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,
|
||||
};
|
||||
|
||||
/* Return 0 if detection is successful, -ENODEV otherwise */
|
||||
static int eeprom_detect(struct i2c_client *client, struct i2c_board_info *info)
|
||||
{
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
|
||||
/* EDID EEPROMs are often 24C00 EEPROMs, which answer to all
|
||||
addresses 0x50-0x57, but we only care about 0x50. So decline
|
||||
attaching to addresses >= 0x51 on DDC buses */
|
||||
if (!(adapter->class & I2C_CLASS_SPD) && client->addr >= 0x51)
|
||||
return -ENODEV;
|
||||
|
||||
/* There are four ways we can read the EEPROM data:
|
||||
(1) I2C block reads (faster, but unsupported by most adapters)
|
||||
(2) Word reads (128% overhead)
|
||||
(3) Consecutive byte reads (88% overhead, unsafe)
|
||||
(4) Regular byte data reads (265% overhead)
|
||||
The third and fourth methods are not implemented by this driver
|
||||
because all known adapters support one of the first two. */
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_WORD_DATA)
|
||||
&& !i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_I2C_BLOCK))
|
||||
return -ENODEV;
|
||||
|
||||
strlcpy(info->type, "eeprom", I2C_NAME_SIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int eeprom_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
struct eeprom_data *data;
|
||||
|
||||
data = devm_kzalloc(&client->dev, sizeof(struct eeprom_data),
|
||||
GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
memset(data->data, 0xff, EEPROM_SIZE);
|
||||
i2c_set_clientdata(client, data);
|
||||
mutex_init(&data->update_lock);
|
||||
data->nature = UNKNOWN;
|
||||
|
||||
/* Detect the Vaio nature of EEPROMs.
|
||||
We use the "PCG-" or "VGN-" prefix as the signature. */
|
||||
if (client->addr == 0x57
|
||||
&& i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA)) {
|
||||
char name[4];
|
||||
|
||||
name[0] = i2c_smbus_read_byte_data(client, 0x80);
|
||||
name[1] = i2c_smbus_read_byte_data(client, 0x81);
|
||||
name[2] = i2c_smbus_read_byte_data(client, 0x82);
|
||||
name[3] = i2c_smbus_read_byte_data(client, 0x83);
|
||||
|
||||
if (!memcmp(name, "PCG-", 4) || !memcmp(name, "VGN-", 4)) {
|
||||
dev_info(&client->dev, "Vaio EEPROM detected, "
|
||||
"enabling privacy protection\n");
|
||||
data->nature = VAIO;
|
||||
}
|
||||
}
|
||||
|
||||
/* create the sysfs eeprom file */
|
||||
return sysfs_create_bin_file(&client->dev.kobj, &eeprom_attr);
|
||||
}
|
||||
|
||||
static int eeprom_remove(struct i2c_client *client)
|
||||
{
|
||||
sysfs_remove_bin_file(&client->dev.kobj, &eeprom_attr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id eeprom_id[] = {
|
||||
{ "eeprom", 0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct i2c_driver eeprom_driver = {
|
||||
.driver = {
|
||||
.name = "eeprom",
|
||||
},
|
||||
.probe = eeprom_probe,
|
||||
.remove = eeprom_remove,
|
||||
.id_table = eeprom_id,
|
||||
|
||||
.class = I2C_CLASS_DDC | I2C_CLASS_SPD,
|
||||
.detect = eeprom_detect,
|
||||
.address_list = normal_i2c,
|
||||
};
|
||||
|
||||
module_i2c_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");
|
321
drivers/misc/eeprom/eeprom_93cx6.c
Normal file
321
drivers/misc/eeprom/eeprom_93cx6.c
Normal file
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
* Copyright (C) 2004 - 2006 rt2x00 SourceForge Project
|
||||
* <http://rt2x00.serialmonkey.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.
|
||||
*
|
||||
* Module: eeprom_93cx6
|
||||
* Abstract: EEPROM reader routines for 93cx6 chipsets.
|
||||
* Supported chipsets: 93c46 & 93c66.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/eeprom_93cx6.h>
|
||||
|
||||
MODULE_AUTHOR("http://rt2x00.serialmonkey.com");
|
||||
MODULE_VERSION("1.0");
|
||||
MODULE_DESCRIPTION("EEPROM 93cx6 chip driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static inline void eeprom_93cx6_pulse_high(struct eeprom_93cx6 *eeprom)
|
||||
{
|
||||
eeprom->reg_data_clock = 1;
|
||||
eeprom->register_write(eeprom);
|
||||
|
||||
/*
|
||||
* Add a short delay for the pulse to work.
|
||||
* According to the specifications the "maximum minimum"
|
||||
* time should be 450ns.
|
||||
*/
|
||||
ndelay(450);
|
||||
}
|
||||
|
||||
static inline void eeprom_93cx6_pulse_low(struct eeprom_93cx6 *eeprom)
|
||||
{
|
||||
eeprom->reg_data_clock = 0;
|
||||
eeprom->register_write(eeprom);
|
||||
|
||||
/*
|
||||
* Add a short delay for the pulse to work.
|
||||
* According to the specifications the "maximum minimum"
|
||||
* time should be 450ns.
|
||||
*/
|
||||
ndelay(450);
|
||||
}
|
||||
|
||||
static void eeprom_93cx6_startup(struct eeprom_93cx6 *eeprom)
|
||||
{
|
||||
/*
|
||||
* Clear all flags, and enable chip select.
|
||||
*/
|
||||
eeprom->register_read(eeprom);
|
||||
eeprom->reg_data_in = 0;
|
||||
eeprom->reg_data_out = 0;
|
||||
eeprom->reg_data_clock = 0;
|
||||
eeprom->reg_chip_select = 1;
|
||||
eeprom->drive_data = 1;
|
||||
eeprom->register_write(eeprom);
|
||||
|
||||
/*
|
||||
* kick a pulse.
|
||||
*/
|
||||
eeprom_93cx6_pulse_high(eeprom);
|
||||
eeprom_93cx6_pulse_low(eeprom);
|
||||
}
|
||||
|
||||
static void eeprom_93cx6_cleanup(struct eeprom_93cx6 *eeprom)
|
||||
{
|
||||
/*
|
||||
* Clear chip_select and data_in flags.
|
||||
*/
|
||||
eeprom->register_read(eeprom);
|
||||
eeprom->reg_data_in = 0;
|
||||
eeprom->reg_chip_select = 0;
|
||||
eeprom->register_write(eeprom);
|
||||
|
||||
/*
|
||||
* kick a pulse.
|
||||
*/
|
||||
eeprom_93cx6_pulse_high(eeprom);
|
||||
eeprom_93cx6_pulse_low(eeprom);
|
||||
}
|
||||
|
||||
static void eeprom_93cx6_write_bits(struct eeprom_93cx6 *eeprom,
|
||||
const u16 data, const u16 count)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
eeprom->register_read(eeprom);
|
||||
|
||||
/*
|
||||
* Clear data flags.
|
||||
*/
|
||||
eeprom->reg_data_in = 0;
|
||||
eeprom->reg_data_out = 0;
|
||||
eeprom->drive_data = 1;
|
||||
|
||||
/*
|
||||
* Start writing all bits.
|
||||
*/
|
||||
for (i = count; i > 0; i--) {
|
||||
/*
|
||||
* Check if this bit needs to be set.
|
||||
*/
|
||||
eeprom->reg_data_in = !!(data & (1 << (i - 1)));
|
||||
|
||||
/*
|
||||
* Write the bit to the eeprom register.
|
||||
*/
|
||||
eeprom->register_write(eeprom);
|
||||
|
||||
/*
|
||||
* Kick a pulse.
|
||||
*/
|
||||
eeprom_93cx6_pulse_high(eeprom);
|
||||
eeprom_93cx6_pulse_low(eeprom);
|
||||
}
|
||||
|
||||
eeprom->reg_data_in = 0;
|
||||
eeprom->register_write(eeprom);
|
||||
}
|
||||
|
||||
static void eeprom_93cx6_read_bits(struct eeprom_93cx6 *eeprom,
|
||||
u16 *data, const u16 count)
|
||||
{
|
||||
unsigned int i;
|
||||
u16 buf = 0;
|
||||
|
||||
eeprom->register_read(eeprom);
|
||||
|
||||
/*
|
||||
* Clear data flags.
|
||||
*/
|
||||
eeprom->reg_data_in = 0;
|
||||
eeprom->reg_data_out = 0;
|
||||
eeprom->drive_data = 0;
|
||||
|
||||
/*
|
||||
* Start reading all bits.
|
||||
*/
|
||||
for (i = count; i > 0; i--) {
|
||||
eeprom_93cx6_pulse_high(eeprom);
|
||||
|
||||
eeprom->register_read(eeprom);
|
||||
|
||||
/*
|
||||
* Clear data_in flag.
|
||||
*/
|
||||
eeprom->reg_data_in = 0;
|
||||
|
||||
/*
|
||||
* Read if the bit has been set.
|
||||
*/
|
||||
if (eeprom->reg_data_out)
|
||||
buf |= (1 << (i - 1));
|
||||
|
||||
eeprom_93cx6_pulse_low(eeprom);
|
||||
}
|
||||
|
||||
*data = buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* eeprom_93cx6_read - Read multiple words from eeprom
|
||||
* @eeprom: Pointer to eeprom structure
|
||||
* @word: Word index from where we should start reading
|
||||
* @data: target pointer where the information will have to be stored
|
||||
*
|
||||
* This function will read the eeprom data as host-endian word
|
||||
* into the given data pointer.
|
||||
*/
|
||||
void eeprom_93cx6_read(struct eeprom_93cx6 *eeprom, const u8 word,
|
||||
u16 *data)
|
||||
{
|
||||
u16 command;
|
||||
|
||||
/*
|
||||
* Initialize the eeprom register
|
||||
*/
|
||||
eeprom_93cx6_startup(eeprom);
|
||||
|
||||
/*
|
||||
* Select the read opcode and the word to be read.
|
||||
*/
|
||||
command = (PCI_EEPROM_READ_OPCODE << eeprom->width) | word;
|
||||
eeprom_93cx6_write_bits(eeprom, command,
|
||||
PCI_EEPROM_WIDTH_OPCODE + eeprom->width);
|
||||
|
||||
/*
|
||||
* Read the requested 16 bits.
|
||||
*/
|
||||
eeprom_93cx6_read_bits(eeprom, data, 16);
|
||||
|
||||
/*
|
||||
* Cleanup eeprom register.
|
||||
*/
|
||||
eeprom_93cx6_cleanup(eeprom);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(eeprom_93cx6_read);
|
||||
|
||||
/**
|
||||
* eeprom_93cx6_multiread - Read multiple words from eeprom
|
||||
* @eeprom: Pointer to eeprom structure
|
||||
* @word: Word index from where we should start reading
|
||||
* @data: target pointer where the information will have to be stored
|
||||
* @words: Number of words that should be read.
|
||||
*
|
||||
* This function will read all requested words from the eeprom,
|
||||
* this is done by calling eeprom_93cx6_read() multiple times.
|
||||
* But with the additional change that while the eeprom_93cx6_read
|
||||
* will return host ordered bytes, this method will return little
|
||||
* endian words.
|
||||
*/
|
||||
void eeprom_93cx6_multiread(struct eeprom_93cx6 *eeprom, const u8 word,
|
||||
__le16 *data, const u16 words)
|
||||
{
|
||||
unsigned int i;
|
||||
u16 tmp;
|
||||
|
||||
for (i = 0; i < words; i++) {
|
||||
tmp = 0;
|
||||
eeprom_93cx6_read(eeprom, word + i, &tmp);
|
||||
data[i] = cpu_to_le16(tmp);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(eeprom_93cx6_multiread);
|
||||
|
||||
/**
|
||||
* eeprom_93cx6_wren - set the write enable state
|
||||
* @eeprom: Pointer to eeprom structure
|
||||
* @enable: true to enable writes, otherwise disable writes
|
||||
*
|
||||
* Set the EEPROM write enable state to either allow or deny
|
||||
* writes depending on the @enable value.
|
||||
*/
|
||||
void eeprom_93cx6_wren(struct eeprom_93cx6 *eeprom, bool enable)
|
||||
{
|
||||
u16 command;
|
||||
|
||||
/* start the command */
|
||||
eeprom_93cx6_startup(eeprom);
|
||||
|
||||
/* create command to enable/disable */
|
||||
|
||||
command = enable ? PCI_EEPROM_EWEN_OPCODE : PCI_EEPROM_EWDS_OPCODE;
|
||||
command <<= (eeprom->width - 2);
|
||||
|
||||
eeprom_93cx6_write_bits(eeprom, command,
|
||||
PCI_EEPROM_WIDTH_OPCODE + eeprom->width);
|
||||
|
||||
eeprom_93cx6_cleanup(eeprom);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(eeprom_93cx6_wren);
|
||||
|
||||
/**
|
||||
* eeprom_93cx6_write - write data to the EEPROM
|
||||
* @eeprom: Pointer to eeprom structure
|
||||
* @addr: Address to write data to.
|
||||
* @data: The data to write to address @addr.
|
||||
*
|
||||
* Write the @data to the specified @addr in the EEPROM and
|
||||
* waiting for the device to finish writing.
|
||||
*
|
||||
* Note, since we do not expect large number of write operations
|
||||
* we delay in between parts of the operation to avoid using excessive
|
||||
* amounts of CPU time busy waiting.
|
||||
*/
|
||||
void eeprom_93cx6_write(struct eeprom_93cx6 *eeprom, u8 addr, u16 data)
|
||||
{
|
||||
int timeout = 100;
|
||||
u16 command;
|
||||
|
||||
/* start the command */
|
||||
eeprom_93cx6_startup(eeprom);
|
||||
|
||||
command = PCI_EEPROM_WRITE_OPCODE << eeprom->width;
|
||||
command |= addr;
|
||||
|
||||
/* send write command */
|
||||
eeprom_93cx6_write_bits(eeprom, command,
|
||||
PCI_EEPROM_WIDTH_OPCODE + eeprom->width);
|
||||
|
||||
/* send data */
|
||||
eeprom_93cx6_write_bits(eeprom, data, 16);
|
||||
|
||||
/* get ready to check for busy */
|
||||
eeprom->drive_data = 0;
|
||||
eeprom->reg_chip_select = 1;
|
||||
eeprom->register_write(eeprom);
|
||||
|
||||
/* wait at-least 250ns to get DO to be the busy signal */
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
/* wait for DO to go high to signify finish */
|
||||
|
||||
while (true) {
|
||||
eeprom->register_read(eeprom);
|
||||
|
||||
if (eeprom->reg_data_out)
|
||||
break;
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
if (--timeout <= 0) {
|
||||
printk(KERN_ERR "%s: timeout\n", __func__);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
eeprom_93cx6_cleanup(eeprom);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(eeprom_93cx6_write);
|
398
drivers/misc/eeprom/eeprom_93xx46.c
Normal file
398
drivers/misc/eeprom/eeprom_93xx46.c
Normal file
|
@ -0,0 +1,398 @@
|
|||
/*
|
||||
* Driver for 93xx46 EEPROMs
|
||||
*
|
||||
* (C) 2011 DENX Software Engineering, Anatolij Gustschin <agust@denx.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/eeprom_93xx46.h>
|
||||
|
||||
#define OP_START 0x4
|
||||
#define OP_WRITE (OP_START | 0x1)
|
||||
#define OP_READ (OP_START | 0x2)
|
||||
#define ADDR_EWDS 0x00
|
||||
#define ADDR_ERAL 0x20
|
||||
#define ADDR_EWEN 0x30
|
||||
|
||||
struct eeprom_93xx46_dev {
|
||||
struct spi_device *spi;
|
||||
struct eeprom_93xx46_platform_data *pdata;
|
||||
struct bin_attribute bin;
|
||||
struct mutex lock;
|
||||
int addrlen;
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
eeprom_93xx46_bin_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct eeprom_93xx46_dev *edev;
|
||||
struct device *dev;
|
||||
struct spi_message m;
|
||||
struct spi_transfer t[2];
|
||||
int bits, ret;
|
||||
u16 cmd_addr;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
edev = dev_get_drvdata(dev);
|
||||
|
||||
if (unlikely(off >= edev->bin.size))
|
||||
return 0;
|
||||
if ((off + count) > edev->bin.size)
|
||||
count = edev->bin.size - off;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
cmd_addr = OP_READ << edev->addrlen;
|
||||
|
||||
if (edev->addrlen == 7) {
|
||||
cmd_addr |= off & 0x7f;
|
||||
bits = 10;
|
||||
} else {
|
||||
cmd_addr |= off & 0x3f;
|
||||
bits = 9;
|
||||
}
|
||||
|
||||
dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n",
|
||||
cmd_addr, edev->spi->max_speed_hz);
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(t, 0, sizeof(t));
|
||||
|
||||
t[0].tx_buf = (char *)&cmd_addr;
|
||||
t[0].len = 2;
|
||||
t[0].bits_per_word = bits;
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].rx_buf = buf;
|
||||
t[1].len = count;
|
||||
t[1].bits_per_word = 8;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
mutex_lock(&edev->lock);
|
||||
|
||||
if (edev->pdata->prepare)
|
||||
edev->pdata->prepare(edev);
|
||||
|
||||
ret = spi_sync(edev->spi, &m);
|
||||
/* have to wait at least Tcsl ns */
|
||||
ndelay(250);
|
||||
if (ret) {
|
||||
dev_err(&edev->spi->dev, "read %zu bytes at %d: err. %d\n",
|
||||
count, (int)off, ret);
|
||||
}
|
||||
|
||||
if (edev->pdata->finish)
|
||||
edev->pdata->finish(edev);
|
||||
|
||||
mutex_unlock(&edev->lock);
|
||||
return ret ? : count;
|
||||
}
|
||||
|
||||
static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on)
|
||||
{
|
||||
struct spi_message m;
|
||||
struct spi_transfer t;
|
||||
int bits, ret;
|
||||
u16 cmd_addr;
|
||||
|
||||
cmd_addr = OP_START << edev->addrlen;
|
||||
if (edev->addrlen == 7) {
|
||||
cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS) << 1;
|
||||
bits = 10;
|
||||
} else {
|
||||
cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS);
|
||||
bits = 9;
|
||||
}
|
||||
|
||||
dev_dbg(&edev->spi->dev, "ew cmd 0x%04x\n", cmd_addr);
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, sizeof(t));
|
||||
|
||||
t.tx_buf = &cmd_addr;
|
||||
t.len = 2;
|
||||
t.bits_per_word = bits;
|
||||
spi_message_add_tail(&t, &m);
|
||||
|
||||
mutex_lock(&edev->lock);
|
||||
|
||||
if (edev->pdata->prepare)
|
||||
edev->pdata->prepare(edev);
|
||||
|
||||
ret = spi_sync(edev->spi, &m);
|
||||
/* have to wait at least Tcsl ns */
|
||||
ndelay(250);
|
||||
if (ret)
|
||||
dev_err(&edev->spi->dev, "erase/write %sable error %d\n",
|
||||
is_on ? "en" : "dis", ret);
|
||||
|
||||
if (edev->pdata->finish)
|
||||
edev->pdata->finish(edev);
|
||||
|
||||
mutex_unlock(&edev->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev,
|
||||
const char *buf, unsigned off)
|
||||
{
|
||||
struct spi_message m;
|
||||
struct spi_transfer t[2];
|
||||
int bits, data_len, ret;
|
||||
u16 cmd_addr;
|
||||
|
||||
cmd_addr = OP_WRITE << edev->addrlen;
|
||||
|
||||
if (edev->addrlen == 7) {
|
||||
cmd_addr |= off & 0x7f;
|
||||
bits = 10;
|
||||
data_len = 1;
|
||||
} else {
|
||||
cmd_addr |= off & 0x3f;
|
||||
bits = 9;
|
||||
data_len = 2;
|
||||
}
|
||||
|
||||
dev_dbg(&edev->spi->dev, "write cmd 0x%x\n", cmd_addr);
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(t, 0, sizeof(t));
|
||||
|
||||
t[0].tx_buf = (char *)&cmd_addr;
|
||||
t[0].len = 2;
|
||||
t[0].bits_per_word = bits;
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].tx_buf = buf;
|
||||
t[1].len = data_len;
|
||||
t[1].bits_per_word = 8;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
ret = spi_sync(edev->spi, &m);
|
||||
/* have to wait program cycle time Twc ms */
|
||||
mdelay(6);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
eeprom_93xx46_bin_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct eeprom_93xx46_dev *edev;
|
||||
struct device *dev;
|
||||
int i, ret, step = 1;
|
||||
|
||||
dev = container_of(kobj, struct device, kobj);
|
||||
edev = dev_get_drvdata(dev);
|
||||
|
||||
if (unlikely(off >= edev->bin.size))
|
||||
return -EFBIG;
|
||||
if ((off + count) > edev->bin.size)
|
||||
count = edev->bin.size - off;
|
||||
if (unlikely(!count))
|
||||
return count;
|
||||
|
||||
/* only write even number of bytes on 16-bit devices */
|
||||
if (edev->addrlen == 6) {
|
||||
step = 2;
|
||||
count &= ~1;
|
||||
}
|
||||
|
||||
/* erase/write enable */
|
||||
ret = eeprom_93xx46_ew(edev, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&edev->lock);
|
||||
|
||||
if (edev->pdata->prepare)
|
||||
edev->pdata->prepare(edev);
|
||||
|
||||
for (i = 0; i < count; i += step) {
|
||||
ret = eeprom_93xx46_write_word(edev, &buf[i], off + i);
|
||||
if (ret) {
|
||||
dev_err(&edev->spi->dev, "write failed at %d: %d\n",
|
||||
(int)off + i, ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (edev->pdata->finish)
|
||||
edev->pdata->finish(edev);
|
||||
|
||||
mutex_unlock(&edev->lock);
|
||||
|
||||
/* erase/write disable */
|
||||
eeprom_93xx46_ew(edev, 0);
|
||||
return ret ? : count;
|
||||
}
|
||||
|
||||
static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev)
|
||||
{
|
||||
struct eeprom_93xx46_platform_data *pd = edev->pdata;
|
||||
struct spi_message m;
|
||||
struct spi_transfer t;
|
||||
int bits, ret;
|
||||
u16 cmd_addr;
|
||||
|
||||
cmd_addr = OP_START << edev->addrlen;
|
||||
if (edev->addrlen == 7) {
|
||||
cmd_addr |= ADDR_ERAL << 1;
|
||||
bits = 10;
|
||||
} else {
|
||||
cmd_addr |= ADDR_ERAL;
|
||||
bits = 9;
|
||||
}
|
||||
|
||||
spi_message_init(&m);
|
||||
memset(&t, 0, sizeof(t));
|
||||
|
||||
t.tx_buf = &cmd_addr;
|
||||
t.len = 2;
|
||||
t.bits_per_word = bits;
|
||||
spi_message_add_tail(&t, &m);
|
||||
|
||||
mutex_lock(&edev->lock);
|
||||
|
||||
if (edev->pdata->prepare)
|
||||
edev->pdata->prepare(edev);
|
||||
|
||||
ret = spi_sync(edev->spi, &m);
|
||||
if (ret)
|
||||
dev_err(&edev->spi->dev, "erase error %d\n", ret);
|
||||
/* have to wait erase cycle time Tec ms */
|
||||
mdelay(6);
|
||||
|
||||
if (pd->finish)
|
||||
pd->finish(edev);
|
||||
|
||||
mutex_unlock(&edev->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t eeprom_93xx46_store_erase(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct eeprom_93xx46_dev *edev = dev_get_drvdata(dev);
|
||||
int erase = 0, ret;
|
||||
|
||||
sscanf(buf, "%d", &erase);
|
||||
if (erase) {
|
||||
ret = eeprom_93xx46_ew(edev, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = eeprom_93xx46_eral(edev);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = eeprom_93xx46_ew(edev, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR(erase, S_IWUSR, NULL, eeprom_93xx46_store_erase);
|
||||
|
||||
static int eeprom_93xx46_probe(struct spi_device *spi)
|
||||
{
|
||||
struct eeprom_93xx46_platform_data *pd;
|
||||
struct eeprom_93xx46_dev *edev;
|
||||
int err;
|
||||
|
||||
pd = spi->dev.platform_data;
|
||||
if (!pd) {
|
||||
dev_err(&spi->dev, "missing platform data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
edev = kzalloc(sizeof(*edev), GFP_KERNEL);
|
||||
if (!edev)
|
||||
return -ENOMEM;
|
||||
|
||||
if (pd->flags & EE_ADDR8)
|
||||
edev->addrlen = 7;
|
||||
else if (pd->flags & EE_ADDR16)
|
||||
edev->addrlen = 6;
|
||||
else {
|
||||
dev_err(&spi->dev, "unspecified address type\n");
|
||||
err = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mutex_init(&edev->lock);
|
||||
|
||||
edev->spi = spi_dev_get(spi);
|
||||
edev->pdata = pd;
|
||||
|
||||
sysfs_bin_attr_init(&edev->bin);
|
||||
edev->bin.attr.name = "eeprom";
|
||||
edev->bin.attr.mode = S_IRUSR;
|
||||
edev->bin.read = eeprom_93xx46_bin_read;
|
||||
edev->bin.size = 128;
|
||||
if (!(pd->flags & EE_READONLY)) {
|
||||
edev->bin.write = eeprom_93xx46_bin_write;
|
||||
edev->bin.attr.mode |= S_IWUSR;
|
||||
}
|
||||
|
||||
err = sysfs_create_bin_file(&spi->dev.kobj, &edev->bin);
|
||||
if (err)
|
||||
goto fail;
|
||||
|
||||
dev_info(&spi->dev, "%d-bit eeprom %s\n",
|
||||
(pd->flags & EE_ADDR8) ? 8 : 16,
|
||||
(pd->flags & EE_READONLY) ? "(readonly)" : "");
|
||||
|
||||
if (!(pd->flags & EE_READONLY)) {
|
||||
if (device_create_file(&spi->dev, &dev_attr_erase))
|
||||
dev_err(&spi->dev, "can't create erase interface\n");
|
||||
}
|
||||
|
||||
spi_set_drvdata(spi, edev);
|
||||
return 0;
|
||||
fail:
|
||||
kfree(edev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int eeprom_93xx46_remove(struct spi_device *spi)
|
||||
{
|
||||
struct eeprom_93xx46_dev *edev = spi_get_drvdata(spi);
|
||||
|
||||
if (!(edev->pdata->flags & EE_READONLY))
|
||||
device_remove_file(&spi->dev, &dev_attr_erase);
|
||||
|
||||
sysfs_remove_bin_file(&spi->dev.kobj, &edev->bin);
|
||||
kfree(edev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_driver eeprom_93xx46_driver = {
|
||||
.driver = {
|
||||
.name = "93xx46",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = eeprom_93xx46_probe,
|
||||
.remove = eeprom_93xx46_remove,
|
||||
};
|
||||
|
||||
module_spi_driver(eeprom_93xx46_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Driver for 93xx46 EEPROMs");
|
||||
MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>");
|
||||
MODULE_ALIAS("spi:93xx46");
|
214
drivers/misc/eeprom/max6875.c
Normal file
214
drivers/misc/eeprom/max6875.c
Normal file
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* max6875.c - driver for MAX6874/MAX6875
|
||||
*
|
||||
* Copyright (C) 2005 Ben Gardner <bgardner@wabtec.com>
|
||||
*
|
||||
* Based on eeprom.c
|
||||
*
|
||||
* The MAX6875 has a bank of registers and two banks of EEPROM.
|
||||
* Address ranges are defined as follows:
|
||||
* * 0x0000 - 0x0046 = configuration registers
|
||||
* * 0x8000 - 0x8046 = configuration EEPROM
|
||||
* * 0x8100 - 0x82FF = user EEPROM
|
||||
*
|
||||
* This driver makes the user EEPROM available for read.
|
||||
*
|
||||
* The registers & config EEPROM should be accessed via i2c-dev.
|
||||
*
|
||||
* The MAX6875 ignores the lowest address bit, so each chip responds to
|
||||
* two addresses - 0x50/0x51 and 0x52/0x53.
|
||||
*
|
||||
* Note that the MAX6875 uses i2c_smbus_write_byte_data() to set the read
|
||||
* address, so this driver is destructive if loaded for the wrong EEPROM chip.
|
||||
*
|
||||
* 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; version 2 of the License.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mutex.h>
|
||||
|
||||
/* The MAX6875 can only read/write 16 bytes at a time */
|
||||
#define SLICE_SIZE 16
|
||||
#define SLICE_BITS 4
|
||||
|
||||
/* USER EEPROM is at addresses 0x8100 - 0x82FF */
|
||||
#define USER_EEPROM_BASE 0x8100
|
||||
#define USER_EEPROM_SIZE 0x0200
|
||||
#define USER_EEPROM_SLICES 32
|
||||
|
||||
/* MAX6875 commands */
|
||||
#define MAX6875_CMD_BLK_READ 0x84
|
||||
|
||||
/* Each client has this additional data */
|
||||
struct max6875_data {
|
||||
struct i2c_client *fake_client;
|
||||
struct mutex update_lock;
|
||||
|
||||
u32 valid;
|
||||
u8 data[USER_EEPROM_SIZE];
|
||||
unsigned long last_updated[USER_EEPROM_SLICES];
|
||||
};
|
||||
|
||||
static void max6875_update_slice(struct i2c_client *client, int slice)
|
||||
{
|
||||
struct max6875_data *data = i2c_get_clientdata(client);
|
||||
int i, j, addr;
|
||||
u8 *buf;
|
||||
|
||||
if (slice >= USER_EEPROM_SLICES)
|
||||
return;
|
||||
|
||||
mutex_lock(&data->update_lock);
|
||||
|
||||
buf = &data->data[slice << SLICE_BITS];
|
||||
|
||||
if (!(data->valid & (1 << slice)) ||
|
||||
time_after(jiffies, data->last_updated[slice])) {
|
||||
|
||||
dev_dbg(&client->dev, "Starting update of slice %u\n", slice);
|
||||
|
||||
data->valid &= ~(1 << slice);
|
||||
|
||||
addr = USER_EEPROM_BASE + (slice << SLICE_BITS);
|
||||
|
||||
/* select the eeprom address */
|
||||
if (i2c_smbus_write_byte_data(client, addr >> 8, addr & 0xFF)) {
|
||||
dev_err(&client->dev, "address set failed\n");
|
||||
goto exit_up;
|
||||
}
|
||||
|
||||
if (i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_READ_I2C_BLOCK)) {
|
||||
if (i2c_smbus_read_i2c_block_data(client,
|
||||
MAX6875_CMD_BLK_READ,
|
||||
SLICE_SIZE,
|
||||
buf) != SLICE_SIZE) {
|
||||
goto exit_up;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < SLICE_SIZE; i++) {
|
||||
j = i2c_smbus_read_byte(client);
|
||||
if (j < 0) {
|
||||
goto exit_up;
|
||||
}
|
||||
buf[i] = j;
|
||||
}
|
||||
}
|
||||
data->last_updated[slice] = jiffies;
|
||||
data->valid |= (1 << slice);
|
||||
}
|
||||
exit_up:
|
||||
mutex_unlock(&data->update_lock);
|
||||
}
|
||||
|
||||
static ssize_t max6875_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t count)
|
||||
{
|
||||
struct i2c_client *client = kobj_to_i2c_client(kobj);
|
||||
struct max6875_data *data = i2c_get_clientdata(client);
|
||||
int slice, max_slice;
|
||||
|
||||
if (off > USER_EEPROM_SIZE)
|
||||
return 0;
|
||||
|
||||
if (off + count > USER_EEPROM_SIZE)
|
||||
count = USER_EEPROM_SIZE - off;
|
||||
|
||||
/* refresh slices which contain requested bytes */
|
||||
max_slice = (off + count - 1) >> SLICE_BITS;
|
||||
for (slice = (off >> SLICE_BITS); slice <= max_slice; slice++)
|
||||
max6875_update_slice(client, slice);
|
||||
|
||||
memcpy(buf, &data->data[off], count);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct bin_attribute user_eeprom_attr = {
|
||||
.attr = {
|
||||
.name = "eeprom",
|
||||
.mode = S_IRUGO,
|
||||
},
|
||||
.size = USER_EEPROM_SIZE,
|
||||
.read = max6875_read,
|
||||
};
|
||||
|
||||
static int max6875_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
struct max6875_data *data;
|
||||
int err;
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE_DATA
|
||||
| I2C_FUNC_SMBUS_READ_BYTE))
|
||||
return -ENODEV;
|
||||
|
||||
/* Only bind to even addresses */
|
||||
if (client->addr & 1)
|
||||
return -ENODEV;
|
||||
|
||||
if (!(data = kzalloc(sizeof(struct max6875_data), GFP_KERNEL)))
|
||||
return -ENOMEM;
|
||||
|
||||
/* A fake client is created on the odd address */
|
||||
data->fake_client = i2c_new_dummy(client->adapter, client->addr + 1);
|
||||
if (!data->fake_client) {
|
||||
err = -ENOMEM;
|
||||
goto exit_kfree;
|
||||
}
|
||||
|
||||
/* Init real i2c_client */
|
||||
i2c_set_clientdata(client, data);
|
||||
mutex_init(&data->update_lock);
|
||||
|
||||
err = sysfs_create_bin_file(&client->dev.kobj, &user_eeprom_attr);
|
||||
if (err)
|
||||
goto exit_remove_fake;
|
||||
|
||||
return 0;
|
||||
|
||||
exit_remove_fake:
|
||||
i2c_unregister_device(data->fake_client);
|
||||
exit_kfree:
|
||||
kfree(data);
|
||||
return err;
|
||||
}
|
||||
|
||||
static int max6875_remove(struct i2c_client *client)
|
||||
{
|
||||
struct max6875_data *data = i2c_get_clientdata(client);
|
||||
|
||||
i2c_unregister_device(data->fake_client);
|
||||
|
||||
sysfs_remove_bin_file(&client->dev.kobj, &user_eeprom_attr);
|
||||
kfree(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max6875_id[] = {
|
||||
{ "max6875", 0 },
|
||||
{ }
|
||||
};
|
||||
|
||||
static struct i2c_driver max6875_driver = {
|
||||
.driver = {
|
||||
.name = "max6875",
|
||||
},
|
||||
.probe = max6875_probe,
|
||||
.remove = max6875_remove,
|
||||
.id_table = max6875_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(max6875_driver);
|
||||
|
||||
MODULE_AUTHOR("Ben Gardner <bgardner@wabtec.com>");
|
||||
MODULE_DESCRIPTION("MAX6875 driver");
|
||||
MODULE_LICENSE("GPL");
|
157
drivers/misc/eeprom/sunxi_sid.c
Normal file
157
drivers/misc/eeprom/sunxi_sid.c
Normal file
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl>
|
||||
* http://www.linux-sunxi.org
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* This driver exposes the Allwinner security ID, efuses exported in byte-
|
||||
* sized chunks.
|
||||
*/
|
||||
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define DRV_NAME "sunxi-sid"
|
||||
|
||||
struct sunxi_sid_data {
|
||||
void __iomem *reg_base;
|
||||
unsigned int keysize;
|
||||
};
|
||||
|
||||
/* We read the entire key, due to a 32 bit read alignment requirement. Since we
|
||||
* want to return the requested byte, this results in somewhat slower code and
|
||||
* uses 4 times more reads as needed but keeps code simpler. Since the SID is
|
||||
* only very rarely probed, this is not really an issue.
|
||||
*/
|
||||
static u8 sunxi_sid_read_byte(const struct sunxi_sid_data *sid_data,
|
||||
const unsigned int offset)
|
||||
{
|
||||
u32 sid_key;
|
||||
|
||||
if (offset >= sid_data->keysize)
|
||||
return 0;
|
||||
|
||||
sid_key = ioread32be(sid_data->reg_base + round_down(offset, 4));
|
||||
sid_key >>= (offset % 4) * 8;
|
||||
|
||||
return sid_key; /* Only return the last byte */
|
||||
}
|
||||
|
||||
static ssize_t sid_read(struct file *fd, struct kobject *kobj,
|
||||
struct bin_attribute *attr, char *buf,
|
||||
loff_t pos, size_t size)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
struct sunxi_sid_data *sid_data;
|
||||
int i;
|
||||
|
||||
pdev = to_platform_device(kobj_to_dev(kobj));
|
||||
sid_data = platform_get_drvdata(pdev);
|
||||
|
||||
if (pos < 0 || pos >= sid_data->keysize)
|
||||
return 0;
|
||||
if (size > sid_data->keysize - pos)
|
||||
size = sid_data->keysize - pos;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
buf[i] = sunxi_sid_read_byte(sid_data, pos + i);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static struct bin_attribute sid_bin_attr = {
|
||||
.attr = { .name = "eeprom", .mode = S_IRUGO, },
|
||||
.read = sid_read,
|
||||
};
|
||||
|
||||
static int sunxi_sid_remove(struct platform_device *pdev)
|
||||
{
|
||||
device_remove_bin_file(&pdev->dev, &sid_bin_attr);
|
||||
dev_dbg(&pdev->dev, "driver unloaded\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id sunxi_sid_of_match[] = {
|
||||
{ .compatible = "allwinner,sun4i-a10-sid", .data = (void *)16},
|
||||
{ .compatible = "allwinner,sun7i-a20-sid", .data = (void *)512},
|
||||
{/* sentinel */},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sunxi_sid_of_match);
|
||||
|
||||
static int sunxi_sid_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct sunxi_sid_data *sid_data;
|
||||
struct resource *res;
|
||||
const struct of_device_id *of_dev_id;
|
||||
u8 *entropy;
|
||||
unsigned int i;
|
||||
|
||||
sid_data = devm_kzalloc(&pdev->dev, sizeof(struct sunxi_sid_data),
|
||||
GFP_KERNEL);
|
||||
if (!sid_data)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
sid_data->reg_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(sid_data->reg_base))
|
||||
return PTR_ERR(sid_data->reg_base);
|
||||
|
||||
of_dev_id = of_match_device(sunxi_sid_of_match, &pdev->dev);
|
||||
if (!of_dev_id)
|
||||
return -ENODEV;
|
||||
sid_data->keysize = (int)of_dev_id->data;
|
||||
|
||||
platform_set_drvdata(pdev, sid_data);
|
||||
|
||||
sid_bin_attr.size = sid_data->keysize;
|
||||
if (device_create_bin_file(&pdev->dev, &sid_bin_attr))
|
||||
return -ENODEV;
|
||||
|
||||
entropy = kzalloc(sizeof(u8) * sid_data->keysize, GFP_KERNEL);
|
||||
for (i = 0; i < sid_data->keysize; i++)
|
||||
entropy[i] = sunxi_sid_read_byte(sid_data, i);
|
||||
add_device_randomness(entropy, sid_data->keysize);
|
||||
kfree(entropy);
|
||||
|
||||
dev_dbg(&pdev->dev, "loaded\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver sunxi_sid_driver = {
|
||||
.probe = sunxi_sid_probe,
|
||||
.remove = sunxi_sid_remove,
|
||||
.driver = {
|
||||
.name = DRV_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = sunxi_sid_of_match,
|
||||
},
|
||||
};
|
||||
module_platform_driver(sunxi_sid_driver);
|
||||
|
||||
MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>");
|
||||
MODULE_DESCRIPTION("Allwinner sunxi security id driver");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Add table
Add a link
Reference in a new issue