#include <linux/module.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/ioport.h>
#include <linux/sysctl.h>
#include <linux/types.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/soundcard.h>
#include <asm/uaccess.h>
#include <asm/errno.h>
#include <asm/io.h>
#include <asm/prom.h>

#include "tas_common.h"

#define CALL0(proc)								\
	do {									\
		struct tas_data_t *self;					\
		if (!tas_client || driver_hooks == NULL)			\
			return -1;						\
		self = dev_get_drvdata(&tas_client->dev);			\
		if (driver_hooks->proc)						\
			return driver_hooks->proc(self);			\
		else								\
			return -EINVAL;						\
	} while (0)

#define CALL(proc,arg...)							\
	do {									\
		struct tas_data_t *self;					\
		if (!tas_client || driver_hooks == NULL)			\
			return -1;						\
		self = dev_get_drvdata(&tas_client->dev);			\
		if (driver_hooks->proc)						\
			return driver_hooks->proc(self, ## arg);		\
		else								\
			return -EINVAL;						\
	} while (0)


static u8 tas_i2c_address = 0x34;
static struct i2c_client *tas_client;
static struct device_node* tas_node;

static int tas_attach_adapter(struct i2c_adapter *);
static int tas_detach_client(struct i2c_client *);

struct i2c_driver tas_driver = {
	.driver = {
		.name	= "tas",
	},
	.attach_adapter	= tas_attach_adapter,
	.detach_client	= tas_detach_client,
};

struct tas_driver_hooks_t *driver_hooks;

int
tas_register_driver(struct tas_driver_hooks_t *hooks)
{
	driver_hooks = hooks;
	return 0;
}

int
tas_get_mixer_level(int mixer, uint *level)
{
	CALL(get_mixer_level,mixer,level);
}

int
tas_set_mixer_level(int mixer,uint level)
{
	CALL(set_mixer_level,mixer,level);
}

int
tas_enter_sleep(void)
{
	CALL0(enter_sleep);
}

int
tas_leave_sleep(void)
{
	CALL0(leave_sleep);
}

int
tas_supported_mixers(void)
{
	CALL0(supported_mixers);
}

int
tas_mixer_is_stereo(int mixer)
{
	CALL(mixer_is_stereo,mixer);
}

int
tas_stereo_mixers(void)
{
	CALL0(stereo_mixers);
}

int
tas_output_device_change(int device_id,int layout_id,int speaker_id)
{
	CALL(output_device_change,device_id,layout_id,speaker_id);
}

int
tas_device_ioctl(u_int cmd, u_long arg)
{
	CALL(device_ioctl,cmd,arg);
}

int
tas_post_init(void)
{
	CALL0(post_init);
}

static int
tas_detect_client(struct i2c_adapter *adapter, int address)
{
	static const char *client_name = "tas Digital Equalizer";
	struct i2c_client *new_client;
	int rc = -ENODEV;

	if (!driver_hooks) {
		printk(KERN_ERR "tas_detect_client called with no hooks !\n");
		return -ENODEV;
	}
	
	new_client = kmalloc(sizeof(*new_client), GFP_KERNEL);
	if (!new_client)
		return -ENOMEM;
	memset(new_client, 0, sizeof(*new_client));

	new_client->addr = address;
	new_client->adapter = adapter;
	new_client->driver = &tas_driver;
	strlcpy(new_client->name, client_name, DEVICE_NAME_SIZE);

        if (driver_hooks->init(new_client))
		goto bail;

	/* Tell the i2c layer a new client has arrived */
	if (i2c_attach_client(new_client)) {
		driver_hooks->uninit(dev_get_drvdata(&new_client->dev));
		goto bail;
	}

	tas_client = new_client;
	return 0;
 bail:
	tas_client = NULL;
	kfree(new_client);
	return rc;
}

static int
tas_attach_adapter(struct i2c_adapter *adapter)
{
	if (!strncmp(adapter->name, "mac-io", 6))
		return tas_detect_client(adapter, tas_i2c_address);
	return 0;
}

static int
tas_detach_client(struct i2c_client *client)
{
	if (client == tas_client) {
		driver_hooks->uninit(dev_get_drvdata(&client->dev));

		i2c_detach_client(client);
		kfree(client);
	}
	return 0;
}

void
tas_cleanup(void)
{
	i2c_del_driver(&tas_driver);
}

int __init
tas_init(int driver_id, const char *driver_name)
{
	u32* paddr;

	printk(KERN_INFO "tas driver [%s])\n", driver_name);

#ifndef CONFIG_I2C_KEYWEST
	request_module("i2c-keywest");
#endif
	tas_node = find_devices("deq");
	if (tas_node == NULL)
		return -ENODEV;
	paddr = (u32 *)get_property(tas_node, "i2c-address", NULL);
	if (paddr) {
		tas_i2c_address = (*paddr) >> 1;
		printk(KERN_INFO "using i2c address: 0x%x from device-tree\n",
				tas_i2c_address);
	} else    
		printk(KERN_INFO "using i2c address: 0x%x (default)\n",
				tas_i2c_address);

	return i2c_add_driver(&tas_driver);
}
