summaryrefslogtreecommitdiff
path: root/drivers/media/pci/tw68/tw68-i2c.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/media/pci/tw68/tw68-i2c.c')
-rw-r--r--drivers/media/pci/tw68/tw68-i2c.c245
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;
+}