/*
 * fix_eeprom.c: Tool to fix bad subsystem ids for the SAA7146 based cards 
 *
 * Copyright (C) 2005-2007 Oliver Endriss <o.endriss@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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
 * 
 *
 * Revision History:
 * -----------------
 * 25jul2007  En  1.0a  updated instructions for HG driver
 *
 */

/*
   *** DISCLAIMER - PLEASE READ ***
   This module will overwrite subsystem vendor and subsystem id in the EEPROM
   of a saa7146-based DVB card without any warning. Use on your own risk!
   The author will not be responsible for any damage caused by this program!
   You should not use this program unless you know what you are doing!

   *** ACHTUNG - ACHTUNG - ACHTUNG ***
   Dieses Programm überschreibt Subsystem-Vendor und Subsystem-ID im EEPROM
   der saa7146-basierten DVB-Karte. Sie verwenden dieses Programm auf eigene
   Gefahr! Der Autor haftet nicht für Schäden durch Anwendung des Programmes!
   Sie sollten dieses Programm nicht verwenden, es sei denn, Sie wissen genau,
   was Sie tun!


   Compilation:
   ------------
   Mercurial(HG) driver:
   - install driver for kernel 2.6.x
   - cd .../v4l
   - copy this file to the v4l directory
   - edit Makefile:
     add 'obj-m += fix_eeprom.o' at the end of the file
   - make
   
   dvb-kernel CVS driver (obsolete):
   - install dvb-kernel driver for kernel 2.6.x
   - cd .../build-2.6
   - copy this file to the build-2.6 directory
   - edit Makefile:
     add 'obj-m += fix_eeprom.o' after the last 'obj-m' line
   - make
   
   
   Usage (you have been warned!):
   ------------------------------
   - identify the subsystem of the broken card using 'lspci -vn'
     (assuming broken card has subsystem AAAA:BBBB)
   - insmod saa7146.ko
   - overwrite broken id:
     insmod fix_eeprom.ko old=0xAAAABBBB new=0xCCCCDDDD
         where CCCCDDDD is the new id to be written:
         CCCC - subsystem vendor, DDDD - subsystem id
     (ignore error message:
      error inserting 'fix_eeprom.ko': -1 Operation not permitted)
   - check your system log:
         *** using device 1131:7146 subsystem AAAA:BBBB ***
         *** eeprom - old id: AAAA:BBBB ***
         *** eeprom - writing new id CCCC:DDDD ***     
   - reboot
*/

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <media/saa7146.h>
#include "budget.h"

static int old;
static int new;
module_param(old, int, 0444);
module_param(new, int, 0444);
MODULE_PARM_DESC(old, "old (bad) subsystem-id");
MODULE_PARM_DESC(new, "new subsystem-id");

static struct i2c_adapter i2c_adap;


static int dev_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info)
{
	u8 subsys[10] = { 0x00 };
	struct i2c_msg msg_read[] = {
		{ .addr = 0x50, .flags = 0, .buf = subsys, .len = 1 },
                { .addr = 0x50, .flags = I2C_M_RD, .buf = subsys+1, .len = 4 } };
	struct i2c_msg msg_write[] = {
		{ .addr = 0x50, .flags = 0, .buf = subsys, .len = 5 } };


	if (dev->pci->subsystem_vendor == ((old >> 16) & 0xffff) &&
	    dev->pci->subsystem_device == (old & 0xffff)) {
		printk("*** using device %04x:%04x subsystem %04x:%04x ***\n",
			dev->pci->vendor, dev->pci->device,
			dev->pci->subsystem_vendor, dev->pci->subsystem_device);
	
		i2c_adap.class = I2C_CLASS_TV_DIGITAL;
		saa7146_i2c_adapter_prepare(dev, &i2c_adap, SAA7146_I2C_BUS_BIT_RATE_120);
		i2c_add_adapter(&i2c_adap);

		i2c_transfer(&i2c_adap, msg_read, 2);

		printk("*** eeprom - old id: %02x%02x:%02x%02x ***\n",
			subsys[3], subsys[4], subsys[1], subsys[2]);

		subsys[1] = (new >> 8) & 0xff;
		subsys[2] = new & 0xff;
		subsys[3] = (new >> 24) & 0xff;
		subsys[4] = (new >> 16) & 0xff;

		if (new != 0) {
			if (subsys[3] == 0 && subsys[4] == 0)
				printk("*** invalid new id: %02x%02x:%02x%02x ***\n",
					subsys[3], subsys[4], subsys[1], subsys[2]);
	 	      	else {
				printk("*** eeprom - writing new id %02x%02x:%02x%02x ***\n",
					subsys[3], subsys[4], subsys[1], subsys[2]);
				i2c_transfer(&i2c_adap, msg_write, 1);
			}
		}

		i2c_del_adapter(&i2c_adap);
	}

	return -1;
}

static int dev_detach(struct saa7146_dev *dev)
{
	printk("%s\n", __FUNCTION__);

	return 0;
}


static struct saa7146_extension budget_extension;

MAKE_BUDGET_INFO(xxx, "SAA7146 PCI", 0);

static struct pci_device_id pci_tbl[] = {
	MAKE_EXTENSION_PCI(xxx,  PCI_ANY_ID, PCI_ANY_ID),
	{
		.vendor    = 0,
	}
};

MODULE_DEVICE_TABLE(pci, pci_tbl);

static struct saa7146_extension budget_extension = {
	.name		= "fix eeprom\0",
	.flags	 	= 0,
	
	.module		= THIS_MODULE,
	.pci_tbl	= pci_tbl,
	.attach		= dev_attach,
	.detach		= dev_detach,
};	

static int __init fix_init(void) 
{
	saa7146_register_extension(&budget_extension);
	saa7146_unregister_extension(&budget_extension); 
	return -1;
}

static void __exit fix_exit(void)
{
	printk("%s\n", __FUNCTION__);

	saa7146_unregister_extension(&budget_extension); 
}

module_init(fix_init);
module_exit(fix_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Oliver Endriss");
MODULE_DESCRIPTION("Tool to fix broken subsystem ids for the SAA7146 based cards, rev 1.0a");
