/*
 * ee24.c
 *
 * $Id: ee24.c,v 1.1 2006/02/13 09:52:32 andrzej Exp $
 *
 * MTD driver for 24xx series I2C eeproms. 
 * Derived from the MTD test driver (mtdram.c).
 *
 * This driver supports only EEPROMs with 16-bit addresses (eg. greater 
 * than 256 byte). Tested with Microchip 24LC series chips. 
 *
 * TODO: Never tested with more than one chip!
 * LIMITATIONS: 
 * 	Assumes all chips have the same size. 
 * 	Assumes EEPROM page size of 128 bytes. 
 * 	Probes 0x50 only (see 'normal_i2c' below).
 * 	erase() implemented as no-op
 *
 * Copyright (C) 2005 Ekiert sp z o.o.
 * Author: Andrzej Ekiert <a.ekiert@ekiert.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. 
 */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/ioport.h>
#include <linux/vmalloc.h>
#include <linux/init.h>
#include <linux/mtd/compatmac.h>
#include <linux/mtd/mtd.h>
#include <linux/i2c.h>
#include <linux/list.h>
#include <linux/delay.h>

#ifdef MODULE
static unsigned long total_size = CONFIG_EE24_SIZE;
module_param(total_size, ulong, 0);
MODULE_PARM_DESC(total_size, "Total device size in bytes");
#define EE24_TOTAL_SIZE (total_size)
#else
#define EE24_TOTAL_SIZE CONFIG_EE24_SIZE
#endif //MODULE

#define EE24_PAGE_SIZE	(128)

/* Probe at 0x50 only */
static unsigned short normal_i2c[] = { 0x50, I2C_CLIENT_END };
static unsigned short normal_i2c_range[] = { I2C_CLIENT_END };

/* Magic definition of all other variables and things */
I2C_CLIENT_INSMOD;

/*
 * I2C driver data
 */
static int ee24_attach_adapter(struct i2c_adapter *adapter);
static int ee24_detach_client(struct i2c_client *client);

static struct i2c_driver ee24_driver = {
	.owner 		= THIS_MODULE,
	.name		= "ee24",
	.flags		= I2C_DF_NOTIFY,
	.attach_adapter	= ee24_attach_adapter,
	.detach_client 	= ee24_detach_client,
};

/*
 * Client data (each client gets its own)
 */
struct ee24_data {
	struct i2c_client client;
	struct list_head list;
	struct mtd_info mtd_info;
	int id;
};

/*
 * Internal variables
 */
static int ee24_id = 0;
static LIST_HEAD(ee24_clients);

/*
 * If we're to write 'count' bytes, to address 'addr', return the number 
 * of bytes that will fit within current page. 
 */
static size_t ee24_bytes_to_eop(size_t addr, size_t count)
{
	size_t to_eop = EE24_PAGE_SIZE - (addr % EE24_PAGE_SIZE);
	return (count < to_eop) ? count : to_eop;
}

/*
 * Erase implemented as no-op. There is no need to erase before write. 
 */
static int ee24_erase(struct mtd_info *mtd, struct erase_info *instr)
{
	DEBUG(MTD_DEBUG_LEVEL2, 
		"ee24_erase(pos:%ld, len:%ld)\n", 
		(long)instr->addr, (long)instr->len);

	if (instr->addr + instr->len > mtd->size) {
		DEBUG(MTD_DEBUG_LEVEL1, 
			"ee24_erase() out of bounds (%ld > %ld)\n", 
			(long)(instr->addr + instr->len), (long)mtd->size);
		return -EINVAL;
	}

	/* Do nothing */
	
	instr->state = MTD_ERASE_DONE;
	mtd_erase_callback(instr);

	return 0;
}

/*
 * Read len bytes from the eeprom to buf. 
 */
static int ee24_read(struct mtd_info *mtd, loff_t from, size_t len,
		size_t *retlen, u_char *buf)
{
	struct i2c_client *client;
	struct i2c_msg msg[2];
	uint16_t eeaddr;

	DEBUG(MTD_DEBUG_LEVEL2,
		"ee24_read(pos:%ld, len:%ld)\n", (long)from, (long)len);

	if (from + len > mtd->size) {
		DEBUG(MTD_DEBUG_LEVEL1, 
			"ee24_read() out of bounds (%ld > %ld)\n", 
			(long)(from + len), (long)mtd->size);
		return -EINVAL;
	}

	client = (struct i2c_client *)mtd->priv;
	
	/* msg[0]: write two byte address */
	eeaddr = from;
	msg[0].addr = client->addr;
	msg[0].flags = 0;
	msg[0].len = 2;
	msg[0].buf = (char*)&eeaddr;

	/* msg[1]: read 'len' bytes */
	msg[1].addr = client->addr;
	msg[1].flags = I2C_M_RD;
	msg[1].len = len;
	msg[1].buf = buf;

	if (i2c_transfer(client->adapter, &msg[0], 2)!=2) {
		*retlen = 0;
		return -EIO;
	}

	*retlen = len;
	return 0;
}

/*
 * Write len bytes from buf to the eeprom. 
 * 
 * Do not write chunks, that are not changed. We could do more, and check
 * every single byte, but that would severly affect performance (already
 * poor). 
 */
static int ee24_write(struct mtd_info *mtd, loff_t to, size_t len,
		size_t *retlen, const u_char *buf)
{
	u_char readbuf[EE24_PAGE_SIZE];
	struct i2c_client *client;
	struct i2c_msg msg[2];

	uint16_t eeaddr;
	size_t wrote, remain, chunk;

	DEBUG(MTD_DEBUG_LEVEL2, 
		"ee24_write(pos:%ld, len:%ld)\n", (long)to, (long)len);
	
	if (to + len > mtd->size) {
		DEBUG(MTD_DEBUG_LEVEL1, 
			"ee24_write() out of bounds (%ld > %ld)\n", 
			(long)(to + len), (long)mtd->size);
		return -EINVAL;
	}

	client = (struct i2c_client *)mtd->priv;
	wrote = 0;
	remain = len;
	eeaddr = to;

	/* Actually, this whole 'chunk' handling is too sophisticated 
	 * for a block device - system accesses seem to be anyway aligned to 
	 * 'erasesize', so the code below writes a single 128 byte page 
	 * at a time, always. But mtd may also be opened as a character
	 * device (recommended!), which changes everything. 
	 */
	while (remain) {
		chunk = ee24_bytes_to_eop(eeaddr, remain);
	
		/* Suppress writes, that are not required. If this device is
		 * opened as a block device, then Linux seems to write a 4096 
		 * byte block always, even if we change just a single character. 
		 * Read first, compare buffers, decide. 
		 */

		/* msg[0]: write two byte address */
		msg[0].addr = client->addr;
		msg[0].flags = 0;
		msg[0].len = 2;
		msg[0].buf = (char*)&eeaddr;

		/* msg[1]: read 'len' bytes */
		msg[1].addr = client->addr;
		msg[1].flags = I2C_M_RD;
		msg[1].len = chunk;
		msg[1].buf = readbuf;

		if (i2c_transfer(client->adapter, &msg[0], 2)!=2) {
			*retlen = wrote;
			return -EIO;
		}

		if (memcmp(readbuf, (char*)&buf[wrote], chunk)!=0) {
			DEBUG(MTD_DEBUG_LEVEL2, "Doing write\n");

			/* msg[0]: write two byte address */
			msg[0].addr = client->addr;
			msg[0].flags = 0;
			msg[0].len = 2;
			msg[0].buf = (char*)&eeaddr;

			/* msg[1]: write 'len' bytes */
			msg[1].addr = client->addr;
			msg[1].flags = I2C_M_NOSTART;
			msg[1].len = chunk;
			msg[1].buf = (char*)&buf[wrote];

			if (i2c_transfer(client->adapter, &msg[0], 2)!=2) {
				*retlen = wrote;
				return -EIO;
			}
		} else {
			DEBUG(MTD_DEBUG_LEVEL2, "Write not neccessary\n");
		}

		remain -= chunk;
		eeaddr += chunk;
		wrote += chunk;

		/* sorry about this, we should poll ACK */
		msleep(5);
	}

	*retlen=wrote;
	return 0;
}

/*
 * Detach a client from the driver. 
 * Deregister an associated MTD device. 
 */
static int ee24_detach_client(struct i2c_client *client)
{
	int err;
	struct ee24_data *data = i2c_get_clientdata(client);
	struct mtd_info *mtd = &(data->mtd_info);

	del_mtd_device(mtd);

	if ((err = i2c_detach_client(client))) {
		dev_err(&client->dev, "Client deregistration failed, "
			"client not detached.\n");
		return err;
	}

	list_del(&data->list);
	kfree(data);
	return 0;
}

/*
 * We'd init on-chip registers here, if there were any ;-)
 * Register an associated MTD device. 
 */
static int ee24_init_client(struct i2c_client *client)
{
	struct ee24_data *data = i2c_get_clientdata(client);
	struct mtd_info *mtd = &(data->mtd_info);

	memset(mtd, 0, sizeof(data->mtd_info));

	/* Setup the MTD structure */
	mtd->name = "I2C EEPROM";
	//mtd->type = MTD_PEROM;
	mtd->type = MTD_RAM;
	mtd->flags = MTD_WRITEABLE;
	mtd->size = EE24_TOTAL_SIZE;
	mtd->erasesize = EE24_PAGE_SIZE;
	mtd->priv = client;

	mtd->owner = THIS_MODULE;
	mtd->erase = ee24_erase;
	mtd->read = ee24_read;
	mtd->write = ee24_write;

	if (add_mtd_device(mtd)) {
		return -EIO;
	}
   
	return 0;
}

/*
 * Detect if a chip is there, if so register the chip. 
 * There isn't much we can to do to detect an EEPROM. 
 */
static int ee24_detect(struct i2c_adapter *adapter, int address, int kind)
{
	struct i2c_client *new_client;
	struct ee24_data *data;
	int err = 0;

	if (!i2c_check_functionality(adapter,
			I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_I2C)) {
		goto exit;
	}

	if (!(data = kmalloc(sizeof(struct ee24_data), GFP_KERNEL))) {
		err = -ENOMEM;
		goto exit;
	}
	memset(data, 0, sizeof(struct ee24_data));
	INIT_LIST_HEAD(&data->list);

	/* The common I2C client data is placed right before the
	 * ee24-specific data.
	 */
	new_client = &data->client;
	i2c_set_clientdata(new_client, data);
	new_client->addr = address;
	new_client->adapter = adapter;
	new_client->driver = &ee24_driver;
	new_client->flags = 0;

	/* Normally we'd do identification here, but 
	 * I have no idea how to do it ;-) 
	 * Assume the chip is there. 
	 */

	/* We can fill in the remaining client fields */
	strlcpy(new_client->name, "ee24", I2C_NAME_SIZE);

	/* Tell the I2C layer a new client has arrived */
	if ((err = i2c_attach_client(new_client))!=0) {
		goto exit_free;
	}

	/* Initialize the eeprom specific part */
	if ((err = ee24_init_client(new_client))!=0) {
		i2c_detach_client(new_client);
		goto exit_free;
	}

	/* Add client to local list */
	data->id = ee24_id++;
	list_add(&data->list, &ee24_clients);

	return 0;

exit_free:
	kfree(data);
exit:
	return err;
}

/*
 * Called by the I2C stack when an adapter is added. 
 */
static int ee24_attach_adapter(struct i2c_adapter *adapter)
{
	return i2c_probe(adapter, &addr_data, ee24_detect);
}

/*
 * Initialize the driver. 
 */
static int __init init_ee24(void)
{
	return i2c_add_driver(&ee24_driver);
}

/*
 * Cleanup and unregister the driver. 
 */
static void __exit cleanup_ee24(void)
{
	i2c_del_driver(&ee24_driver);
}

module_init(init_ee24);
module_exit(cleanup_ee24);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Andrzej Ekiert <a.ekiert@ekiert.com>");
MODULE_DESCRIPTION("24xx EEPROM driver");


