mfd: Add an AS3711 PMIC MFD driver
authorGuennadi Liakhovetski <g.liakhovetski@gmx.de>
Thu, 22 Nov 2012 10:12:04 +0000 (11:12 +0100)
committerSamuel Ortiz <sameo@linux.intel.com>
Fri, 23 Nov 2012 11:09:19 +0000 (12:09 +0100)
AS3711 is a PMIC with multiple DCDC and LDO power supplies, GPIOs, an RTC,
a battery charger and a general purpose ADC. This patch adds support for
the MFD with support for a regulator driver and a backlight driver.

Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
drivers/mfd/Kconfig
drivers/mfd/Makefile
drivers/mfd/as3711.c [new file with mode: 0644]
include/linux/mfd/as3711.h [new file with mode: 0644]

index f5b839b718aa8c72dc10631c25705869ba486129..475c26696b954adbde8e636642bc2b886aa15bdf 100644 (file)
@@ -1103,6 +1103,15 @@ config MFD_RETU
          Retu is a multi-function device found on Nokia Internet Tablets
          (770, N800 and N810).
 
+config MFD_AS3711
+       bool "Support for AS3711"
+       select MFD_CORE
+       select REGMAP_I2C
+       select REGMAP_IRQ
+       depends on I2C=y
+       help
+         Support for the AS3711 PMIC from AMS
+
 endmenu
 endif
 
index 2689c8a0d78131c2fbc3d24fac037093a327f3d6..f2216dfd963c677cc586aaf80b4bb0df0fa0aa40 100644 (file)
@@ -146,3 +146,4 @@ obj-$(CONFIG_MFD_SEC_CORE)  += sec-core.o sec-irq.o
 obj-$(CONFIG_MFD_SYSCON)       += syscon.o
 obj-$(CONFIG_MFD_LM3533)       += lm3533-core.o lm3533-ctrlbank.o
 obj-$(CONFIG_MFD_RETU)         += retu-mfd.o
+obj-$(CONFIG_MFD_AS3711)       += as3711.o
diff --git a/drivers/mfd/as3711.c b/drivers/mfd/as3711.c
new file mode 100644 (file)
index 0000000..e994c96
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * AS3711 PMIC MFC driver
+ *
+ * Copyright (C) 2012 Renesas Electronics Corporation
+ * Author: Guennadi Liakhovetski, <g.liakhovetski@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the version 2 of the GNU General Public License as
+ * published by the Free Software Foundation
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mfd/as3711.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+enum {
+       AS3711_REGULATOR,
+       AS3711_BACKLIGHT,
+};
+
+/*
+ * Ok to have it static: it is only used during probing and multiple I2C devices
+ * cannot be probed simultaneously. Just make sure to avoid stale data.
+ */
+static struct mfd_cell as3711_subdevs[] = {
+       [AS3711_REGULATOR] = {.name = "as3711-regulator",},
+       [AS3711_BACKLIGHT] = {.name = "as3711-backlight",},
+};
+
+static bool as3711_volatile_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case AS3711_GPIO_SIGNAL_IN:
+       case AS3711_INTERRUPT_STATUS_1:
+       case AS3711_INTERRUPT_STATUS_2:
+       case AS3711_INTERRUPT_STATUS_3:
+       case AS3711_CHARGER_STATUS_1:
+       case AS3711_CHARGER_STATUS_2:
+       case AS3711_REG_STATUS:
+               return true;
+       }
+       return false;
+}
+
+static bool as3711_precious_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case AS3711_INTERRUPT_STATUS_1:
+       case AS3711_INTERRUPT_STATUS_2:
+       case AS3711_INTERRUPT_STATUS_3:
+               return true;
+       }
+       return false;
+}
+
+static bool as3711_readable_reg(struct device *dev, unsigned int reg)
+{
+       switch (reg) {
+       case AS3711_SD_1_VOLTAGE:
+       case AS3711_SD_2_VOLTAGE:
+       case AS3711_SD_3_VOLTAGE:
+       case AS3711_SD_4_VOLTAGE:
+       case AS3711_LDO_1_VOLTAGE:
+       case AS3711_LDO_2_VOLTAGE:
+       case AS3711_LDO_3_VOLTAGE:
+       case AS3711_LDO_4_VOLTAGE:
+       case AS3711_LDO_5_VOLTAGE:
+       case AS3711_LDO_6_VOLTAGE:
+       case AS3711_LDO_7_VOLTAGE:
+       case AS3711_LDO_8_VOLTAGE:
+       case AS3711_SD_CONTROL:
+       case AS3711_GPIO_SIGNAL_OUT:
+       case AS3711_GPIO_SIGNAL_IN:
+       case AS3711_SD_CONTROL_1:
+       case AS3711_SD_CONTROL_2:
+       case AS3711_CURR_CONTROL:
+       case AS3711_CURR1_VALUE:
+       case AS3711_CURR2_VALUE:
+       case AS3711_CURR3_VALUE:
+       case AS3711_STEPUP_CONTROL_1:
+       case AS3711_STEPUP_CONTROL_2:
+       case AS3711_STEPUP_CONTROL_4:
+       case AS3711_STEPUP_CONTROL_5:
+       case AS3711_REG_STATUS:
+       case AS3711_INTERRUPT_STATUS_1:
+       case AS3711_INTERRUPT_STATUS_2:
+       case AS3711_INTERRUPT_STATUS_3:
+       case AS3711_CHARGER_STATUS_1:
+       case AS3711_CHARGER_STATUS_2:
+       case AS3711_ASIC_ID_1:
+       case AS3711_ASIC_ID_2:
+               return true;
+       }
+       return false;
+}
+
+static const struct regmap_config as3711_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .volatile_reg = as3711_volatile_reg,
+       .readable_reg = as3711_readable_reg,
+       .precious_reg = as3711_precious_reg,
+       .max_register = AS3711_MAX_REGS,
+       .num_reg_defaults_raw = AS3711_MAX_REGS,
+       .cache_type = REGCACHE_RBTREE,
+};
+
+static int as3711_i2c_probe(struct i2c_client *client,
+                           const struct i2c_device_id *id)
+{
+       struct as3711 *as3711;
+       struct as3711_platform_data *pdata = client->dev.platform_data;
+       unsigned int id1, id2;
+       int ret;
+
+       if (!pdata)
+               dev_dbg(&client->dev, "Platform data not found\n");
+
+       as3711 = devm_kzalloc(&client->dev, sizeof(struct as3711), GFP_KERNEL);
+       if (!as3711) {
+               dev_err(&client->dev, "Memory allocation failed\n");
+               return -ENOMEM;
+       }
+
+       as3711->dev = &client->dev;
+       i2c_set_clientdata(client, as3711);
+
+       if (client->irq)
+               dev_notice(&client->dev, "IRQ not supported yet\n");
+
+       as3711->regmap = devm_regmap_init_i2c(client, &as3711_regmap_config);
+       if (IS_ERR(as3711->regmap)) {
+               ret = PTR_ERR(as3711->regmap);
+               dev_err(&client->dev, "regmap initialization failed: %d\n", ret);
+               return ret;
+       }
+
+       ret = regmap_read(as3711->regmap, AS3711_ASIC_ID_1, &id1);
+       if (!ret)
+               ret = regmap_read(as3711->regmap, AS3711_ASIC_ID_2, &id2);
+       if (ret < 0) {
+               dev_err(&client->dev, "regmap_read() failed: %d\n", ret);
+               return ret;
+       }
+       if (id1 != 0x8b)
+               return -ENODEV;
+       dev_info(as3711->dev, "AS3711 detected: %x:%x\n", id1, id2);
+
+       /* We can reuse as3711_subdevs[], it will be copied in mfd_add_devices() */
+       if (pdata) {
+               as3711_subdevs[AS3711_REGULATOR].platform_data = &pdata->regulator;
+               as3711_subdevs[AS3711_REGULATOR].pdata_size = sizeof(pdata->regulator);
+               as3711_subdevs[AS3711_BACKLIGHT].platform_data = &pdata->backlight;
+               as3711_subdevs[AS3711_BACKLIGHT].pdata_size = sizeof(pdata->backlight);
+       } else {
+               as3711_subdevs[AS3711_REGULATOR].platform_data = NULL;
+               as3711_subdevs[AS3711_REGULATOR].pdata_size = 0;
+               as3711_subdevs[AS3711_BACKLIGHT].platform_data = NULL;
+               as3711_subdevs[AS3711_BACKLIGHT].pdata_size = 0;
+       }
+
+       ret = mfd_add_devices(as3711->dev, -1, as3711_subdevs,
+                             ARRAY_SIZE(as3711_subdevs), NULL, 0, NULL);
+       if (ret < 0)
+               dev_err(&client->dev, "add mfd devices failed: %d\n", ret);
+
+       return ret;
+}
+
+static int as3711_i2c_remove(struct i2c_client *client)
+{
+       struct as3711 *as3711 = i2c_get_clientdata(client);
+
+       mfd_remove_devices(as3711->dev);
+       return 0;
+}
+
+static const struct i2c_device_id as3711_i2c_id[] = {
+       {.name = "as3711", .driver_data = 0},
+       {}
+};
+
+MODULE_DEVICE_TABLE(i2c, as3711_i2c_id);
+
+static struct i2c_driver as3711_i2c_driver = {
+       .driver = {
+                  .name = "as3711",
+                  .owner = THIS_MODULE,
+                  },
+       .probe = as3711_i2c_probe,
+       .remove = as3711_i2c_remove,
+       .id_table = as3711_i2c_id,
+};
+
+static int __init as3711_i2c_init(void)
+{
+       return i2c_add_driver(&as3711_i2c_driver);
+}
+/* Initialise early */
+subsys_initcall(as3711_i2c_init);
+
+static void __exit as3711_i2c_exit(void)
+{
+       i2c_del_driver(&as3711_i2c_driver);
+}
+module_exit(as3711_i2c_exit);
+
+MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de>");
+MODULE_DESCRIPTION("AS3711 PMIC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/mfd/as3711.h b/include/linux/mfd/as3711.h
new file mode 100644 (file)
index 0000000..38452ce
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * AS3711 PMIC MFC driver header
+ *
+ * Copyright (C) 2012 Renesas Electronics Corporation
+ * Author: Guennadi Liakhovetski, <g.liakhovetski@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the version 2 of the GNU General Public License as
+ * published by the Free Software Foundation
+ */
+
+#ifndef MFD_AS3711_H
+#define MFD_AS3711_H
+
+/*
+ * Client data
+ */
+
+/* Register addresses */
+#define AS3711_SD_1_VOLTAGE            0       /* Digital Step-Down */
+#define AS3711_SD_2_VOLTAGE            1
+#define AS3711_SD_3_VOLTAGE            2
+#define AS3711_SD_4_VOLTAGE            3
+#define AS3711_LDO_1_VOLTAGE           4       /* Analog LDO */
+#define AS3711_LDO_2_VOLTAGE           5
+#define AS3711_LDO_3_VOLTAGE           6       /* Digital LDO */
+#define AS3711_LDO_4_VOLTAGE           7
+#define AS3711_LDO_5_VOLTAGE           8
+#define AS3711_LDO_6_VOLTAGE           9
+#define AS3711_LDO_7_VOLTAGE           0xa
+#define AS3711_LDO_8_VOLTAGE           0xb
+#define AS3711_SD_CONTROL              0x10
+#define AS3711_GPIO_SIGNAL_OUT         0x20
+#define AS3711_GPIO_SIGNAL_IN          0x21
+#define AS3711_SD_CONTROL_1            0x30
+#define AS3711_SD_CONTROL_2            0x31
+#define AS3711_CURR_CONTROL            0x40
+#define AS3711_CURR1_VALUE             0x43
+#define AS3711_CURR2_VALUE             0x44
+#define AS3711_CURR3_VALUE             0x45
+#define AS3711_STEPUP_CONTROL_1                0x50
+#define AS3711_STEPUP_CONTROL_2                0x51
+#define AS3711_STEPUP_CONTROL_4                0x53
+#define AS3711_STEPUP_CONTROL_5                0x54
+#define AS3711_REG_STATUS              0x73
+#define AS3711_INTERRUPT_STATUS_1      0x77
+#define AS3711_INTERRUPT_STATUS_2      0x78
+#define AS3711_INTERRUPT_STATUS_3      0x79
+#define AS3711_CHARGER_STATUS_1                0x86
+#define AS3711_CHARGER_STATUS_2                0x87
+#define AS3711_ASIC_ID_1               0x90
+#define AS3711_ASIC_ID_2               0x91
+
+#define AS3711_MAX_REGS                        0x92
+
+/* Regulators */
+enum {
+       AS3711_REGULATOR_SD_1,
+       AS3711_REGULATOR_SD_2,
+       AS3711_REGULATOR_SD_3,
+       AS3711_REGULATOR_SD_4,
+       AS3711_REGULATOR_LDO_1,
+       AS3711_REGULATOR_LDO_2,
+       AS3711_REGULATOR_LDO_3,
+       AS3711_REGULATOR_LDO_4,
+       AS3711_REGULATOR_LDO_5,
+       AS3711_REGULATOR_LDO_6,
+       AS3711_REGULATOR_LDO_7,
+       AS3711_REGULATOR_LDO_8,
+
+       AS3711_REGULATOR_MAX,
+};
+
+struct device;
+struct regmap;
+
+struct as3711 {
+       struct device *dev;
+       struct regmap *regmap;
+};
+
+#define AS3711_MAX_STEPDOWN 4
+#define AS3711_MAX_STEPUP 2
+#define AS3711_MAX_LDO 8
+
+enum as3711_su2_feedback {
+       AS3711_SU2_VOLTAGE,
+       AS3711_SU2_CURR1,
+       AS3711_SU2_CURR2,
+       AS3711_SU2_CURR3,
+       AS3711_SU2_CURR_AUTO,
+};
+
+enum as3711_su2_fbprot {
+       AS3711_SU2_LX_SD4,
+       AS3711_SU2_GPIO2,
+       AS3711_SU2_GPIO3,
+       AS3711_SU2_GPIO4,
+};
+
+/*
+ * Platform data
+ */
+
+struct as3711_regulator_pdata {
+       struct regulator_init_data *init_data[AS3711_REGULATOR_MAX];
+};
+
+struct as3711_bl_pdata {
+       const char *su1_fb;
+       int su1_max_uA;
+       const char *su2_fb;
+       int su2_max_uA;
+       enum as3711_su2_feedback su2_feedback;
+       enum as3711_su2_fbprot su2_fbprot;
+       bool su2_auto_curr1;
+       bool su2_auto_curr2;
+       bool su2_auto_curr3;
+};
+
+struct as3711_platform_data {
+       struct as3711_regulator_pdata regulator;
+       struct as3711_bl_pdata backlight;
+};
+
+#endif