Merge branch 'audit.b51' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/audit...
[pandora-kernel.git] / drivers / i2c / chips / tps65010.c
index e320994..8594968 100644 (file)
 #include <linux/debugfs.h>
 #include <linux/seq_file.h>
 #include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#include <linux/i2c/tps65010.h>
+
+#include <asm/gpio.h>
 
-#include <asm/arch/tps65010.h>
 
 /*-------------------------------------------------------------------------*/
 
@@ -60,7 +64,6 @@ static struct i2c_driver tps65010_driver;
  * as part of board setup by a bootloader.
  */
 enum tps_model {
-       TPS_UNKNOWN = 0,
        TPS65010,
        TPS65011,
        TPS65012,
@@ -84,7 +87,9 @@ struct tps65010 {
        u8                      chgstatus, regstatus, chgconf;
        u8                      nmask1, nmask2;
 
-       /* not currently tracking GPIO state */
+       u8                      outmask;
+       struct gpio_chip        chip;
+       struct platform_device  *leds;
 };
 
 #define        POWER_POLL_DELAY        msecs_to_jiffies(5000)
@@ -447,6 +452,59 @@ static irqreturn_t tps65010_irq(int irq, void *_tps)
        return IRQ_HANDLED;
 }
 
+/*-------------------------------------------------------------------------*/
+
+/* offsets 0..3 == GPIO1..GPIO4
+ * offsets 4..5 == LED1/nPG, LED2 (we set one of the non-BLINK modes)
+ */
+static void
+tps65010_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+       if (offset < 4)
+               tps65010_set_gpio_out_value(offset + 1, value);
+       else
+               tps65010_set_led(offset - 3, value ? ON : OFF);
+}
+
+static int
+tps65010_output(struct gpio_chip *chip, unsigned offset, int value)
+{
+       /* GPIOs may be input-only */
+       if (offset < 4) {
+               struct tps65010         *tps;
+
+               tps = container_of(chip, struct tps65010, chip);
+               if (!(tps->outmask & (1 << offset)))
+                       return -EINVAL;
+               tps65010_set_gpio_out_value(offset + 1, value);
+       } else
+               tps65010_set_led(offset - 3, value ? ON : OFF);
+
+       return 0;
+}
+
+static int tps65010_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+       int                     value;
+       struct tps65010         *tps;
+
+       tps = container_of(chip, struct tps65010, chip);
+
+       if (offset < 4) {
+               value = i2c_smbus_read_byte_data(tps->client, TPS_DEFGPIO);
+               if (value < 0)
+                       return 0;
+               if (value & (1 << (offset + 4)))        /* output */
+                       return !(value & (1 << offset));
+               else                                    /* input */
+                       return (value & (1 << offset));
+       }
+
+       /* REVISIT we *could* report LED1/nPG and LED2 state ... */
+       return 0;
+}
+
+
 /*-------------------------------------------------------------------------*/
 
 static struct tps65010 *the_tps;
@@ -454,21 +512,31 @@ static struct tps65010 *the_tps;
 static int __exit tps65010_remove(struct i2c_client *client)
 {
        struct tps65010         *tps = i2c_get_clientdata(client);
+       struct tps65010_board   *board = client->dev.platform_data;
 
+       if (board && board->teardown) {
+               int status = board->teardown(client, board->context);
+               if (status < 0)
+                       dev_dbg(&client->dev, "board %s %s err %d\n",
+                               "teardown", client->name, status);
+       }
        if (client->irq > 0)
                free_irq(client->irq, tps);
        cancel_delayed_work(&tps->work);
        flush_scheduled_work();
        debugfs_remove(tps->file);
        kfree(tps);
+       i2c_set_clientdata(client, NULL);
        the_tps = NULL;
        return 0;
 }
 
-static int tps65010_probe(struct i2c_client *client)
+static int tps65010_probe(struct i2c_client *client,
+                         const struct i2c_device_id *id)
 {
        struct tps65010         *tps;
        int                     status;
+       struct tps65010_board   *board = client->dev.platform_data;
 
        if (the_tps) {
                dev_dbg(&client->dev, "only one tps6501x chip allowed\n");
@@ -485,20 +553,7 @@ static int tps65010_probe(struct i2c_client *client)
        mutex_init(&tps->lock);
        INIT_DELAYED_WORK(&tps->work, tps65010_work);
        tps->client = client;
-
-       if (strcmp(client->name, "tps65010") == 0)
-               tps->model = TPS65010;
-       else if (strcmp(client->name, "tps65011") == 0)
-               tps->model = TPS65011;
-       else if (strcmp(client->name, "tps65012") == 0)
-               tps->model = TPS65012;
-       else if (strcmp(client->name, "tps65013") == 0)
-               tps->model = TPS65013;
-       else {
-               dev_warn(&client->dev, "unknown chip '%s'\n", client->name);
-               status = -ENODEV;
-               goto fail1;
-       }
+       tps->model = id->driver_data;
 
        /* the IRQ is active low, but many gpio lines can't support that
         * so this driver uses falling-edge triggers instead.
@@ -527,9 +582,6 @@ static int tps65010_probe(struct i2c_client *client)
        case TPS65012:
                tps->por = 1;
                break;
-       case TPS_UNKNOWN:
-               printk(KERN_WARNING "%s: unknown TPS chip\n", DRIVER_NAME);
-               break;
        /* else CHGCONFIG.POR is replaced by AUA, enabling a WAIT mode */
        }
        tps->chgconf = i2c_smbus_read_byte_data(client, TPS_CHGCONFIG);
@@ -548,6 +600,7 @@ static int tps65010_probe(struct i2c_client *client)
                i2c_smbus_read_byte_data(client, TPS_DEFGPIO),
                i2c_smbus_read_byte_data(client, TPS_MASK3));
 
+       i2c_set_clientdata(client, tps);
        the_tps = tps;
 
 #if    defined(CONFIG_USB_GADGET) && !defined(CONFIG_USB_OTG)
@@ -577,18 +630,60 @@ static int tps65010_probe(struct i2c_client *client)
 
        tps->file = debugfs_create_file(DRIVER_NAME, S_IRUGO, NULL,
                                tps, DEBUG_FOPS);
+
+       /* optionally register GPIOs */
+       if (board && board->base > 0) {
+               tps->outmask = board->outmask;
+
+               tps->chip.label = client->name;
+
+               tps->chip.set = tps65010_gpio_set;
+               tps->chip.direction_output = tps65010_output;
+
+               /* NOTE:  only partial support for inputs; nyet IRQs */
+               tps->chip.get = tps65010_gpio_get;
+
+               tps->chip.base = board->base;
+               tps->chip.ngpio = 6;
+               tps->chip.can_sleep = 1;
+
+               status = gpiochip_add(&tps->chip);
+               if (status < 0)
+                       dev_err(&client->dev, "can't add gpiochip, err %d\n",
+                                       status);
+               else if (board->setup) {
+                       status = board->setup(client, board->context);
+                       if (status < 0) {
+                               dev_dbg(&client->dev,
+                                       "board %s %s err %d\n",
+                                       "setup", client->name, status);
+                               status = 0;
+                       }
+               }
+       }
+
        return 0;
 fail1:
        kfree(tps);
        return status;
 }
 
+static const struct i2c_device_id tps65010_id[] = {
+       { "tps65010", TPS65010 },
+       { "tps65011", TPS65011 },
+       { "tps65012", TPS65012 },
+       { "tps65013", TPS65013 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, tps65010_id);
+
 static struct i2c_driver tps65010_driver = {
        .driver = {
                .name   = "tps65010",
        },
        .probe  = tps65010_probe,
        .remove = __exit_p(tps65010_remove),
+       .id_table = tps65010_id,
 };
 
 /*-------------------------------------------------------------------------*/