summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaxman Dewangan <ldewangan@nvidia.com>2012-08-07 14:37:34 +0530
committerDan Willemsen <dwillemsen@nvidia.com>2013-09-14 12:31:22 -0700
commit4ae11c5dad37d7d981ad0314e3afe2fc68717158 (patch)
treebeb0df89abcece2bb28c593c91528b9d0faaea18
parentab1353388729de3eaebe04a38442a222d32ec759 (diff)
mfd: add support for MAXIM77665
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 Rebase-Id: Rd04b240a50d85af6f8e44db9c825f90adad50acd
-rw-r--r--drivers/mfd/Kconfig13
-rw-r--r--drivers/mfd/Makefile1
-rw-r--r--drivers/mfd/max77665.c371
-rw-r--r--include/linux/mfd/max77665.h92
4 files changed, 477 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 5fe8ca433aed..9d476dd429ee 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -277,6 +277,19 @@ config MFD_88PM860X
select individual components like voltage regulators, RTC and
battery-charger 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_MAX77686
bool "Maxim Semiconductor MAX77686 PMIC Support"
depends on I2C=y && GENERIC_HARDIRQS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 88497db6c667..32465151b07c 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -105,6 +105,7 @@ obj-$(CONFIG_MFD_LP8788) += lp8788.o lp8788-irq.o
da9055-objs := da9055-core.o da9055-i2c.o
obj-$(CONFIG_MFD_DA9055) += da9055.o
+obj-$(CONFIG_MFD_MAX77665) += max77665.o
obj-$(CONFIG_MFD_MAX77686) += max77686.o max77686-irq.o
obj-$(CONFIG_MFD_MAX77693) += max77693.o max77693-irq.o
obj-$(CONFIG_MFD_MAX8907) += max8907.o
diff --git a/drivers/mfd/max77665.c b/drivers/mfd/max77665.c
new file mode 100644
index 000000000000..3c5755c82c1e
--- /dev/null
+++ b/drivers/mfd/max77665.c
@@ -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 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, NULL);
+ 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 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 = 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
index 000000000000..380a1a4aac0d
--- /dev/null
+++ b/include/linux/mfd/max77665.h
@@ -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 */