aboutsummaryrefslogtreecommitdiff
path: root/drivers/mfd/88pm860x-i2c.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2010-03-07 15:56:04 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2010-03-07 15:56:04 -0800
commit6dc3eb5c1f96641cda7056aa34393e317076d6cf (patch)
tree9a615b884d7ff5375382b5a3f020f518f618c589 /drivers/mfd/88pm860x-i2c.c
parent8fe900b8c7aa6a307e552ff776e0c04c28dcf9c8 (diff)
parent2c08583c6a6b4c5f5dea4cb0931eca82af7db6fe (diff)
Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/sameo/mfd-2.6
* 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/sameo/mfd-2.6: (66 commits) mfd: Fix ucb1x00 build failure for collie_defconfig mfd: Fix lpc_sch related depends/selects, fix build error gpio: Fix sch_gpio warning gpio: add Intel SCH GPIO controller driver i2c: convert i2c-isch to platform_device mfd: Use completion interrupt for WM831x AUXADC mfd: Use completion interrupt for WM835x AUXADC mfd: Introduce remove_script function for twl4030 mfd/mmc: SDHI Kconfig update mfd: sh_mobile_sdhi MMC_CAP_MMC_HIGHSPEED support gpiolib: Force wm831x GPIOs into GPIO mode when requested mfd: Add WM831x revision B support gpiolib: Correct debugfs display of WM831x GPIO inversion gpiolib: Actually set output state in wm831x_gpio_direction_output() tmio_mmc: Balance cell enable()/disable() calls tmio_mmc: Remove const from platform data V3 tmio_mmc: Use 100ms mmc_detect_change() delay tmio_mmc: Add MMC_CAP_MMC_HIGHSPEED support V2 tmio_mmc: Keep card-detect interrupts enabled mfd: Add twl6030 base addr for ID0, ID1, ID2 ...
Diffstat (limited to 'drivers/mfd/88pm860x-i2c.c')
-rw-r--r--drivers/mfd/88pm860x-i2c.c236
1 files changed, 236 insertions, 0 deletions
diff --git a/drivers/mfd/88pm860x-i2c.c b/drivers/mfd/88pm860x-i2c.c
new file mode 100644
index 00000000000..c37e12bf300
--- /dev/null
+++ b/drivers/mfd/88pm860x-i2c.c
@@ -0,0 +1,236 @@
+/*
+ * I2C driver for Marvell 88PM860x
+ *
+ * Copyright (C) 2009 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.com>
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/mfd/88pm860x.h>
+
+static inline int pm860x_read_device(struct i2c_client *i2c,
+ int reg, int bytes, void *dest)
+{
+ unsigned char data;
+ int ret;
+
+ data = (unsigned char)reg;
+ ret = i2c_master_send(i2c, &data, 1);
+ if (ret < 0)
+ return ret;
+
+ ret = i2c_master_recv(i2c, dest, bytes);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static inline int pm860x_write_device(struct i2c_client *i2c,
+ int reg, int bytes, void *src)
+{
+ unsigned char buf[bytes + 1];
+ int ret;
+
+ buf[0] = (unsigned char)reg;
+ memcpy(&buf[1], src, bytes);
+
+ ret = i2c_master_send(i2c, buf, bytes + 1);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+int pm860x_reg_read(struct i2c_client *i2c, int reg)
+{
+ struct pm860x_chip *chip = i2c_get_clientdata(i2c);
+ unsigned char data;
+ int ret;
+
+ mutex_lock(&chip->io_lock);
+ ret = pm860x_read_device(i2c, reg, 1, &data);
+ mutex_unlock(&chip->io_lock);
+
+ if (ret < 0)
+ return ret;
+ else
+ return (int)data;
+}
+EXPORT_SYMBOL(pm860x_reg_read);
+
+int pm860x_reg_write(struct i2c_client *i2c, int reg,
+ unsigned char data)
+{
+ struct pm860x_chip *chip = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&chip->io_lock);
+ ret = pm860x_write_device(i2c, reg, 1, &data);
+ mutex_unlock(&chip->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(pm860x_reg_write);
+
+int pm860x_bulk_read(struct i2c_client *i2c, int reg,
+ int count, unsigned char *buf)
+{
+ struct pm860x_chip *chip = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&chip->io_lock);
+ ret = pm860x_read_device(i2c, reg, count, buf);
+ mutex_unlock(&chip->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(pm860x_bulk_read);
+
+int pm860x_bulk_write(struct i2c_client *i2c, int reg,
+ int count, unsigned char *buf)
+{
+ struct pm860x_chip *chip = i2c_get_clientdata(i2c);
+ int ret;
+
+ mutex_lock(&chip->io_lock);
+ ret = pm860x_write_device(i2c, reg, count, buf);
+ mutex_unlock(&chip->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(pm860x_bulk_write);
+
+int pm860x_set_bits(struct i2c_client *i2c, int reg,
+ unsigned char mask, unsigned char data)
+{
+ struct pm860x_chip *chip = i2c_get_clientdata(i2c);
+ unsigned char value;
+ int ret;
+
+ mutex_lock(&chip->io_lock);
+ ret = pm860x_read_device(i2c, reg, 1, &value);
+ if (ret < 0)
+ goto out;
+ value &= ~mask;
+ value |= data;
+ ret = pm860x_write_device(i2c, reg, 1, &value);
+out:
+ mutex_unlock(&chip->io_lock);
+ return ret;
+}
+EXPORT_SYMBOL(pm860x_set_bits);
+
+
+static const struct i2c_device_id pm860x_id_table[] = {
+ { "88PM860x", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, pm860x_id_table);
+
+static int verify_addr(struct i2c_client *i2c)
+{
+ unsigned short addr_8607[] = {0x30, 0x34};
+ unsigned short addr_8606[] = {0x10, 0x11};
+ int size, i;
+
+ if (i2c == NULL)
+ return 0;
+ size = ARRAY_SIZE(addr_8606);
+ for (i = 0; i < size; i++) {
+ if (i2c->addr == *(addr_8606 + i))
+ return CHIP_PM8606;
+ }
+ size = ARRAY_SIZE(addr_8607);
+ for (i = 0; i < size; i++) {
+ if (i2c->addr == *(addr_8607 + i))
+ return CHIP_PM8607;
+ }
+ return 0;
+}
+
+static int __devinit pm860x_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct pm860x_platform_data *pdata = client->dev.platform_data;
+ struct pm860x_chip *chip;
+
+ if (!pdata) {
+ pr_info("No platform data in %s!\n", __func__);
+ return -EINVAL;
+ }
+
+ chip = kzalloc(sizeof(struct pm860x_chip), GFP_KERNEL);
+ if (chip == NULL)
+ return -ENOMEM;
+
+ chip->id = verify_addr(client);
+ chip->client = client;
+ i2c_set_clientdata(client, chip);
+ chip->dev = &client->dev;
+ mutex_init(&chip->io_lock);
+ dev_set_drvdata(chip->dev, chip);
+
+ /*
+ * Both client and companion client shares same platform driver.
+ * Driver distinguishes them by pdata->companion_addr.
+ * pdata->companion_addr is only assigned if companion chip exists.
+ * At the same time, the companion_addr shouldn't equal to client
+ * address.
+ */
+ if (pdata->companion_addr && (pdata->companion_addr != client->addr)) {
+ chip->companion_addr = pdata->companion_addr;
+ chip->companion = i2c_new_dummy(chip->client->adapter,
+ chip->companion_addr);
+ i2c_set_clientdata(chip->companion, chip);
+ }
+
+ pm860x_device_init(chip, pdata);
+ return 0;
+}
+
+static int __devexit pm860x_remove(struct i2c_client *client)
+{
+ struct pm860x_chip *chip = i2c_get_clientdata(client);
+
+ pm860x_device_exit(chip);
+ i2c_unregister_device(chip->companion);
+ i2c_set_clientdata(chip->companion, NULL);
+ i2c_set_clientdata(chip->client, NULL);
+ kfree(chip);
+ return 0;
+}
+
+static struct i2c_driver pm860x_driver = {
+ .driver = {
+ .name = "88PM860x",
+ .owner = THIS_MODULE,
+ },
+ .probe = pm860x_probe,
+ .remove = __devexit_p(pm860x_remove),
+ .id_table = pm860x_id_table,
+};
+
+static int __init pm860x_i2c_init(void)
+{
+ int ret;
+ ret = i2c_add_driver(&pm860x_driver);
+ if (ret != 0)
+ pr_err("Failed to register 88PM860x I2C driver: %d\n", ret);
+ return ret;
+}
+subsys_initcall(pm860x_i2c_init);
+
+static void __exit pm860x_i2c_exit(void)
+{
+ i2c_del_driver(&pm860x_driver);
+}
+module_exit(pm860x_i2c_exit);
+
+MODULE_DESCRIPTION("I2C Driver for Marvell 88PM860x");
+MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
+MODULE_LICENSE("GPL");