/*
 *   Generic i2c interface for linux
 *
 *   (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de>
 *   Modified for the ALSA driver by Jaroslav Kysela <perex@suse.cz>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#define SND_MAIN_OBJECT_FILE
#include "driver.h"
#include "i2c.h"

#define SND_REGPRINT(x)	if (verbose) (x)
#define SND_I2C_DEBUG(x)  if (snd_i2c_debug) (x)

static int scan = 0;
static int verbose = 1;
static int snd_i2c_debug = 0;
#ifdef LINUX_2_1
MODULE_PARM(scan, "i");
MODULE_PARM(verbose, "i");
MODULE_PARM(snd_i2c_debug, "i");
#endif

/* ----------------------------------------------------------------------- */

static struct snd_i2c_bus *busses[SND_I2C_BUS_MAX];
static struct snd_i2c_driver *drivers[SND_I2C_DRIVER_MAX];
static int bus_count = 0, driver_count = 0;

int snd_i2c_init(void)
{
#if 0
	printk(KERN_INFO "i2c: initialized%s\n",
	       scan ? " (i2c bus scan enabled)" : "");
#endif
	/* anything to do here ? */
	return 0;
}

/* ----------------------------------------------------------------------- */

static void snd_i2c_attach_device(struct snd_i2c_bus *bus, struct snd_i2c_driver *driver)
{
	unsigned long flags;
	struct snd_i2c_device *device;
	int i, j, ack = 1;
	unsigned char addr;

	/* probe for device */
	SND_LOCK_I2C_BUS(bus);
	for (addr = driver->addr_l; addr <= driver->addr_h; addr += 2) {
		snd_i2c_start(bus);
		ack = snd_i2c_sendbyte(bus, addr, 0);
		snd_i2c_stop(bus);
		if (!ack)
			break;
	}
	SND_UNLOCK_I2C_BUS(bus);
	if (ack)
		return;

	/* got answer */
	for (i = 0; i < SND_I2C_DEVICE_MAX; i++)
		if (NULL == driver->devices[i])
			break;
	if (SND_I2C_DEVICE_MAX == i)
		return;

	for (j = 0; j < SND_I2C_DEVICE_MAX; j++)
		if (NULL == bus->devices[j])
			break;
	if (SND_I2C_DEVICE_MAX == j)
		return;

	if (NULL == (device = snd_malloc(sizeof(struct snd_i2c_device))))
		 return;
	device->bus = bus;
	device->driver = driver;
	device->addr = addr;

	/* attach */
	if (0 != driver->attach(device)) {
		snd_free(device, sizeof(struct snd_i2c_device));
		return;
	}
	driver->devices[i] = device;
	driver->devcount++;
	bus->devices[j] = device;
	bus->devcount++;

	if (bus->attach_inform)
		bus->attach_inform(bus, driver->id);
	SND_REGPRINT(printk("i2c: device attached: %s (addr=0x%02x, bus=%s, driver=%s)\n", device->name, addr, bus->name, driver->name));
}

static void snd_i2c_detach_device(struct snd_i2c_device *device)
{
	int i;

	if (device->bus->detach_inform)
		device->bus->detach_inform(device->bus, device->driver->id);
	device->driver->detach(device);

	for (i = 0; i < SND_I2C_DEVICE_MAX; i++)
		if (device == device->driver->devices[i])
			break;
	if (SND_I2C_DEVICE_MAX == i) {
		printk(KERN_WARNING "i2c: detach_device #1: device not found: %s\n",
		       device->name);
		return;
	}
	device->driver->devices[i] = NULL;
	device->driver->devcount--;

	for (i = 0; i < SND_I2C_DEVICE_MAX; i++)
		if (device == device->bus->devices[i])
			break;
	if (SND_I2C_DEVICE_MAX == i) {
		printk(KERN_WARNING "i2c: detach_device #2: device not found: %s\n",
		       device->name);
		return;
	}
	device->bus->devices[i] = NULL;
	device->bus->devcount--;

	SND_REGPRINT(printk("i2c: device detached: %s (addr=0x%02x, bus=%s, driver=%s)\n", device->name, device->addr, device->bus->name, device->driver->name));
	snd_free(device, sizeof(struct snd_i2c_device));
}
 
/* ----------------------------------------------------------------------- */

struct snd_i2c_bus *snd_i2c_bus_new(int id, char *name)
{
	struct snd_i2c_bus *bus;
	
	bus = (struct snd_i2c_bus *)snd_calloc(sizeof(*bus));
	if (bus == NULL)
		return NULL;
	bus->id = id;
	strncpy(bus->name, name, sizeof(bus->name) - 1);
	return bus;
}

/* ----------------------------------------------------------------------- */

int snd_i2c_bus_free(struct snd_i2c_bus *bus)
{
	if (bus == NULL)
		return -EINVAL;
	if (bus->private_free)
		bus->private_free(bus->private_data);
	snd_free(bus, sizeof(*bus));
	return 0;
}

/* ----------------------------------------------------------------------- */

int snd_i2c_register_bus(struct snd_i2c_bus *bus)
{
	unsigned long flags;
	int i, ack;

	memset(bus->devices, 0, sizeof(bus->devices));
	bus->devcount = 0;

	for (i = 0; i < SND_I2C_BUS_MAX; i++)
		if (NULL == busses[i])
			break;
	if (SND_I2C_BUS_MAX == i)
		return -ENOMEM;

	busses[i] = bus;
	bus_count++;
	SND_REGPRINT(printk("i2c: bus registered: %s\n", bus->name));

	SND_LOCK_I2C_BUS(bus);
	snd_i2c_reset(bus);
	if (scan) {
		/* scan whole i2c bus */
		for (i = 0; i < 256; i += 2) {
			snd_i2c_start(bus);
			ack = snd_i2c_sendbyte(bus, i, 0);
			snd_i2c_stop(bus);
			if (!ack) {
				printk("i2c: scanning bus %s: found device at addr=0x%02x\n",
				       bus->name, i);
			}
		}
	}
	SND_UNLOCK_I2C_BUS(bus);

	/* probe available drivers */
	for (i = 0; i < SND_I2C_DRIVER_MAX; i++)
		if (drivers[i])
			snd_i2c_attach_device(bus, drivers[i]);

	return 0;
}

int snd_i2c_unregister_bus(struct snd_i2c_bus *bus)
{
	int i;

	/* detach devices */
	for (i = 0; i < SND_I2C_DEVICE_MAX; i++)
		if (bus->devices[i])
			snd_i2c_detach_device(bus->devices[i]);

	for (i = 0; i < SND_I2C_BUS_MAX; i++)
		if (bus == busses[i])
			break;
	if (SND_I2C_BUS_MAX == i) {
		printk(KERN_WARNING "i2c: unregister_bus #1: bus not found: %s\n",
		       bus->name);
		return -ENODEV;
	}
	busses[i] = NULL;
	bus_count--;
	SND_REGPRINT(printk("i2c: bus unregistered: %s\n", bus->name));

	return 0;
}

/* ----------------------------------------------------------------------- */

int snd_i2c_register_driver(struct snd_i2c_driver *driver)
{
	int i;

	memset(driver->devices, 0, sizeof(driver->devices));
	driver->devcount = 0;

	for (i = 0; i < SND_I2C_DRIVER_MAX; i++)
		if (NULL == drivers[i])
			break;
	if (SND_I2C_DRIVER_MAX == i)
		return -ENOMEM;

	drivers[i] = driver;
	driver_count++;
	SND_REGPRINT(printk("i2c: driver registered: %s\n", driver->name));

	/* probe available busses */
	for (i = 0; i < SND_I2C_BUS_MAX; i++)
		if (busses[i])
			snd_i2c_attach_device(busses[i], driver);

	return 0;
}

int snd_i2c_unregister_driver(struct snd_i2c_driver *driver)
{
	int i;

	/* detach devices */
	for (i = 0; i < SND_I2C_DEVICE_MAX; i++)
		if (driver->devices[i])
			snd_i2c_detach_device(driver->devices[i]);

	for (i = 0; i < SND_I2C_DRIVER_MAX; i++)
		if (driver == drivers[i])
			break;
	if (SND_I2C_DRIVER_MAX == i) {
		printk(KERN_WARNING "i2c: unregister_driver: driver not found: %s\n",
		       driver->name);
		return -ENODEV;
	}
	drivers[i] = NULL;
	driver_count--;
	SND_REGPRINT(printk("i2c: driver unregistered: %s\n", driver->name));

	return 0;
}

/* ----------------------------------------------------------------------- */

int snd_i2c_control_device(struct snd_i2c_bus *bus, int id,
			   unsigned int cmd, void *arg)
{
	int i;

	for (i = 0; i < SND_I2C_DEVICE_MAX; i++)
		if (bus->devices[i] && bus->devices[i]->driver->id == id)
			break;
	if (i == SND_I2C_DEVICE_MAX)
		return -ENODEV;
	if (NULL == bus->devices[i]->driver->command)
		return -ENODEV;
	return bus->devices[i]->driver->command(bus->devices[i], cmd, arg);
}

/* ----------------------------------------------------------------------- */

#define SND_I2C_SET(bus,ctrl,data)  (bus->i2c_setlines(bus,ctrl,data))
#define SND_I2C_GET(bus)            (bus->i2c_getdataline(bus))

void snd_i2c_reset(struct snd_i2c_bus *bus)
{
	SND_I2C_SET(bus, 1, 1);
	SND_I2C_DEBUG(printk("%s: bus reset", bus->name));
}

void snd_i2c_start(struct snd_i2c_bus *bus)
{
	SND_I2C_SET(bus, 0, 1);
	SND_I2C_SET(bus, 1, 1);
	SND_I2C_SET(bus, 1, 0);
	SND_I2C_SET(bus, 0, 0);
	SND_I2C_DEBUG(printk("%s: < ", bus->name));
}

void snd_i2c_stop(struct snd_i2c_bus *bus)
{
	SND_I2C_SET(bus, 0, 0);
	SND_I2C_SET(bus, 1, 0);
	SND_I2C_SET(bus, 1, 1);
	SND_I2C_DEBUG(printk(">\n"));
}

void snd_i2c_one(struct snd_i2c_bus *bus)
{
	SND_I2C_SET(bus, 0, 1);
	SND_I2C_SET(bus, 1, 1);
	SND_I2C_SET(bus, 0, 1);
}

void snd_i2c_zero(struct snd_i2c_bus *bus)
{
	SND_I2C_SET(bus, 0, 0);
	SND_I2C_SET(bus, 1, 0);
	SND_I2C_SET(bus, 0, 0);
}

int snd_i2c_ack(struct snd_i2c_bus *bus)
{
	int ack;

	SND_I2C_SET(bus, 0, 1);
	SND_I2C_SET(bus, 1, 1);
	ack = SND_I2C_GET(bus);
	SND_I2C_SET(bus, 0, 1);
	return ack;
}

int snd_i2c_sendbyte(struct snd_i2c_bus *bus, unsigned char data, int wait_for_ack)
{
	int i, ack;

	SND_I2C_SET(bus, 0, 0);
	for (i = 7; i >= 0; i--)
		(data & (1 << i)) ? snd_i2c_one(bus) : snd_i2c_zero(bus);
	if (wait_for_ack)
		snd_delay((wait_for_ack + 9) / 10);
	ack = snd_i2c_ack(bus);
	SND_I2C_DEBUG(printk("%02x%c ", (int) data, ack ? '-' : '+'));
	return ack;
}

unsigned char snd_i2c_readbyte(struct snd_i2c_bus *bus, int last)
{
	int i;
	unsigned char data = 0;

	SND_I2C_SET(bus, 0, 1);
	for (i = 7; i >= 0; i--) {
		SND_I2C_SET(bus, 1, 1);
		if (SND_I2C_GET(bus))
			data |= (1 << i);
		SND_I2C_SET(bus, 0, 1);
	}
	last ? snd_i2c_one(bus) : snd_i2c_zero(bus);
	SND_I2C_DEBUG(printk("=%02x%c ", (int) data, last ? '-' : '+'));
	return data;
}

/* ----------------------------------------------------------------------- */

int snd_i2c_read(struct snd_i2c_bus *bus, unsigned char addr)
{
	int ret;

	if (bus->i2c_read)
		return bus->i2c_read(bus, addr);

	snd_i2c_start(bus);
	snd_i2c_sendbyte(bus, addr, 0);
	ret = snd_i2c_readbyte(bus, 1);
	snd_i2c_stop(bus);
	return ret;
}

int snd_i2c_write(struct snd_i2c_bus *bus, unsigned char addr,
		  unsigned char data1, unsigned char data2, int both)
{
	int ack;

	if (bus->i2c_write)
		return bus->i2c_write(bus, addr, data1, data2, both);

	snd_i2c_start(bus);
	snd_i2c_sendbyte(bus, addr, 0);
	ack = snd_i2c_sendbyte(bus, data1, 0);
	if (both)
		ack = snd_i2c_sendbyte(bus, data2, 0);
	snd_i2c_stop(bus);
	return ack ? -1 : 0;
}

/* ----------------------------------------------------------------------- */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_i2c_export;
#endif

int init_module(void)
{
#ifndef LINUX_2_1
	if (register_symtab(&snd_symbol_table_i2c_export) < 0)
		return -ENOMEM;
#endif
	return snd_i2c_init();
}

void cleanup_module(void)
{
}
