i2c: Bitbanging I2C bus driver using the GPIO API
[powerpc.git] / drivers / i2c / busses / i2c-gpio.c
diff --git a/drivers/i2c/busses/i2c-gpio.c b/drivers/i2c/busses/i2c-gpio.c
new file mode 100644 (file)
index 0000000..a7dd546
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * Bitbanging I2C bus driver using the GPIO API
+ *
+ * Copyright (C) 2007 Atmel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+#include <linux/i2c-gpio.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <asm/gpio.h>
+
+/* Toggle SDA by changing the direction of the pin */
+static void i2c_gpio_setsda_dir(void *data, int state)
+{
+       struct i2c_gpio_platform_data *pdata = data;
+
+       if (state)
+               gpio_direction_input(pdata->sda_pin);
+       else
+               gpio_direction_output(pdata->sda_pin, 0);
+}
+
+/*
+ * Toggle SDA by changing the output value of the pin. This is only
+ * valid for pins configured as open drain (i.e. setting the value
+ * high effectively turns off the output driver.)
+ */
+static void i2c_gpio_setsda_val(void *data, int state)
+{
+       struct i2c_gpio_platform_data *pdata = data;
+
+       gpio_set_value(pdata->sda_pin, state);
+}
+
+/* Toggle SCL by changing the direction of the pin. */
+static void i2c_gpio_setscl_dir(void *data, int state)
+{
+       struct i2c_gpio_platform_data *pdata = data;
+
+       if (state)
+               gpio_direction_input(pdata->scl_pin);
+       else
+               gpio_direction_output(pdata->scl_pin, 0);
+}
+
+/*
+ * Toggle SCL by changing the output value of the pin. This is used
+ * for pins that are configured as open drain and for output-only
+ * pins. The latter case will break the i2c protocol, but it will
+ * often work in practice.
+ */
+static void i2c_gpio_setscl_val(void *data, int state)
+{
+       struct i2c_gpio_platform_data *pdata = data;
+
+       gpio_set_value(pdata->scl_pin, state);
+}
+
+int i2c_gpio_getsda(void *data)
+{
+       struct i2c_gpio_platform_data *pdata = data;
+
+       return gpio_get_value(pdata->sda_pin);
+}
+
+int i2c_gpio_getscl(void *data)
+{
+       struct i2c_gpio_platform_data *pdata = data;
+
+       return gpio_get_value(pdata->scl_pin);
+}
+
+static int __init i2c_gpio_probe(struct platform_device *pdev)
+{
+       struct i2c_gpio_platform_data *pdata;
+       struct i2c_algo_bit_data *bit_data;
+       struct i2c_adapter *adap;
+       int ret;
+
+       pdata = pdev->dev.platform_data;
+       if (!pdata)
+               return -ENXIO;
+
+       ret = -ENOMEM;
+       adap = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL);
+       if (!adap)
+               goto err_alloc_adap;
+       bit_data = kzalloc(sizeof(struct i2c_algo_bit_data), GFP_KERNEL);
+       if (!bit_data)
+               goto err_alloc_bit_data;
+
+       ret = gpio_request(pdata->sda_pin, "sda");
+       if (ret)
+               goto err_request_sda;
+       ret = gpio_request(pdata->scl_pin, "scl");
+       if (ret)
+               goto err_request_scl;
+
+       if (pdata->sda_is_open_drain) {
+               gpio_direction_output(pdata->sda_pin, 1);
+               bit_data->setsda = i2c_gpio_setsda_val;
+       } else {
+               gpio_direction_input(pdata->sda_pin);
+               bit_data->setsda = i2c_gpio_setsda_dir;
+       }
+
+       if (pdata->scl_is_open_drain || pdata->scl_is_output_only) {
+               gpio_direction_output(pdata->scl_pin, 1);
+               bit_data->setscl = i2c_gpio_setscl_val;
+       } else {
+               gpio_direction_input(pdata->scl_pin);
+               bit_data->setscl = i2c_gpio_setscl_dir;
+       }
+
+       if (!pdata->scl_is_output_only)
+               bit_data->getscl = i2c_gpio_getscl;
+       bit_data->getsda = i2c_gpio_getsda;
+
+       if (pdata->udelay)
+               bit_data->udelay = pdata->udelay;
+       else if (pdata->scl_is_output_only)
+               bit_data->udelay = 50;                  /* 10 kHz */
+       else
+               bit_data->udelay = 5;                   /* 100 kHz */
+
+       if (pdata->timeout)
+               bit_data->timeout = pdata->timeout;
+       else
+               bit_data->timeout = HZ / 10;            /* 100 ms */
+
+       bit_data->data = pdata;
+
+       adap->owner = THIS_MODULE;
+       snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id);
+       adap->algo_data = bit_data;
+       adap->dev.parent = &pdev->dev;
+
+       ret = i2c_bit_add_bus(adap);
+       if (ret)
+               goto err_add_bus;
+
+       platform_set_drvdata(pdev, adap);
+
+       dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n",
+                pdata->sda_pin, pdata->scl_pin,
+                pdata->scl_is_output_only
+                ? ", no clock stretching" : "");
+
+       return 0;
+
+err_add_bus:
+       gpio_free(pdata->scl_pin);
+err_request_scl:
+       gpio_free(pdata->sda_pin);
+err_request_sda:
+       kfree(bit_data);
+err_alloc_bit_data:
+       kfree(adap);
+err_alloc_adap:
+       return ret;
+}
+
+static int __exit i2c_gpio_remove(struct platform_device *pdev)
+{
+       struct i2c_gpio_platform_data *pdata;
+       struct i2c_adapter *adap;
+
+       adap = platform_get_drvdata(pdev);
+       pdata = pdev->dev.platform_data;
+
+       i2c_del_adapter(adap);
+       gpio_free(pdata->scl_pin);
+       gpio_free(pdata->sda_pin);
+       kfree(adap->algo_data);
+       kfree(adap);
+
+       return 0;
+}
+
+static struct platform_driver i2c_gpio_driver = {
+       .driver         = {
+               .name   = "i2c-gpio",
+               .owner  = THIS_MODULE,
+       },
+       .remove         = __exit_p(i2c_gpio_remove),
+};
+
+static int __init i2c_gpio_init(void)
+{
+       int ret;
+
+       ret = platform_driver_probe(&i2c_gpio_driver, i2c_gpio_probe);
+       if (ret)
+               printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);
+
+       return ret;
+}
+module_init(i2c_gpio_init);
+
+static void __exit i2c_gpio_exit(void)
+{
+       platform_driver_unregister(&i2c_gpio_driver);
+}
+module_exit(i2c_gpio_exit);
+
+MODULE_AUTHOR("Haavard Skinnemoen <hskinnemoen@atmel.com>");
+MODULE_DESCRIPTION("Platform-independent bitbanging I2C driver");
+MODULE_LICENSE("GPL");