/*
    sp5055II.c		A kernel 2.6 driver for MITEL SP5055
    			2.6GHz Bidirectional I2C BUS Controlled Synthesizer

    Version: $Id: sp5055II.c,v 1.6 2004/11/07 12:09:51 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
   
    sp5055II.c		MITEL SP5055 I2C driver
 
    Copyright (C) 2000,2001 Rolf Siebrecht <rolf.siebrecht@t-online.de>
 */

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

#include "sp5055II.h"

static int debug;		// module load parameter
static int foffset;
module_param(debug, int, 0444);
MODULE_PARM_DESC(debug, "debug level (default: 0)");
module_param(foffset, int, 0444);
MODULE_PARM_DESC(foffset,
		 "shift of receiving frequency (default:0, meaningful range: ca. +/- 50)");

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

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

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

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

static struct i2c_driver sp5055_driver;

/* sp5055_setfreq: set tuner osc freq by sending four bytes to SP5055 */
static int sp5055_setfreq(struct i2c_client *client, unsigned long int freq)
{
	struct sp5055 *decoder = i2c_get_clientdata(client);
	unsigned long int div;
	unsigned char b[4];
	int ret;

	/* Compute PLL divider: */
	/* - freq parameter is tv channel frequency in units of Hz */
	/* - Tuner IF is 480 MHz */
	div = (freq / 125000) + 3840 + foffset;

	b[0] = (unsigned char)((div >> 8) & 0x7f);
	b[1] = (unsigned char)(div & 0xff);
	/* choose high charge pump current in case of "big" frequency difference */
	b[2] = decoder->control0;
	b[2] |= ((abs(div - decoder->divider) > 1600) ? SP5055_CTRL0_CP : 0);
	b[3] = decoder->control1;

	dprintk(2,
		"f=%ld; div=%ld; b0=0x%02x, b1=0x%02x, b2=0x%02x, b3=0x%02x.\n",
		freq, div, b[0], b[1], b[2], b[3]);

	if ((ret = i2c_master_send(client, b, sizeof(b))) != sizeof(b)) {
		dprintk(1, "2c_master_send() failed.\n");
		return -EIO;
	}

	decoder->divider = div;

	return 0;
}

/* sp5055_chip_init: initialize SP5055 chip */
static int sp5055_chip_init(struct i2c_client *client)
{
	struct sp5055 *decoder = i2c_get_clientdata(client);
	int ret = 0;

	dprintk(4, ".\n");

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

	decoder->control0 = 0x8e;

	/* Setup for an initial TV channel */
	if ((ret = sp5055_setfreq(client, 1464000000)) != 0) {	/* Astra Transponder 1 */
		dprintk(1, "sp5055_setfreq() failed.\n");
		return ret;
	}

	return 0;
}

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

	dprintk(4, "addr=0x%02x.\n", addr);

	/* let's see whether this adapter can support what we need */
	if (i2c_check_functionality
	    (adap, I2C_FUNC_I2C | I2C_FUNC_SMBUS_READ_BYTE) == 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 tuner structure */
	if (NULL == (decoder = kmalloc(sizeof(struct sp5055), GFP_KERNEL))) {
		kfree(client);
		return -ENOMEM;
	}
	memset(decoder, 0, sizeof(struct sp5055));

	/* fill client structure */
	sprintf(client->name, "MITEL SP5055");
	client->id = sp5055_id++;
	client->flags = 0;
	client->addr = addr;
	client->adapter = adap;
	client->driver = &sp5055_driver;
	i2c_set_clientdata(client, decoder);

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

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

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

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

	return 0;
}

static int sp5055_attach(struct i2c_adapter *adap)
{
	dprintk(4, ".\n");

	return i2c_probe(adap, &addr_data, &sp5055_detect);
}

static int sp5055_detach(struct i2c_client *client)
{
	struct sp5055 *decoder = i2c_get_clientdata(client);
	int ret = 0;

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

	if ((ret = i2c_detach_client(client)) != 0) {
		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(decoder);
	kfree(client);

	return 0;
}

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

	switch (cmd) {
	case TUNER_SET_TVFREQ:{
			unsigned long int freq = *(unsigned long *)arg;

			dprintk(4, "TUNER_SET_TVFREQ (freq=%ld)\n", freq);

			return sp5055_setfreq(client, freq);
		}

	case TUNER_SET_TYPE:

		dprintk(4, "TUNER_SET_TYPE (not supported)\n");

		return -EINVAL;

/*
		case TUNER_SET_RADIOFREQ:
		
			dprintk(4,"I2C client '%s'; TUNER_SET_RADIOFREQ (not supported)\n", client->name)); 

			return -EINVAL;
*/
	case TUNER_GET_SIGNAL:{
			unsigned char stat = i2c_smbus_read_byte(client) & 0x07;

			*(int *)arg = ((int)(stat & 0x07) << 13);

			dprintk(4,
				"TUNER_GET_SIGNAL (afc=%d; stat=0x%02x; rtrn val=%d)\n",
				stat & 0x07, stat, *(int *)arg);

			return ret;
		}

	default:

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

		return -ENOIOCTLCMD;
	}
}

static struct i2c_driver sp5055_driver = {
	.owner = THIS_MODULE,
	.name = "sp5055II.o",
	.id = I2C_DRIVERID_SP5055,
	.flags = I2C_DF_NOTIFY,
	.attach_adapter = sp5055_attach,
	.detach_client = sp5055_detach,
	.command = sp5055_command,
};

static int __init sp5055_init(void)
{
	int ret;

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

	return 0;
}

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

MODULE_AUTHOR("Torsten Tittmann <Torsten.Tittmann@gmx.de>");
MODULE_DESCRIPTION("MITEL SP5055 I2C driver");
MODULE_LICENSE("GPL");

module_init(sp5055_init);
module_exit(sp5055_exit);
