diff options
Diffstat (limited to 'drivers/media/pci/tw68/tw68-i2c.c')
-rw-r--r-- | drivers/media/pci/tw68/tw68-i2c.c | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/drivers/media/pci/tw68/tw68-i2c.c b/drivers/media/pci/tw68/tw68-i2c.c new file mode 100644 index 000000000000..38659d0b1e18 --- /dev/null +++ b/drivers/media/pci/tw68/tw68-i2c.c @@ -0,0 +1,245 @@ +/* + * tw68 code to handle the i2c interface. + * + * Much of this code is derived from the bt87x driver. The original + * work was by Gerd Knorr; more recently the code was enhanced by Mauro + * Carvalho Chehab. Their work is gratefully acknowledged. Full credit + * goes to them - any problems within this code are mine. + * + * Copyright (C) 2009 William M. Brack <wbrack@mmm.com.hk> + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/init.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/delay.h> + +#include "tw68.h" +#include <media/v4l2-common.h> +#include <linux/i2c-algo-bit.h> + +/*----------------------------------------------------------------*/ + +static unsigned int i2c_debug; +module_param(i2c_debug, int, 0644); +MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); + +#if 0 +static unsigned int i2c_scan; +module_param(i2c_scan, int, 0444); +MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time"); +#endif + +#define d1printk if (1 == i2c_debug) printk + +#define I2C_CLOCK 0xa6 /* 99.4 kHz */ + +/*----------------------------------------------------------------------*/ +/* Although the TW68xx i2c controller has a "hardware" mode, where all of + * the low-level i2c/smbbus is handled by the chip, it appears that mode + * is not suitable for linux i2c handling routines because extended "bursts" + * of data (sequences of bytes without intervening START/STOP bits) are + * not possible. Instead, we put the chip into "software" mode, and handle + * the i2c bus at a low level. To accomplish this, we use the routines + * from the i2c modules. + * + * Because the particular boards which I had for testing did not have any + * devices attached to the i2c bus, I have been unable to test these + * routines. + */ + +/*----------------------------------------------------------------------*/ +/* I2C functions - "bit-banging" adapter (software i2c) */ + +/* tw68_bit_setcl + * Handles "toggling" the i2c clock bit + */ +static void tw68_bit_setscl(void *data, int state) +{ + struct tw68_dev *dev = data; + + tw_andorb(TW68_SBUSC, (state ? 1 : 0) << TW68_SSCLK, TW68_SSCLK_B); +} + +/* tw68_bit_setsda + * Handles "toggling" the i2c data bit + */ +static void tw68_bit_setsda(void *data, int state) +{ + struct tw68_dev *dev = data; + + tw_andorb(TW68_SBUSC, (state ? 1 : 0) << TW68_SSDAT, TW68_SSDAT_B); +} + +/* tw68_bit_getscl + * + * Returns the current state of the clock bit + */ +static int tw68_bit_getscl(void *data) +{ + struct tw68_dev *dev = data; + + return (tw_readb(TW68_SBUSC) & TW68_SSCLK_B) ? 1 : 0; +} + +/* tw68_bit_getsda + * + * Returns the current state of the data bit + */ +static int tw68_bit_getsda(void *data) +{ + struct tw68_dev *dev = data; + + return (tw_readb(TW68_SBUSC) & TW68_SSDAT_B) ? 1 : 0; +} + +static struct i2c_algo_bit_data __devinitdata tw68_i2c_algo_bit_template = { + .setsda = tw68_bit_setsda, + .setscl = tw68_bit_setscl, + .getsda = tw68_bit_getsda, + .getscl = tw68_bit_getscl, + .udelay = 16, + .timeout = 200, +}; + +static struct i2c_client tw68_client_template = { + .name = "tw68 internal", +}; + +/*----------------------------------------------------------------*/ + +static int attach_inform(struct i2c_client *client) +{ +/* struct tw68_dev *dev = client->adapter->algo_data; */ + + d1printk("%s i2c attach [addr=0x%x,client=%s]\n", + client->driver->driver.name, client->addr, client->name); + + switch (client->addr) { + /* No info yet on what addresses to expect */ + } + + return 0; +} + +static struct i2c_adapter tw68_adap_sw_template = { + .owner = THIS_MODULE, + .name = "tw68_sw", + .client_register = attach_inform, +}; + +static int tw68_i2c_eeprom(struct tw68_dev *dev, unsigned char *eedata, + int len) +{ + unsigned char buf; + int i, err; + + dev->i2c_client.addr = 0xa0 >> 1; + buf = 256 - len; + + err = i2c_master_send(&dev->i2c_client, &buf, 1); + if (1 != err) { + printk(KERN_INFO "%s: Huh, no eeprom present (err = %d)?\n", + dev->name, err); + return -1; + } + err = i2c_master_recv(&dev->i2c_client, eedata, len); + if (len != err) { + printk(KERN_WARNING "%s: i2c eeprom read error (err=%d)\n", + dev->name, err); + return -1; + } + + for (i = 0; i < len; i++) { + if (0 == (i % 16)) + printk(KERN_INFO "%s: i2c eeprom %02x:", + dev->name, i); + printk(KERN_INFO " %02x", eedata[i]); + if (15 == (i % 16)) + printk("\n"); + } + return 0; +} + +#if 0 +static char *i2c_devs[128] = { + [0xa0 >> 1] = "eeprom", +}; + +static void do_i2c_scan(char *name, struct i2c_client *c) +{ + unsigned char buf; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) { + c->addr = i; + rc = i2c_master_recv(c, &buf, 1); + if (rc < 0) + continue; + printk(KERN_INFO "%s: i2c scan: found device " + "@ 0x%x [%s]\n", name, i << 1, + i2c_devs[i] ? i2c_devs[i] : "???"); + } +} +#endif + +int __devinit tw68_i2c_register(struct tw68_dev *dev) +{ + int rc; + +printk(KERN_DEBUG "%s: Registering i2c module\n", __func__); + tw_writeb(TW68_I2C_RST, 1); /* reset the i2c module */ + + memcpy(&dev->i2c_client, &tw68_client_template, + sizeof(tw68_client_template)); + + memcpy(&dev->i2c_adap, &tw68_adap_sw_template, + sizeof(tw68_adap_sw_template)); + dev->i2c_adap.algo_data = &dev->i2c_algo; + dev->i2c_adap.dev.parent = &dev->pci->dev; + + memcpy(&dev->i2c_algo, &tw68_i2c_algo_bit_template, + sizeof(tw68_i2c_algo_bit_template)); + dev->i2c_algo.data = dev; + /* TODO - may want to set better name (see bttv code) */ + + i2c_set_adapdata(&dev->i2c_adap, &dev->v4l2_dev); + dev->i2c_client.adapter = &dev->i2c_adap; + + /* Assure chip is in "software" mode */ + tw_writel(TW68_SBUSC, TW68_SSDAT | TW68_SSCLK); + tw68_bit_setscl(dev, 1); + tw68_bit_setsda(dev, 1); + + rc = i2c_bit_add_bus(&dev->i2c_adap); + + tw68_i2c_eeprom(dev, dev->eedata, sizeof(dev->eedata)); +#if 0 + if (i2c_scan) + do_i2c_scan(dev->name, &dev->i2c_client); +#endif + + return rc; +} + +int tw68_i2c_unregister(struct tw68_dev *dev) +{ + i2c_del_adapter(&dev->i2c_adap); + return 0; +} |