summaryrefslogtreecommitdiff
path: root/drivers/mfd
diff options
context:
space:
mode:
authorRobin Gong <B38343@freescale.com>2011-12-19 18:04:47 +0800
committerJason Liu <r64343@freescale.com>2012-01-09 21:12:07 +0800
commit9b1c100011041c9d3889334a5c339cdb7bd0fb3c (patch)
tree7e801a9bd4bb1a250f020293d9bfe9d6e6dac3de /drivers/mfd
parent960f8c3f4f3b739fdac3262bad168a9a7cb2d5aa (diff)
ENGR00170255-2 MX6Q PFUZE100: add pfuze100 driver
add pfuze core driver for mx6 Signed-off-by: Robin Gong <B38343@freescale.com>
Diffstat (limited to 'drivers/mfd')
-rwxr-xr-xdrivers/mfd/Kconfig7
-rwxr-xr-xdrivers/mfd/Makefile2
-rw-r--r--drivers/mfd/pfuze-core.c570
3 files changed, 577 insertions, 2 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 97f3f9d4f9e1..714b0fa625bb 100755
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -521,7 +521,12 @@ config MFD_MC34708
select MFD_MC_PMIC
help
Support for the Freescale's PMIC MC34708
-
+config MFD_PFUZE
+ tristate "Support for Freescale's PMIC PFUZE"
+ depends on I2C
+ select MFD_CORE
+ help
+ Support for Freescale's PMIC: PFUZE100
config PCF50633_ADC
tristate "Support for NXP PCF50633 ADC"
depends on MFD_PCF50633
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 58cace2fea97..347f39d66ede 100755
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -43,7 +43,7 @@ obj-$(CONFIG_TWL4030_CODEC) += twl4030-codec.o
obj-$(CONFIG_TWL6030_PWM) += twl6030-pwm.o
obj-$(CONFIG_MFD_MC13XXX) += mc13xxx-core.o
obj-$(CONFIG_MFD_MC_PMIC) += mc-pmic-core.o
-
+obj-$(CONFIG_MFD_PFUZE) += pfuze-core.o
obj-$(CONFIG_MFD_CORE) += mfd-core.o
obj-$(CONFIG_EZX_PCAP) += ezx-pcap.o
diff --git a/drivers/mfd/pfuze-core.c b/drivers/mfd/pfuze-core.c
new file mode 100644
index 000000000000..ae3a37e24025
--- /dev/null
+++ b/drivers/mfd/pfuze-core.c
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+/*#define VERBOSE_DEBUG*/
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/pfuze.h>
+#include <linux/delay.h>
+
+#define PFUZE_REG_DEVICE_ID 0
+#define PFUZE_REG_REVISION_ID 3
+#define PFUZE_REG_FAB_ID 4
+#define PFUZE_REG_INT_STATUS0 5
+#define PFUZE_REG_INT_MASK0 6
+#define PFUZE_REG_INT_SENSE0 7
+
+struct mc_pfuze {
+ struct i2c_client *i2c_client;
+ struct mutex lock;
+ int irq; /*irq number */
+ irq_handler_t irqhandler[PFUZE_NUM_IRQ];
+ void *irqdata[PFUZE_NUM_IRQ];
+};
+
+static const struct i2c_device_id pfuze_device_id[] = {
+ {"pfuze100", 0},
+ {},
+};
+
+enum pfuze_id {
+ PFUZE_ID_PFUZE100,
+ PFUZE_ID_INVALID,
+};
+static const char *pfuze_chipname[] = {
+ [PFUZE_ID_PFUZE100] = "pfuze100",
+};
+
+void pfuze_lock(struct mc_pfuze *mc_pfuze)
+{
+ if (!mutex_trylock(&mc_pfuze->lock)) {
+ dev_dbg(&mc_pfuze->i2c_client->dev, "wait for %s from %pf\n",
+ __func__, __builtin_return_address(0));
+ mutex_lock(&mc_pfuze->lock);
+ }
+ dev_dbg(&mc_pfuze->i2c_client->dev, "%s from %pf\n",
+ __func__, __builtin_return_address(0));
+}
+
+EXPORT_SYMBOL(pfuze_lock);
+
+void pfuze_unlock(struct mc_pfuze *mc_pfuze)
+{
+ dev_dbg(&mc_pfuze->i2c_client->dev, "%s from %pf\n", __func__,
+ __builtin_return_address(0));
+ mutex_unlock(&mc_pfuze->lock);
+}
+
+EXPORT_SYMBOL(pfuze_unlock);
+
+int pfuze_reg_read(struct mc_pfuze *mc_pfuze, unsigned int offset,
+ unsigned char *val)
+{
+ unsigned char data;
+ int ret, i;
+ BUG_ON(!mutex_is_locked(&mc_pfuze->lock));
+ if (offset > PFUZE_NUMREGS)
+ return -EINVAL;
+ data = (unsigned char)offset;
+ for (i = 0; i < PFUZE_I2C_RETRY_TIMES; i++) {
+ ret =
+ i2c_smbus_read_i2c_block_data(mc_pfuze->i2c_client, data, 1,
+ val);
+ if (ret == 1)
+ break;
+ msleep(1);
+ }
+ if (ret != 1) {
+ dev_err(&mc_pfuze->i2c_client->dev, "recv failed!:%i,%x\n", ret,
+ *val);
+ return -1;
+ }
+
+ return 0;
+}
+
+EXPORT_SYMBOL(pfuze_reg_read);
+
+int pfuze_reg_write(struct mc_pfuze *mc_pfuze, unsigned int offset,
+ unsigned char val)
+{
+ unsigned char buf[2];
+ int ret;
+
+ buf[0] = (unsigned char)offset;
+ memcpy(&buf[1], &val, 1);
+
+ ret = i2c_master_send(mc_pfuze->i2c_client, buf, 2);
+ if (ret != 2) {
+ dev_err(&mc_pfuze->i2c_client->dev, "write failed!:%i\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+EXPORT_SYMBOL(pfuze_reg_write);
+
+int pfuze_reg_rmw(struct mc_pfuze *mc_pfuze, unsigned int offset,
+ unsigned char mask, unsigned char val)
+{
+ int ret;
+ unsigned char valread;
+ BUG_ON(val & ~mask);
+ ret = pfuze_reg_read(mc_pfuze, offset, &valread);
+ if (ret)
+ return ret;
+ valread = (valread & ~mask) | val;
+ return pfuze_reg_write(mc_pfuze, offset, valread);
+}
+
+EXPORT_SYMBOL(pfuze_reg_rmw);
+
+int pfuze_irq_mask(struct mc_pfuze *mc_pfuze, int irq)
+{
+ int ret;
+ unsigned int offmask = PFUZE_REG_INT_MASK0 + 3 * (irq / 8);
+ unsigned char irqbit = 1 << (irq % 8);
+ unsigned char mask;
+
+ if (irq < 0 || irq >= PFUZE_NUM_IRQ)
+ return -EINVAL;
+
+ ret = pfuze_reg_read(mc_pfuze, offmask, &mask);
+ if (ret)
+ return ret;
+ if (mask & irqbit)
+ /* already masked */
+ return 0;
+ return pfuze_reg_write(mc_pfuze, offmask, mask | irqbit);
+}
+
+EXPORT_SYMBOL(pfuze_irq_mask);
+
+int pfuze_irq_unmask(struct mc_pfuze *mc_pfuze, int irq)
+{
+ int ret;
+ unsigned int offmask = PFUZE_REG_INT_MASK0 + 3 * (irq / 8);
+ unsigned char irqbit = 1 << (irq % 8);
+ unsigned char mask;
+
+ if (irq < 0 || irq >= PFUZE_NUM_IRQ)
+ return -EINVAL;
+
+ ret = pfuze_reg_read(mc_pfuze, offmask, &mask);
+ if (ret)
+ return ret;
+ if (!(mask & irqbit))
+ /* already masked */
+ return 0;
+ return pfuze_reg_write(mc_pfuze, offmask, mask & ~irqbit);
+}
+
+EXPORT_SYMBOL(pfuze_irq_unmask);
+
+int pfuze_irq_status(struct mc_pfuze *mc_pfuze, int irq, int *enabled,
+ int *pending)
+{
+ int ret;
+ unsigned int offmask = PFUZE_REG_INT_MASK0 + 3 * (irq / 8);
+ unsigned int offstat = PFUZE_REG_INT_STATUS0 + 3 * (irq / 8);
+ unsigned char irqbit = 1 << (irq % 8);
+
+ if (irq < 0 || irq >= PFUZE_NUM_IRQ)
+ return -EINVAL;
+ if (enabled) {
+ unsigned char mask;
+ ret = pfuze_reg_read(mc_pfuze, offmask, &mask);
+ if (ret)
+ return ret;
+ *enabled = mask & irqbit;
+ }
+ if (pending) {
+ unsigned char stat;
+ ret = pfuze_reg_read(mc_pfuze, offstat, &stat);
+ if (ret)
+ return ret;
+ *pending = stat & irqbit;
+ }
+ return 0;
+}
+
+EXPORT_SYMBOL(pfuze_irq_status);
+
+int pfuze_irq_ack(struct mc_pfuze *mc_pfuze, int irq)
+{
+ unsigned int offstat = PFUZE_REG_INT_STATUS0 + 3 * (irq / 8);
+ unsigned char val = 1 << (irq % 8);
+ BUG_ON(irq < 0 || irq >= PFUZE_NUM_IRQ);
+ return pfuze_reg_write(mc_pfuze, offstat, val);
+}
+
+EXPORT_SYMBOL(pfuze_irq_ack);
+
+int pfuze_irq_request_nounmask(struct mc_pfuze *mc_pfuze, int irq,
+ irq_handler_t handler, const char *name,
+ void *dev)
+{
+ BUG_ON(!mutex_is_locked(&mc_pfuze->lock));
+ BUG_ON(!handler);
+
+ if (irq < 0 || irq >= PFUZE_NUM_IRQ)
+ return -EINVAL;
+ if (mc_pfuze->irqhandler[irq])
+ return -EBUSY;
+ mc_pfuze->irqhandler[irq] = handler;
+ mc_pfuze->irqdata[irq] = dev;
+
+ return 0;
+}
+
+EXPORT_SYMBOL(pfuze_irq_request_nounmask);
+
+int pfuze_irq_request(struct mc_pfuze *mc_pfuze, int irq,
+ irq_handler_t handler, const char *name, void *dev)
+{
+ int ret;
+
+ ret = pfuze_irq_request_nounmask(mc_pfuze, irq, handler, name, dev);
+ if (ret)
+ return ret;
+ ret = pfuze_irq_unmask(mc_pfuze, irq);
+ if (ret) {
+ mc_pfuze->irqhandler[irq] = NULL;
+ mc_pfuze->irqdata[irq] = NULL;
+ return ret;
+ }
+ return 0;
+}
+
+EXPORT_SYMBOL(pfuze_irq_request);
+
+int pfuze_irq_free(struct mc_pfuze *mc_pfuze, int irq, void *dev)
+{
+ int ret;
+ BUG_ON(!mutex_is_locked(&mc_pfuze->lock));
+
+ if (irq < 0 || irq >= PFUZE_NUM_IRQ || !mc_pfuze->irqhandler[irq] ||
+ mc_pfuze->irqdata[irq] != dev)
+ return -EINVAL;
+
+ ret = pfuze_irq_mask(mc_pfuze, irq);
+ if (ret)
+ return ret;
+ mc_pfuze->irqhandler[irq] = NULL;
+ mc_pfuze->irqdata[irq] = NULL;
+ return 0;
+}
+
+EXPORT_SYMBOL(pfuze_irq_free);
+
+static inline irqreturn_t pfuze_irqhandler(struct mc_pfuze *mc_pfuze, int irq)
+{
+ return mc_pfuze->irqhandler[irq] (irq, mc_pfuze->irqdata[irq]);
+}
+
+static int pfuze_irq_handle(struct mc_pfuze *mc_pfuze, unsigned int offstat,
+ unsigned int offmask, int baseirq)
+{
+ unsigned char stat, mask;
+ int ret = pfuze_reg_read(mc_pfuze, offstat, &stat);
+ int num_handled = 0;
+
+ if (ret)
+ return ret;
+ ret = pfuze_reg_read(mc_pfuze, offmask, &mask);
+ if (ret)
+ return ret;
+ while (stat & ~mask) {
+ unsigned char irq = __ffs(stat & ~mask);
+ stat &= ~(1 << irq);
+ if (likely(mc_pfuze->irqhandler[baseirq + irq])) {
+ irqreturn_t handled;
+ handled = pfuze_irqhandler(mc_pfuze, baseirq + irq);
+ if (handled == IRQ_HANDLED)
+ num_handled++;
+ } else {
+ dev_err(&mc_pfuze->i2c_client->dev,
+ "BUG: irq %u but no handler\n", baseirq + irq);
+ mask |= 1 << irq;
+ ret = pfuze_reg_write(mc_pfuze, offmask, mask);
+ }
+ }
+ return num_handled;
+}
+
+static irqreturn_t pfuze_irq_thread(int irq, void *data)
+{
+ struct mc_pfuze *mc_pfuze = data;
+ irqreturn_t ret;
+ int i, handled = 0;
+
+ pfuze_lock(mc_pfuze);
+ for (i = 0; i < 7; i++) {
+ ret = pfuze_irq_handle(mc_pfuze, PFUZE_REG_INT_STATUS0 + 3 * i,
+ PFUZE_REG_INT_MASK0 + 3 * i, 8 * i);
+ if (ret > 0)
+ handled = 1;
+ }
+ pfuze_unlock(mc_pfuze);
+ return IRQ_RETVAL(handled);
+}
+
+static int pfuze_identify(struct mc_pfuze *mc_pfuze, enum pfuze_id *id)
+{
+ unsigned char value;
+ int ret;
+ ret = pfuze_reg_read(mc_pfuze, PFUZE_REG_DEVICE_ID, &value);
+ if (ret)
+ return ret;
+ switch (value & 0x0f) {
+ case 0x0:
+ *id = PFUZE_ID_PFUZE100;
+ break;
+ default:
+ *id = PFUZE_ID_INVALID;
+ dev_warn(&mc_pfuze->i2c_client->dev, "Illegal ID: %x\n", value);
+ break;
+ }
+ ret = pfuze_reg_read(mc_pfuze, PFUZE_REG_REVISION_ID, &value);
+ if (ret)
+ return ret;
+ dev_info(&mc_pfuze->i2c_client->dev, "Full lay: %x ,Metal lay: %x\n",
+ (value & 0xf0) >> 4, value & 0x0f);
+ ret = pfuze_reg_read(mc_pfuze, PFUZE_REG_FAB_ID, &value);
+ if (ret)
+ return ret;
+ dev_info(&mc_pfuze->i2c_client->dev, "FAB: %x ,FIN: %x\n",
+ (value & 0xc) >> 2, value & 0x3);
+ return 0;
+}
+
+static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
+ const struct i2c_client *client)
+{
+ while (id->name[0]) {
+ if (strcmp(client->name, id->name) == 0)
+ return id;
+ id++;
+ }
+ return NULL;
+
+}
+
+static const char *pfuze_get_chipname(struct mc_pfuze *mc_pfuze)
+{
+ const struct i2c_driver *idrv =
+ to_i2c_driver(mc_pfuze->i2c_client->dev.driver);
+ const struct i2c_device_id *devid =
+ i2c_match_id(idrv->id_table, mc_pfuze->i2c_client);
+ if (!devid)
+ return NULL;
+ return pfuze_chipname[devid->driver_data];
+}
+
+unsigned int pfuze_get_flags(struct mc_pfuze *mc_pfuze)
+{
+ struct pfuze_platform_data *pdata =
+ dev_get_platdata(&mc_pfuze->i2c_client->dev);
+ return pdata->flags;
+}
+
+EXPORT_SYMBOL(pfuze_get_flags);
+
+static int pfuze_add_subdevice_pdata(struct mc_pfuze *mc_pfuze,
+ const char *format, void *pdata,
+ size_t pdata_size)
+{
+ char buf[30];
+ const char *name = pfuze_get_chipname(mc_pfuze);
+ struct mfd_cell cell = {
+ .platform_data = pdata,
+ .data_size = pdata_size,
+ };
+ if (snprintf(buf, sizeof(buf), format, name) > sizeof(buf))
+ return -E2BIG;
+ cell.name = kmemdup(buf, strlen(buf) + 1, GFP_KERNEL);
+ if (!cell.name)
+ return -ENOMEM;
+ return mfd_add_devices(&mc_pfuze->i2c_client->dev, -1, &cell, 1, NULL,
+ 0);
+}
+
+static int pfuze_add_subdevice(struct mc_pfuze *mc_pfuze, const char *format)
+{
+ return pfuze_add_subdevice_pdata(mc_pfuze, format, NULL, 0);
+}
+
+static ssize_t pfuze_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ unsigned char i, value;
+ int offset = PFUZE_NUMREGS;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mc_pfuze *mc_pfuze = i2c_get_clientdata(client);
+
+ pfuze_lock(mc_pfuze);
+ for (i = 0; i < offset; i++) {
+ pfuze_reg_read(mc_pfuze, i, &value);
+ pr_info("reg%03d: %02x\t\t", i, value);
+ }
+ pfuze_unlock(mc_pfuze);
+ pr_info("\n");
+
+ return 0;
+}
+
+static ssize_t pfuze_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ unsigned char reg, value;
+ int ret;
+ char *p;
+ struct i2c_client *client = to_i2c_client(dev);
+ struct mc_pfuze *mc_pfuze = i2c_get_clientdata(client);
+
+ reg = simple_strtoul(buf, NULL, 10);
+
+ p = NULL;
+ p = memchr(buf, ' ', count);
+
+ if (p == NULL) {
+ pfuze_lock(mc_pfuze);
+ pfuze_reg_read(mc_pfuze, reg, &value);
+ pfuze_unlock(mc_pfuze);
+ pr_debug("reg%03d: %02x\n", reg, value);
+ return count;
+ }
+
+ p += 1;
+
+ value = simple_strtoul(p, NULL, 16);
+ pfuze_lock(mc_pfuze);
+ ret = pfuze_reg_write(mc_pfuze, reg, value);
+ if (ret == 0)
+ pr_debug("write reg%03d: %02x\n", reg, value);
+ else
+ pr_debug("register update failed\n");
+
+ pfuze_unlock(mc_pfuze);
+ return count;
+}
+
+static struct device_attribute pfuze_dev_attr = {
+ .attr = {
+ .name = "pfuze_ctl",
+ .mode = S_IRUSR | S_IWUSR,
+ },
+ .show = pfuze_show,
+ .store = pfuze_store,
+};
+
+static int pfuze_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct mc_pfuze *mc_pfuze;
+ struct pfuze_platform_data *pdata = client->dev.platform_data;
+ int ret, i;
+ enum pfuze_id pfuze_id;
+ mc_pfuze = kzalloc(sizeof(*mc_pfuze), GFP_KERNEL);
+ if (!mc_pfuze)
+ return -ENOMEM;
+ i2c_set_clientdata(client, mc_pfuze);
+ mc_pfuze->i2c_client = client;
+
+ mutex_init(&mc_pfuze->lock);
+ pfuze_lock(mc_pfuze);
+ ret = pfuze_identify(mc_pfuze, &pfuze_id);
+ if (ret || pfuze_id == PFUZE_ID_INVALID)
+ goto err_revision;
+ /* mask all of irqs */
+ for (i = 0; i < 7; i++) {
+ ret =
+ pfuze_reg_write(mc_pfuze, PFUZE_REG_INT_MASK0 + 3 * i,
+ 0xff);
+ if (ret)
+ goto err_mask;
+ }
+ ret = request_threaded_irq(client->irq, NULL, pfuze_irq_thread,
+ IRQF_ONESHOT | IRQF_TRIGGER_LOW, "pfuze",
+ mc_pfuze);
+ if (ret) {
+ err_mask:
+ err_revision:
+ pfuze_unlock(mc_pfuze);
+ dev_set_drvdata(&client->dev, NULL);
+ kfree(mc_pfuze);
+ return ret;
+ }
+ pfuze_unlock(mc_pfuze);
+ if (pdata->flags & PFUZE_USE_REGULATOR) {
+ struct pfuze_regulator_platform_data regulator_pdata = {
+ .num_regulators = pdata->num_regulators,
+ .regulators = pdata->regulators,
+ };
+ pfuze_add_subdevice_pdata(mc_pfuze, "%s-regulator",
+ &regulator_pdata,
+ sizeof(regulator_pdata));
+ }
+ ret = device_create_file(&client->dev, &pfuze_dev_attr);
+ if (ret)
+ dev_err(&client->dev, "create device file failed!\n");
+ return 0;
+}
+
+static int __devexit pfuze_remove(struct i2c_client *client)
+{
+ struct mc_pfuze *mc_pfuze = dev_get_drvdata(&client->dev);
+ free_irq(mc_pfuze->i2c_client->irq, mc_pfuze);
+ mfd_remove_devices(&client->dev);
+ kfree(mc_pfuze);
+ return 0;
+}
+
+static struct i2c_driver pfuze_driver = {
+ .id_table = pfuze_device_id,
+ .driver = {
+ .name = "mc_pfuze",
+ .owner = THIS_MODULE,
+ },
+ .probe = pfuze_probe,
+ .remove = __devexit_p(pfuze_remove),
+};
+
+static int __init pfuze_init(void)
+{
+ return i2c_add_driver(&pfuze_driver);
+}
+
+subsys_initcall(pfuze_init);
+
+static void __exit pfuze_exit(void)
+{
+ i2c_del_driver(&pfuze_driver);
+}
+
+module_exit(pfuze_exit);
+
+MODULE_DESCRIPTION("Core driver for Freescale PFUZE PMIC");
+MODULE_AUTHOR("Freescale Semiconductor, Inc");
+MODULE_LICENSE("GPL v2");