/*
    drp3510II.c		A kernel 2.6 driver for Micronas Intermetall DRP3510A 
			Digital Radio Processor

    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
 
    drp3510II.c  Micronas Intermetall DRP3510A I2C driver

    Copyright (C) 2000,2001 Rolf Siebrecht <rolf.siebrecht@t-online.de>
 */

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

#include "drp3510II.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_DRP3510, I2C_CLIENT_END };
static unsigned short int normal_i2c_range[] = { I2C_CLIENT_END };

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

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

static struct i2c_driver drp_driver;

/* write to a DRP3510 data register */
static int drp3510_write_data(struct i2c_client *client, unsigned int reg,
			      unsigned long data)
{
	unsigned char buf[5] = {
		0x68,		/* b[0] r/w cmd = "write" */
		0x90 | ((reg >> 4) & 0x0f),	/* b[1] 0x9 r1  */
		((reg << 4) & 0xf0) | (data & 0x0f),	/* b[2] r0  d0  */
		(data >> 12) & 0xff,	/* b[3] d4  d3  */
		(data >> 4) & 0xff	/* b[4] d2  d1  */
	};
	int count = sizeof(buf);

	dprintk(2, "reg=0x%x, data=0x%lx.\n", reg, data);

	if (count != i2c_master_send(client, buf, count)) {
		dprintk(1, "i2c_master_send() failed.\n");
		return -EIO;
	}

	return 0;
}

#if 0
/* read from a DRP3510 data register */
static int drp3510_read_data(struct i2c_client *client, u16 reg)
{
	int ret = 0;
	u32 val = 0;
	u8 addr_buf[3] = {
		0x68,		/* b[0] r/w cmd = "write" */
		0xd0 | ((reg >> 4) & 0x0f),	/* b[1] 0xd r1  */
		(reg << 4) & 0xf0	/* b[2] r0  0   */
	};
	int addr_count = sizeof(addr_buf);
	u8 send_buf[1] = {
		0x69		/* b[0] r/w cmd = "read" */
	};
	u8 recv_buf[4];
	struct i2c_msg transfer_msgs[] = {
		{
		 client->addr,
		 client->flags,
		 sizeof(send_buf),
		 send_buf}
		,
		{
		 client->addr,
		 client->flags | I2C_M_RD,
		 sizeof(recv_buf),
		 recv_buf}
	};
	int num_of_msgs = sizeof(transfer_msgs) / sizeof(transfer_msgs[0]);

	dprintk(2, "reg=0x%04x.\n", reg);

	ret |= (addr_count != i2c_master_send(client, addr_buf, addr_count));	/* send register address */
	ret |=
	    (num_of_msgs !=
	     i2c_transfer(client->adapter, transfer_msgs, num_of_msgs));

	val =
	    (((u32) recv_buf[3] << 16) & 0xf0000) | (((u32) recv_buf[0] << 8) &
						     0xff00) | recv_buf[1];

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

	return val;
}
#endif

/* write to DRP3510 control register */
static int drp3510_write_control(struct i2c_client *client, u16 data)
{
	int ret;

	dprintk(2, "data=0x%x.\n", data);

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

	return 0;
}

/* read DRP3510 status + index */
static int drp3510_read_statusindex(struct i2c_client *client)
{
	int ret;

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

	dprintk(2, "data=0x%04x.\n", ret);

	return ret;
}

/* read DRP3510 ADR data field */
static u8 *drp3510_read_adrdata(struct i2c_client *client, u8 offset, u8 count)
{
	struct drp3510 *decoder = i2c_get_clientdata(client);
	int ret = 0;
	u8 addr_buf[3] = {
		0x68,		/* b[0] r/w cmd = "write"       */
		0x60,		/* b[1] 0x60    */
		0		/* b[2] later: offset count     */
	};
	int addr_count = sizeof(addr_buf);
	u8 send_buf[1] = {
		0x69		/* b[0] r/w cmd = "read"        */
	};
	struct i2c_msg transfer_msgs[] = {
		{client->addr,
		 client->flags,
		 sizeof(send_buf),
		 send_buf}
		,
		{
		 client->addr,
		 client->flags | I2C_M_RD,
		 sizeof(decoder->adrdata),	/* preliminary */
		 decoder->adrdata}
	};
	int num_of_msgs = sizeof(transfer_msgs) / sizeof(transfer_msgs[0]);

	if (offset > 9)
		offset = 9;
	if (count > 10)
		count = 10;
	if (count == 0)
		count = 1;

	dprintk(2, "ofs=%d; cnt=%d.\n", offset, count);

	addr_buf[2] = (offset << 4) | (count & 0x0f);
	transfer_msgs[1].len = count << 1;

	ret |= (addr_count != i2c_master_send(client, addr_buf, addr_count));

	dprintk(2, "i2c_master_send %s!\n", ret ? "error" : "ok");

	ret |=
	    (num_of_msgs !=
	     i2c_transfer(client->adapter, transfer_msgs, num_of_msgs));

	dprintk(2, "i2c_transfer %s!\n", ret ? "error" : "ok");

	if (0 != ret) {
		dprintk(1, "i2c_transfer() failed.\n");
		/* return -EIO;         can't return an error code here */
	}

	return decoder->adrdata;
}

static int drp3510_chip_init(struct i2c_client *client)
{
	struct drp3510 *decoder = i2c_get_clientdata(client);

	dprintk(4, ".\n");

	/* FIXME!! Initialize DRP3510 chip here as far as needed */

	memset(decoder, 0, sizeof(struct drp3510));

	decoder->adapter = client->adapter;
	/* FIXME!! set more variables of struct drp to initial values */

	return 0;
}

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

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

	/* let's see whether this adapter can support what we need */
	/* doesn't work yet!
	   if (i2c_check_functionality (adap, I2C_FUNC_I2C | I2C_FUNC_SMBUS_WORD_DATA) == 0) {
	   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));

	/* allocate memory for drp structure */
	if (NULL == (decoder = kmalloc(sizeof(struct drp3510), GFP_KERNEL))) {
		kfree(client);
		return -ENOMEM;
	}
	memset(decoder, 0, sizeof(struct drp3510));

	/* fill client structure */
	client->id = drp3510_id++;
	sprintf(client->name, "Micronas DRP3510(%d)", client->id);
	client->flags = 0;
	client->addr = addr;
	client->adapter = adap;
	client->driver = &drp_driver;
	i2c_set_clientdata(client, decoder);

	if (0 == drp3510_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(decoder);
		kfree(client);
		return ret;
	}

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

	/* DRP3510 chip init */
	if (0 != (ret = drp3510_chip_init(client))) {
		dprintk(1, "init @ 0x%02x failed.\n", client->addr);
		return ret;
	}

	return 0;
}

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

static int drp3510_detach(struct i2c_client *client)
{
	struct drp3510 *decoder = i2c_get_clientdata(client);
	int ret;

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

	/* FIXME!! disable ADR, mute ADR output, disable PIO DMA mode,... */

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

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

	kfree(decoder);
	kfree(client);

	return 0;
}

static int drp3510_command(struct i2c_client *client, unsigned int cmd,
			   void *arg)
{
	/* struct drp3510 *drp = (struct drp3510 *) client->data; */

	u16 *sarg = arg;	/* status pointer */
	u8 **darg = arg;	/* data field pointer */
	int ret = 0;

	switch (cmd) {
	case DRP_ADR:
		ret |= drp3510_write_control(client, 0x0100);	/* reset drp */
		ret |= drp3510_write_control(client, 0x0000);	/* release drp reset */

		ret |= drp3510_write_data(client, 0xA8, 0x00000200);
		ret |= drp3510_write_data(client, 0x67, 0x00008000);
		ret |= drp3510_write_data(client, 0x60, 0x00070000);
		ret |= drp3510_write_data(client, 0xF4, 0x00000000);

		dprintk(4, "DRP_ADR\n");

		if (0 != ret)
			return -EIO;

		return 0;

	case DRP_PIO_DMA:

		dprintk(4, "DRP_PIO_DMA\n");

		return drp3510_write_data(client, 0x60, 0x00010800);

	case DRP_STATUS:
		*sarg = drp3510_read_statusindex(client);

		dprintk(4, "DRP_STATUS\n");

		return ret;

	case DRP_ADR_DATA:
		*darg = drp3510_read_adrdata(client, 0, 10);	/* no offset, 10 words -> 20 bytes */

		dprintk(4, "DRP_ADR_DATA (address: 0x%p)\n", darg);

		return ret;

	default:
		dprintk(4, "Unknown ioctl cmd (0x%x)\n", cmd);
		return -ENOIOCTLCMD;
	}
}

static struct i2c_driver drp_driver = {
	.owner = THIS_MODULE,
	.name = "drp3510II",
	.id = I2C_DRIVERID_DRP3510,
	.flags = I2C_DF_NOTIFY,
	.attach_adapter = drp3510_attach,
	.detach_client = drp3510_detach,
	.command = drp3510_command,
};

static int __init drp3510_init(void)
{
	int ret;

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

	return 0;
}

static void __exit drp3510_exit(void)
{
	if (0 != i2c_del_driver(&drp_driver))
		dprintk(1, "i2c_del_driver() failed\n");
}

MODULE_AUTHOR("Torsten Tittmann <Torsten.Tittmann@gmx.de>");
MODULE_DESCRIPTION("Micronas Intermetall DRP3510A I2C driver");
MODULE_LICENSE("GPL");

module_init(drp3510_init);
module_exit(drp3510_exit);
