/*
    ee24lc16.c	A kernel 2.6 driver for 24LC16 Serial EEPROMs

    Version: $Id: ee24lc16.c,v 1.6 2004/11/07 12:09:50 ttittmann Exp $
    
    Copyright (C) 2003,2004 Torsten Tittmann <Torsten.Tittmann@gmx.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.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
    Derived from

    ee24lc16.c		24LC16 Serial EEPROM I2C driver
   
    Copyright (C) 2000,2001 Rolf Siebrecht <rolf.siebrecht@t-online.de>
*/

/* For the time being only the first 256 from 2048 bytes of a	*/
/* 24LC16 Serial EEPROM can be read/written by this driver.	*/
/* (= No page switching possible.)				*/

#include <linux/delay.h>
#include <linux/i2c.h>
#include <asm/ioctl.h>

#include "videodev_priv.h"
#include "ee24lc16.h"

static int debug;		/* module load parameter */
module_param(debug, int, 0444);
MODULE_PARM_DESC(debug, "debug level (default: 0)");

#define dprintk(level,args...) \
	do { if ((debug & level)) { printk("%s: %s(): ",__stringify(KBUILD_MODNAME), __FUNCTION__); printk(args); } } while (0)

/* I2C addresses to scan */
static unsigned short int normal_i2c[] = { I2C_CLIENT_END };
static unsigned short int normal_i2c_range[] =
    { I2C_EEPROM_LOW, I2C_EEPROM_HIGH, I2C_CLIENT_END };

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

/* unique ID allocation */
static int eeprom_id = 0;

static struct i2c_driver eeprom_driver;

static int eeprom_getbyte(struct i2c_client *client, __u16 addr)
{
	int val;

	val = i2c_smbus_read_byte_data(client, addr);

	dprintk(2, "addr=0x%x; byte=0x%02x.\n", addr, val);

	if (0 != val) {
		dprintk(1, "i2c_smbus_read_byte_data() failed.\n");
	}

	return val;
}

static int eeprom_setbyte(struct i2c_client *client, __u16 addr, __u8 byt)
{
	int ret = 0;

	dprintk(2, "addr=0x%x; byte=0x%02x.\n", addr, byt);

	ret |= i2c_smbus_write_byte_data(client, addr, byt);
	mdelay(10);

	if (0 != ret) {
		dprintk(1, "i2c_smbus_write_byte_data() failed.\n");
		return -EIO;
	}

	return 0;
}

/* this function is called by i2c_probe */
static int eeprom_detect(struct i2c_adapter *adap, int addr, int kind)
{
	struct i2c_client *client;
	int ret;

	dprintk(4, "addr=0x%02x @ adapter '%s'.\n", addr, adap->name);

	/* let's see whether this adapter can support what we need */
	if (0 ==
	    i2c_check_functionality(adap,
				    I2C_FUNC_I2C | I2C_FUNC_SMBUS_READ_BYTE |
				    I2C_FUNC_SMBUS_WRITE_BYTE))
		return 0;

	/* allocate memory for client structure */
	if (NULL == (client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL)))
		return -ENOMEM;
	memset(client, 0, sizeof(struct i2c_client));

	/* fill client structure */
	client->id = eeprom_id++;
	sprintf(client->name, "Serial EEPROM(%d)", client->id);
	client->flags = 0;
	client->addr = addr;
	client->adapter = adap;
	client->driver = &eeprom_driver;

	if (0 == eeprom_id) {
		dprintk(1, "No hardware detected.\n");
	} else {
		dprintk(1, "detected at i2c address 0x%02x.\n", client->addr);
	}

	/* tell the i2c layer a new client has arrived */
	if (0 != (ret = i2c_attach_client(client))) {
		dprintk(1,
			"i2c_attach_client() failed, client @ 0x%02x not attached.\n",
			client->addr);
		/* kfree (client->data); */
		kfree(client);
		return ret;
	}

	dprintk(1, " @ 0x%02x attached to adapter '%s'.\n", client->addr,
		client->adapter->name);

	return 0;
}

static int eeprom_attach(struct i2c_adapter *adap)
{
	return i2c_probe(adap, &addr_data, &eeprom_detect);
}

static int eeprom_detach(struct i2c_client *client)
{
	int ret = 0;

	dprintk(4, "addr=0x%02x; adap='%s'.\n", client->addr,
		client->adapter->name);

	if (0 != (ret = i2c_detach_client(client))) {
		dprintk(1,
			"i2c_detach_client() failed, client @ 0x%02x not detached.\n",
			client->addr);
		return ret;
	}

	dprintk(1, "client @ 0x%02x detached from adapter '%s'.\n",
		client->addr, client->adapter->name);

	kfree(client);

	return 0;
}

static int eeprom_command(struct i2c_client *client, unsigned int cmd,
			  void *arg)
{
	int ret = 0;

	switch (cmd) {
	case GET_EEPROM_DATA:
		{
			struct eeprom_s ee;
			int val;

			memcpy(&ee, arg, sizeof(ee));

			dprintk(4,
				"GET_EEPROM_DATA (addr=0x%x; cnt=%d; *data=0x%p).\n",
				ee.addr, ee.count, ee.data);

			if ((ee.addr >= 256) || (ee.count > 256)
			    || ((ee.addr + ee.count) > 256))
				return -EINVAL;

			if (0 == ee.count) {
				val = eeprom_getbyte(client, ee.addr);
				if (val < 0)
					return val;
				*ee.data = (__u8) val;
			} else
				do {
					val = eeprom_getbyte(client, ee.addr++);
					if (val < 0)
						return val;
					*ee.data++ = (__u8) val;
				} while (--ee.count);

			return 0;
		}

	case SET_EEPROM_DATA:
		{
			struct eeprom_s ee;

			memcpy(&ee, arg, sizeof(ee));

			dprintk(4,
				"SET_EEPROM_DATA (addr=0x%x; cnt=%d; *data=0x%p).\n",
				ee.addr, ee.count, ee.data);

			if ((ee.addr >= 256) || (ee.count > 256)
			    || ((ee.addr + ee.count) > 256))
				return -EINVAL;

			if (0 == ee.count) {
				ret = eeprom_setbyte(client, ee.addr, *ee.data);
			} else {
				do {
					ret =
					    eeprom_setbyte(client, ee.addr++,
							   *ee.data++);
					if (ret < 0)
						return ret;
				} while (--ee.count);
				ret = 0;
			}
			return ret;
		}

	default:

		dprintk(4, "Unknown ioctl cmd (0x%x)\n", cmd);

		return -ENOIOCTLCMD;
	}
}

static struct i2c_driver eeprom_driver = {
	.owner = THIS_MODULE,
	.name = "ee24lc16.o",
	.id = I2C_DRIVERID_AT24Cxx,
	.flags = I2C_DF_NOTIFY,
	.attach_adapter = eeprom_attach,
	.detach_client = eeprom_detach,
	.command = eeprom_command,
};

static int __devinit eeprom_init(void)
{
	int ret;

	if (0 != (ret = i2c_add_driver(&eeprom_driver))) {
		dprintk(1, "i2c_add_driver() failed, module not inserted.\n");
		return ret;
	}

	return 0;
}

static void __devexit eeprom_exit(void)
{
	if (0 != i2c_del_driver(&eeprom_driver))
		dprintk(1, "i2c_del_driver failed.\n");
}

MODULE_AUTHOR("Torsten Tittmann <Torsten.Tittmann@gmx.de>");
MODULE_DESCRIPTION("24LC16 Serial EEPROM I2C driver");
MODULE_LICENSE("GPL");

module_init(eeprom_init);
module_exit(eeprom_exit);
