mfd: add support for MAXIM77665
Laxman Dewangan [Tue, 7 Aug 2012 09:07:34 +0000 (14:07 +0530)]
Maxim 77665 is Companion PMIC for Smartphones and Tablets.
This support Flash, Fuel Gauge, Haptic, MUIC and battery
charging.
This patch add the core driver for interface for accessing
resgister of the device.

Change-Id: I7d5dff8c222147b2ca1cd21a652f593cd7294601
Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
Reviewed-on: http://git-master/r/121587
Reviewed-by: Automatic_Commit_Validation_User

drivers/mfd/Kconfig
drivers/mfd/Makefile
drivers/mfd/max77665.c [new file with mode: 0644]
include/linux/mfd/max77665.h [new file with mode: 0644]

index 7040ae6..e31f771 100644 (file)
@@ -353,6 +353,19 @@ config PMIC_ADP5520
          individual components like LCD backlight, LEDs, GPIOs and Kepad
          under the corresponding menus.
 
+config MFD_MAX77665
+       bool "Maxim Semiconductor MAX77665 Companion PMIC Support"
+       depends on I2C=y && GENERIC_HARDIRQS
+       select MFD_CORE
+       select REGMAP_I2C
+       help
+         Say yes here to support for Maxim Semiconductor MAX77665.
+         This is a Power Management IC with Flash, Fuel Gauge, Haptic,
+         MUIC controls on chip.
+         This driver provides common support for accessing the device;
+         additional drivers must be enabled in order to use the functionality
+         of the device.
+
 config MFD_MAX8925
        bool "Maxim Semiconductor MAX8925 PMIC Support"
        depends on I2C=y && GENERIC_HARDIRQS
index efd9f7f..482cd27 100644 (file)
@@ -67,6 +67,7 @@ endif
 obj-$(CONFIG_UCB1400_CORE)     += ucb1400_core.o
 
 obj-$(CONFIG_PMIC_DA903X)      += da903x.o
+obj-$(CONFIG_MFD_MAX77665)     += max77665.o
 max8925-objs                   := max8925-core.o max8925-i2c.o
 obj-$(CONFIG_MFD_MAX8925)      += max8925.o
 obj-$(CONFIG_MFD_MAX8997)      += max8997.o max8997-irq.o
diff --git a/drivers/mfd/max77665.c b/drivers/mfd/max77665.c
new file mode 100644 (file)
index 0000000..b66712b
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+ * Core driver for MAXIM MAX77665
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/max77665.h>
+#include <linux/slab.h>
+
+#define MAX77665_INT_STS       0x22
+#define MAX77665_INT_MSK       0x23
+#define MAX77665_PMIC_FLASH    0x00 ... 0x10
+#define MAX77665_PMIC_PMIC     0x20 ... 0x2D
+#define MAX77665_PMIC_CHARGER  0xB0 ... 0xC6
+#define MAX77665_MUIC          0x00 ... 0x0E
+#define MAX77665_HAPTIC                0x00 ... 0x10
+
+static u8 max77665_i2c_slave_address[] = {
+       [MAX77665_I2C_SLAVE_PMIC] = 0x66,
+       [MAX77665_I2C_SLAVE_MUIC] = 0x25,
+       [MAX77665_I2C_SLAVE_HAPTIC] = 0x48,
+};
+
+struct max77665_irq_data {
+       int bit;
+};
+
+#define MAX77665_IRQ(_id, _bit_pos)                            \
+       [MAX77665_IRQ_##_id] =  {                               \
+                                       .bit    = (_bit_pos),   \
+                               }
+
+static const struct max77665_irq_data max77665_irqs[] = {
+       MAX77665_IRQ(CHARGER, 0),
+       MAX77665_IRQ(TOP_SYS, 1),
+       MAX77665_IRQ(FLASH, 2),
+       MAX77665_IRQ(MUIC, 3),
+};
+
+static struct mfd_cell max77665s[] = {
+       {.name = "max77665-charger",},
+       {.name = "max77665-flash",},
+       {.name = "max77665-muic",},
+       {.name = "max77665-haptic",},
+};
+
+static void max77665_irq_lock(struct irq_data *data)
+{
+       struct max77665 *max77665 = irq_data_get_irq_chip_data(data);
+
+       mutex_lock(&max77665->irq_lock);
+}
+
+static void max77665_irq_mask(struct irq_data *irq_data)
+{
+       struct max77665 *max77665 = irq_data_get_irq_chip_data(irq_data);
+       unsigned int __irq = irq_data->irq - max77665->irq_base;
+       const struct max77665_irq_data *data = &max77665_irqs[__irq];
+       int ret;
+
+       ret = max77665_set_bits(max77665->dev, MAX77665_I2C_SLAVE_PMIC,
+                               MAX77665_INT_MSK, data->bit);
+       if (ret < 0)
+               dev_err(max77665->dev,
+                       "Clearing mask reg failed e = %d\n", ret);
+}
+
+static void max77665_irq_unmask(struct irq_data *irq_data)
+{
+       struct max77665 *max77665 = irq_data_get_irq_chip_data(irq_data);
+       unsigned int __irq = irq_data->irq - max77665->irq_base;
+       const struct max77665_irq_data *data = &max77665_irqs[__irq];
+       int ret;
+
+       ret = max77665_clr_bits(max77665->dev, MAX77665_I2C_SLAVE_PMIC,
+                               MAX77665_INT_MSK, data->bit);
+       if (ret < 0)
+               dev_err(max77665->dev,
+                       "Setting mask reg failed e = %d\n", ret);
+}
+
+static void max77665_irq_sync_unlock(struct irq_data *data)
+{
+       struct max77665 *max77665 = irq_data_get_irq_chip_data(data);
+
+       mutex_unlock(&max77665->irq_lock);
+}
+
+static irqreturn_t max77665_irq(int irq, void *data)
+{
+       struct max77665 *max77665 = data;
+       int ret = 0;
+       u8 status = 0;
+       unsigned long int acks = 0;
+       int i;
+
+       ret = max77665_read(max77665->dev, MAX77665_I2C_SLAVE_PMIC,
+                                       MAX77665_INT_STS, &status);
+       if (ret < 0) {
+               dev_err(max77665->dev,
+                               "failed to read status regi, e %d\n", ret);
+               return IRQ_NONE;
+       }
+       acks = status;
+       for_each_set_bit(i, &acks, ARRAY_SIZE(max77665_irqs))
+               handle_nested_irq(max77665->irq_base + i);
+       return acks ? IRQ_HANDLED : IRQ_NONE;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int max77665_irq_set_wake(struct irq_data *data, unsigned int enable)
+{
+       struct max77665 *max77665 = irq_data_get_irq_chip_data(data);
+
+       return irq_set_irq_wake(max77665->irq_base, enable);
+}
+
+#else
+#define max77665_irq_set_wake NULL
+#endif
+
+static int __devinit max77665_irq_init(struct max77665 *max77665, int irq,
+       int irq_base)
+{
+       int i, ret;
+
+       if (irq_base <= 0) {
+               dev_err(max77665->dev, "IRQ base not set, int not supported\n");
+               return -EINVAL;
+       }
+
+       mutex_init(&max77665->irq_lock);
+
+       ret = max77665_write(max77665->dev, MAX77665_I2C_SLAVE_PMIC,
+                                       MAX77665_INT_MSK, 0xFF);
+       if (ret < 0) {
+               dev_err(max77665->dev,
+                       "Int mask reg write failed, e %d\n", ret);
+               return ret;
+       }
+
+       max77665->irq_base = irq_base;
+       max77665->irq_chip.name = "max77665";
+       max77665->irq_chip.irq_mask = max77665_irq_mask;
+       max77665->irq_chip.irq_unmask = max77665_irq_unmask;
+       max77665->irq_chip.irq_bus_lock = max77665_irq_lock;
+       max77665->irq_chip.irq_bus_sync_unlock = max77665_irq_sync_unlock;
+       max77665->irq_chip.irq_set_wake = max77665_irq_set_wake;
+
+       for (i = 0; i < ARRAY_SIZE(max77665_irqs); i++) {
+               int __irq = i + max77665->irq_base;
+               irq_set_chip_data(__irq, max77665);
+               irq_set_chip_and_handler(__irq, &max77665->irq_chip,
+                                        handle_simple_irq);
+               irq_set_nested_thread(__irq, 1);
+#ifdef CONFIG_ARM
+               set_irq_flags(__irq, IRQF_VALID);
+#endif
+       }
+
+       ret = request_threaded_irq(irq, NULL, max77665_irq, IRQF_ONESHOT,
+                               "max77665", max77665);
+       if (ret < 0) {
+               dev_err(max77665->dev, "Int registration failed, e %d\n", ret);
+               return ret;
+       }
+
+       device_init_wakeup(max77665->dev, 1);
+       return ret;
+}
+
+static bool rd_wr_reg_pmic(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case MAX77665_PMIC_FLASH:
+       case MAX77665_PMIC_PMIC:
+       case MAX77665_PMIC_CHARGER:
+               return true;
+       default:
+               dev_err(dev, "non-existing reg %s() reg 0x%x\n", __func__, reg);
+               return false;
+       }
+}
+
+static bool rd_wr_reg_muic(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case MAX77665_MUIC:
+               return true;
+       default:
+               dev_err(dev, "non-existing reg %s() reg 0x%x\n", __func__, reg);
+               return false;
+       }
+}
+
+static bool rd_wr_reg_haptic(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case MAX77665_HAPTIC:
+               return true;
+       default:
+               dev_err(dev, "non-existing reg %s() reg 0x%x\n", __func__, reg);
+               return false;
+       }
+}
+
+static const struct regmap_config max77665_regmap_config[] = {
+       {
+               .reg_bits = 8,
+               .val_bits = 8,
+               .max_register = 0xFF,
+               .writeable_reg = rd_wr_reg_pmic,
+               .readable_reg = rd_wr_reg_pmic,
+               .cache_type = REGCACHE_RBTREE,
+       }, {
+               .reg_bits = 8,
+               .val_bits = 8,
+               .max_register = 0x0E,
+               .writeable_reg = rd_wr_reg_muic,
+               .readable_reg = rd_wr_reg_muic,
+               .cache_type = REGCACHE_RBTREE,
+       }, {
+               .reg_bits = 8,
+               .val_bits = 8,
+               .max_register = 0x10,
+               .writeable_reg = rd_wr_reg_haptic,
+               .readable_reg = rd_wr_reg_haptic,
+               .cache_type = REGCACHE_RBTREE,
+       },
+};
+
+static int __devinit max77665_i2c_probe(struct i2c_client *client,
+                                       const struct i2c_device_id *id)
+{
+       struct max77665_platform_data *pdata = client->dev.platform_data;
+       struct max77665 *max77665;
+       struct i2c_client *slv_client;
+       int ret;
+       int i;
+
+       if (!pdata) {
+               dev_err(&client->dev, "max77665 requires platform data\n");
+               return -EINVAL;
+       }
+
+       max77665 = devm_kzalloc(&client->dev, sizeof(*max77665), GFP_KERNEL);
+       if (!max77665) {
+               dev_err(&client->dev, "mem alloc for max77665 failed\n");
+               return -ENOMEM;
+       }
+
+       max77665->dev = &client->dev;
+
+       for (i = 0; i < MAX77665_I2C_SLAVE_MAX; ++i) {
+               slv_client = max77665->client[i];
+               if (i == 0)
+                       slv_client = client;
+               else
+                       slv_client = i2c_new_dummy(client->adapter,
+                                               max77665_i2c_slave_address[i]);
+               if (!slv_client) {
+                       dev_err(&client->dev, "can't attach client %d\n", i);
+                       ret = -ENOMEM;
+                       goto err_exit;
+               }
+               i2c_set_clientdata(slv_client, max77665);
+
+               max77665->regmap[i] = devm_regmap_init_i2c(slv_client,
+                                       &max77665_regmap_config[i]);
+               if (IS_ERR(max77665->regmap[i])) {
+                       ret = PTR_ERR(max77665->regmap[i]);
+                       dev_err(&client->dev,
+                               "regmap %d init failed with err: %d\n", i, ret);
+                       goto err_exit;
+               }
+       }
+
+       if (client->irq > 0)
+               max77665_irq_init(max77665, client->irq, pdata->irq_base);
+
+       ret = mfd_add_devices(max77665->dev, -1, max77665s,
+               ARRAY_SIZE(max77665s), NULL, 0);
+       if (ret) {
+               dev_err(&client->dev, "add mfd devices failed with err: %d\n",
+                       ret);
+               goto err_irq_exit;
+       }
+
+       return 0;
+
+err_irq_exit:
+       if (client->irq > 0)
+               free_irq(client->irq, max77665);
+err_exit:
+       for (i = 0; i < MAX77665_I2C_SLAVE_MAX; ++i) {
+               slv_client = max77665->client[i];
+               if (slv_client && slv_client != client)
+                       i2c_unregister_device(slv_client);
+       }
+       return ret;
+}
+
+static int __devexit max77665_i2c_remove(struct i2c_client *client)
+{
+       struct max77665 *max77665 = i2c_get_clientdata(client);
+       int i;
+       struct i2c_client *slv_client;
+
+       mfd_remove_devices(max77665->dev);
+       if (client->irq > 0)
+               free_irq(client->irq, max77665);
+
+       for (i = 0; i < MAX77665_I2C_SLAVE_MAX; ++i) {
+               slv_client = max77665->client[i];
+               if (slv_client && slv_client != client)
+                       i2c_unregister_device(slv_client);
+       }
+
+       return 0;
+}
+
+static const struct i2c_device_id max77665_id_table[] = {
+       { "max77665", 0 },
+       { },
+};
+MODULE_DEVICE_TABLE(i2c, max77665_id_table);
+
+static struct i2c_driver max77665_driver = {
+       .driver = {
+               .name   = "max77665",
+               .owner  = THIS_MODULE,
+       },
+       .probe          = max77665_i2c_probe,
+       .remove         = __devexit_p(max77665_i2c_remove),
+       .id_table       = max77665_id_table,
+};
+
+static int __init max77665_init(void)
+{
+       return i2c_add_driver(&max77665_driver);
+}
+subsys_initcall(max77665_init);
+
+static void __exit max77665_exit(void)
+{
+       i2c_del_driver(&max77665_driver);
+}
+module_exit(max77665_exit);
+
+MODULE_DESCRIPTION("MAXIM MAX77665 core driver");
+MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/max77665.h b/include/linux/mfd/max77665.h
new file mode 100644 (file)
index 0000000..380a1a4
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Core driver interface for MAXIM77665
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LINUX_MFD_MAX77665_H
+#define __LINUX_MFD_MAX77665_H
+
+#include <linux/irq.h>
+#include <linux/regmap.h>
+
+/* MAX77665 Interrups */
+enum {
+       MAX77665_IRQ_CHARGER,
+       MAX77665_IRQ_TOP_SYS,
+       MAX77665_IRQ_FLASH,
+       MAX77665_IRQ_MUIC,
+};
+
+enum {
+       MAX77665_I2C_SLAVE_PMIC,
+       MAX77665_I2C_SLAVE_MUIC,
+       MAX77665_I2C_SLAVE_HAPTIC,
+       MAX77665_I2C_SLAVE_MAX,
+};
+
+struct max77665 {
+       struct device           *dev;
+       struct i2c_client       *client[MAX77665_I2C_SLAVE_MAX];
+       struct regmap           *regmap[MAX77665_I2C_SLAVE_MAX];
+       struct irq_chip         irq_chip;
+       struct mutex            irq_lock;
+       int                     irq_base;
+};
+
+struct max77665_platform_data {
+       int irq_base;
+};
+
+static inline int max77665_write(struct device *dev, int slv_id,
+               int reg, uint8_t val)
+{
+       struct max77665 *maxim = dev_get_drvdata(dev);
+
+       return regmap_write(maxim->regmap[slv_id], reg, val);
+}
+
+static inline int max77665_read(struct device *dev, int slv_id,
+               int reg, uint8_t *val)
+{
+       struct max77665 *maxim = dev_get_drvdata(dev);
+       unsigned int temp_val;
+       int ret;
+
+       ret = regmap_read(maxim->regmap[slv_id], reg, &temp_val);
+       if (!ret)
+               *val = temp_val;
+       return ret;
+}
+
+static inline int max77665_set_bits(struct device *dev, int slv_id,
+               int reg, uint8_t bit_num)
+{
+       struct max77665 *maxim = dev_get_drvdata(dev);
+
+       return regmap_update_bits(maxim->regmap[slv_id],
+                               reg, BIT(bit_num), ~0u);
+}
+
+static inline int max77665_clr_bits(struct device *dev, int slv_id,
+               int reg, uint8_t bit_num)
+{
+       struct max77665 *maxim = dev_get_drvdata(dev);
+
+       return regmap_update_bits(maxim->regmap[slv_id],
+                               reg, BIT(bit_num), 0u);
+}
+
+#endif /*__LINUX_MFD_MAX77665_H */