* [PATCH v2 1/5] mfd: max14577: Add max14577 MFD driver core
2013-11-20 14:12 [PATCH v2 0/5] mfd: max14577: Add max14577 MFD drivers Krzysztof Kozlowski
@ 2013-11-20 14:12 ` Krzysztof Kozlowski
[not found] ` <1384956732-19526-2-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
2013-11-21 10:34 ` Lee Jones
2013-11-20 14:12 ` [PATCH v2 2/5] extcon: max14577: Add extcon-max14577 driver to support MUIC device Krzysztof Kozlowski
` (3 subsequent siblings)
4 siblings, 2 replies; 16+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-20 14:12 UTC (permalink / raw)
To: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Lee Jones,
Anton Vorontsov, David Woodhouse, Liam Girdwood, Mark Brown,
Grant Likely, Rob Herring, linux-kernel, devicetree, Pawel Moll,
Stephen Warren, Ian Campbell, Rob Landley, linux-doc,
linux-arm-kernel
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park,
Krzysztof Kozlowski
From: Chanwoo Choi <cw00.choi@samsung.com>
This patch adds max14577 core/irq driver to support MUIC(Micro USB IC)
device and charger device and support irq domain method to control
internal interrupt of max14577 device. Also, this patch supports DT
binding with max14577_i2c_parse_dt().
The MAXIM 14577 chip contains Micro-USB Interface Circuit and Li+ Battery
Charger. It contains accessory and USB charger detection logic. It supports
USB 2.0 Hi-Speed, UART and stereo audio signals over Micro-USB connector.
The battery charger is compliant with the USB Battery Charging Specification
Revision 1.1. It has also SFOUT LDO output for powering USB devices.
Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
Signed-off-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
drivers/mfd/Kconfig | 13 ++
drivers/mfd/Makefile | 1 +
drivers/mfd/max14577-irq.c | 245 ++++++++++++++++++++++++++
drivers/mfd/max14577.c | 216 +++++++++++++++++++++++
include/linux/mfd/max14577-private.h | 317 ++++++++++++++++++++++++++++++++++
include/linux/mfd/max14577.h | 72 ++++++++
6 files changed, 864 insertions(+)
create mode 100644 drivers/mfd/max14577-irq.c
create mode 100644 drivers/mfd/max14577.c
create mode 100644 include/linux/mfd/max14577-private.h
create mode 100644 include/linux/mfd/max14577.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 62a60ca..3da452a4 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -321,6 +321,19 @@ config MFD_88PM860X
select individual components like voltage regulators, RTC and
battery-charger under the corresponding menus.
+config MFD_MAX14577
+ bool "Maxim Semiconductor MAX14577 MUIC + Charger Support"
+ depends on I2C=y
+ select MFD_CORE
+ select REGMAP_I2C
+ select IRQ_DOMAIN
+ help
+ Say yes here to support for Maxim Semiconductor MAX14577.
+ This is a Micro-USB IC with Charger 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
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 8a28dc9..3feaffc 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -110,6 +110,7 @@ obj-$(CONFIG_MFD_DA9055) += da9055.o
da9063-objs := da9063-core.o da9063-irq.o da9063-i2c.o
obj-$(CONFIG_MFD_DA9063) += da9063.o
+obj-$(CONFIG_MFD_MAX14577) += max14577.o max14577-irq.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/max14577-irq.c b/drivers/mfd/max14577-irq.c
new file mode 100644
index 0000000..bbb6356
--- /dev/null
+++ b/drivers/mfd/max14577-irq.c
@@ -0,0 +1,245 @@
+/*
+ * max14577-irq.c - MFD Interrupt controller support for MAX14577
+ *
+ * Copyright (C) 2013 Samsung Electronics Co.Ltd
+ * Chanwoo Choi <cw00.choi@samsung.com>
+ * Krzysztof Kozlowski <k.kozlowski@samsung.com>
+ *
+ * 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.
+ *
+ * This driver is based on max8997-irq.c
+ */
+
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/mfd/max14577.h>
+#include <linux/mfd/max14577-private.h>
+
+/**
+ * After resuming from suspend it may happen that IRQ is signalled but
+ * IRQ GPIO is not high. Also the interrupt registers won't have any data
+ * (all of them equal to 0x00).
+ *
+ * In such case retry few times reading the interrupt registers.
+ */
+#define IRQ_READ_REG_RETRY_CNT 5
+
+static const u8 max14577_mask_reg[] = {
+ [MAX14577_IRQ_INT1] = MAX14577_REG_INTMASK1,
+ [MAX14577_IRQ_INT2] = MAX14577_REG_INTMASK2,
+ [MAX14577_IRQ_INT3] = MAX14577_REG_INTMASK3,
+};
+
+struct max14577_irq_data {
+ int mask;
+ enum max14577_irq_source group;
+};
+
+#define DECLARE_IRQ(idx, _group, _mask) \
+ [(idx)] = { .group = (_group), .mask = (_mask) }
+static const struct max14577_irq_data max14577_irqs[] = {
+ DECLARE_IRQ(MAX14577_IRQ_INT1_ADC, MAX14577_IRQ_INT1, 1 << 0),
+ DECLARE_IRQ(MAX14577_IRQ_INT1_ADCLOW, MAX14577_IRQ_INT1, 1 << 1),
+ DECLARE_IRQ(MAX14577_IRQ_INT1_ADCERR, MAX14577_IRQ_INT1, 1 << 2),
+
+ DECLARE_IRQ(MAX14577_IRQ_INT2_CHGTYP, MAX14577_IRQ_INT2, 1 << 0),
+ DECLARE_IRQ(MAX14577_IRQ_INT2_CHGDETRUN, MAX14577_IRQ_INT2, 1 << 1),
+ DECLARE_IRQ(MAX14577_IRQ_INT2_DCDTMR, MAX14577_IRQ_INT2, 1 << 2),
+ DECLARE_IRQ(MAX14577_IRQ_INT2_DBCHG, MAX14577_IRQ_INT2, 1 << 3),
+ DECLARE_IRQ(MAX14577_IRQ_INT2_VBVOLT, MAX14577_IRQ_INT2, 1 << 4),
+
+ DECLARE_IRQ(MAX14577_IRQ_INT3_EOC, MAX14577_IRQ_INT3, 1 << 0),
+ DECLARE_IRQ(MAX14577_IRQ_INT3_CGMBC, MAX14577_IRQ_INT3, 1 << 1),
+ DECLARE_IRQ(MAX14577_IRQ_INT3_OVP, MAX14577_IRQ_INT3, 1 << 2),
+ DECLARE_IRQ(MAX14577_IRQ_INT3_MBCCHGERR, MAX14577_IRQ_INT3, 1 << 3),
+};
+
+static void max14577_irq_lock(struct irq_data *data)
+{
+ struct max14577 *max14577 = irq_get_chip_data(data->irq);
+
+ mutex_lock(&max14577->irq_lock);
+}
+
+static void max14577_irq_sync_unlock(struct irq_data *data)
+{
+ struct max14577 *max14577 = irq_get_chip_data(data->irq);
+ int i;
+
+ for (i = 0; i < MAX14577_IRQ_REGS_NUM; i++) {
+ u8 mask_reg = max14577_mask_reg[i];
+
+ if (mask_reg == MAX14577_REG_INVALID)
+ continue;
+
+ max14577->irq_masks_cache[i] = max14577->irq_masks_cur[i];
+
+ max14577_write_reg(max14577->regmap, max14577_mask_reg[i],
+ max14577->irq_masks_cur[i]);
+ }
+
+ mutex_unlock(&max14577->irq_lock);
+}
+
+static const inline struct max14577_irq_data *
+irq_to_max14577_irq(struct max14577 *max14577, int irq)
+{
+ struct irq_data *data = irq_get_irq_data(irq);
+ return &max14577_irqs[data->hwirq];
+}
+
+static void max14577_irq_mask(struct irq_data *data)
+{
+ struct max14577 *max14577 = irq_get_chip_data(data->irq);
+ const struct max14577_irq_data *irq_data =
+ irq_to_max14577_irq(max14577, data->irq);
+
+ if (!irq_data)
+ return;
+
+ if (irq_data->group >= MAX14577_IRQ_REGS_NUM)
+ return;
+
+ max14577->irq_masks_cur[irq_data->group] &= ~irq_data->mask;
+}
+
+static void max14577_irq_unmask(struct irq_data *data)
+{
+ struct max14577 *max14577 = irq_get_chip_data(data->irq);
+ const struct max14577_irq_data *irq_data =
+ irq_to_max14577_irq(max14577, data->irq);
+
+ if (!irq_data)
+ return;
+
+ if (irq_data->group >= MAX14577_IRQ_REGS_NUM)
+ return;
+
+ max14577->irq_masks_cur[irq_data->group] |= irq_data->mask;
+}
+
+static struct irq_chip max14577_irq_chip = {
+ .name = MAX14577_MFD_DEV_NAME,
+ .irq_bus_lock = max14577_irq_lock,
+ .irq_bus_sync_unlock = max14577_irq_sync_unlock,
+ .irq_mask = max14577_irq_mask,
+ .irq_unmask = max14577_irq_unmask,
+};
+
+static irqreturn_t max14577_irq_thread(int irq, void *data)
+{
+ struct max14577 *max14577 = data;
+ u8 irq_reg[MAX14577_IRQ_REGS_NUM] = {0};
+ u8 tmp_irq_reg[MAX14577_IRQ_REGS_NUM] = {};
+ int i, cur_irq;
+
+ max14577_bulk_read(max14577->regmap, MAX14577_REG_INT1,
+ &tmp_irq_reg[MAX14577_IRQ_INT1],
+ MAX14577_IRQ_REGS_NUM);
+
+ for (i = MAX14577_IRQ_INT1; i < MAX14577_IRQ_REGS_NUM; i++)
+ irq_reg[i] |= tmp_irq_reg[i];
+
+ for (i = 0; i < MAX14577_IRQ_REGS_NUM; i++)
+ irq_reg[i] &= max14577->irq_masks_cur[i];
+
+ for (i = 0; i < MAX14577_IRQ_NUM; i++) {
+ if (irq_reg[max14577_irqs[i].group] & max14577_irqs[i].mask) {
+ cur_irq = irq_find_mapping(max14577->irq_domain, i);
+ if (cur_irq)
+ handle_nested_irq(cur_irq);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int max14577_irq_domain_map(struct irq_domain *d, unsigned int irq,
+ irq_hw_number_t hw)
+{
+ struct max14577 *max14577 = d->host_data;
+
+ irq_set_chip_data(irq, max14577);
+ irq_set_chip_and_handler(irq, &max14577_irq_chip, handle_edge_irq);
+ irq_set_nested_thread(irq, 1);
+#ifdef CONFIG_ARM
+ set_irq_flags(irq, IRQF_VALID);
+#else
+ irq_set_noprobe(irq);
+#endif
+ return 0;
+}
+
+static struct irq_domain_ops max14577_irq_domain_ops = {
+ .map = max14577_irq_domain_map,
+};
+
+int max14577_irq_init(struct max14577 *max14577)
+{
+ struct irq_domain *domain;
+ int i, ret;
+
+ mutex_init(&max14577->irq_lock);
+
+ /* Mask individual interrupt sources */
+ for (i = 0; i < MAX14577_IRQ_REGS_NUM; i++) {
+ /* IRQ 0:MASK 1:NOT MASK */
+ max14577->irq_masks_cur[i] = 0x00;
+ max14577->irq_masks_cache[i] = 0x00;
+
+ if (max14577_mask_reg[i] == MAX14577_REG_INVALID)
+ continue;
+
+ max14577_write_reg(max14577->regmap, max14577_mask_reg[i],
+ max14577->irq_masks_cur[i]);
+ }
+
+ domain = irq_domain_add_linear(NULL, MAX14577_IRQ_NUM,
+ &max14577_irq_domain_ops, max14577);
+ if (!domain) {
+ dev_err(max14577->dev, "Could not create IRQ domain\n");
+ ret = -ENODEV;
+ goto err;
+ }
+ max14577->irq_domain = domain;
+
+ ret = request_threaded_irq(max14577->irq, NULL, max14577_irq_thread,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "max14577-irq", max14577);
+ if (ret) {
+ dev_err(max14577->dev, "Failed to request IRQ %d: %d\n",
+ max14577->irq, ret);
+ goto err;
+ }
+
+ return 0;
+err:
+ mutex_destroy(&max14577->irq_lock);
+ return ret;
+}
+
+int max14577_irq_resume(struct max14577 *max14577)
+{
+ int ret = 0;
+
+ if (max14577->irq && max14577->irq_domain)
+ ret = max14577_irq_thread(0, max14577);
+
+ return ret >= 0 ? 0 : ret;
+}
+
+void max14577_irq_exit(struct max14577 *max14577)
+{
+ if (max14577->irq)
+ free_irq(max14577->irq, max14577);
+ mutex_destroy(&max14577->irq_lock);
+}
diff --git a/drivers/mfd/max14577.c b/drivers/mfd/max14577.c
new file mode 100644
index 0000000..bfc4eb5
--- /dev/null
+++ b/drivers/mfd/max14577.c
@@ -0,0 +1,216 @@
+/*
+ * max14577.c - mfd core driver for the Maxim 14577
+ *
+ * Copyright (C) 2013 Samsung Electrnoics
+ * Chanwoo Choi <cw00.choi@samsung.com>
+ * Krzysztof Kozlowski <k.kozlowski@samsung.com>
+ *
+ * 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.
+ *
+ * This driver is based on max8997.c
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/max14577.h>
+#include <linux/mfd/max14577-private.h>
+
+static struct mfd_cell max14577_devs[] = {
+ { .name = "max14577-muic", },
+ { .name = "max14577-regulator", },
+ { .name = "max14577-charger", },
+};
+
+static bool max14577_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case MAX14577_REG_INT1 ... MAX14577_REG_STATUS3:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+static const struct regmap_config max14577_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_reg = max14577_volatile_reg,
+ .max_register = MAX14577_REG_END,
+};
+
+static int max14577_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct max14577 *max14577;
+ struct max14577_platform_data *pdata = dev_get_platdata(&i2c->dev);
+ u8 reg_data;
+ int ret = 0;
+
+ if (i2c->dev.of_node) {
+ pdata = devm_kzalloc(&i2c->dev,
+ sizeof(struct max14577_platform_data),
+ GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+ i2c->dev.platform_data = pdata;
+ }
+
+ if (IS_ERR_OR_NULL(pdata)) {
+ dev_err(&i2c->dev, "No platform data found: %ld\n",
+ PTR_ERR(pdata));
+ return -EINVAL;
+ }
+
+ max14577 = devm_kzalloc(&i2c->dev, sizeof(struct max14577), GFP_KERNEL);
+ if (!max14577)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, max14577);
+ max14577->pdata = pdata;
+ max14577->dev = &i2c->dev;
+ max14577->i2c = i2c;
+ max14577->irq = i2c->irq;
+
+ max14577->regmap = devm_regmap_init_i2c(i2c, &max14577_regmap_config);
+ if (IS_ERR(max14577->regmap)) {
+ ret = PTR_ERR(max14577->regmap);
+ dev_err(max14577->dev, "Failed to allocate register map: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = max14577_read_reg(max14577->regmap, MAX14577_REG_DEVICEID,
+ ®_data);
+ if (ret) {
+ dev_err(max14577->dev, "Device not found on this channel: %d\n",
+ ret);
+ return ret;
+ }
+ max14577->vendor_id = (reg_data & 0x7);
+ max14577->device_id = ((reg_data & 0xF8) >> 0x3);
+ dev_info(max14577->dev, "Device ID: 0x%x, vendor: 0x%x\n",
+ max14577->device_id, max14577->vendor_id);
+
+ ret = max14577_irq_init(max14577);
+ if (ret < 0)
+ return ret;
+
+ ret = mfd_add_devices(max14577->dev, -1, max14577_devs,
+ ARRAY_SIZE(max14577_devs), NULL, 0, NULL);
+ if (ret < 0)
+ goto err_mfd;
+
+ device_init_wakeup(max14577->dev, 1);
+
+ return 0;
+
+err_mfd:
+ max14577_irq_exit(max14577);
+ return ret;
+}
+
+static int max14577_i2c_remove(struct i2c_client *i2c)
+{
+ struct max14577 *max14577 = i2c_get_clientdata(i2c);
+
+ mfd_remove_devices(max14577->dev);
+ max14577_irq_exit(max14577);
+
+ return 0;
+}
+
+static const struct i2c_device_id max14577_i2c_id[] = {
+ { MAX14577_MFD_DEV_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max14577_i2c_id);
+
+#ifdef CONFIG_PM
+static int max14577_suspend(struct device *dev)
+{
+ struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+ struct max14577 *max14577 = i2c_get_clientdata(i2c);
+
+ if (device_may_wakeup(dev)) {
+ enable_irq_wake(max14577->irq);
+ /*
+ * MUIC IRQ must be disabled during suspend if this is
+ * a wake up source because it will be handled before
+ * resuming I2C.
+ *
+ * When device is woken up from suspend (e.g. by ADC change),
+ * an interrupt occurs before resuming I2C bus controller.
+ * Interrupt handler tries to read registers but this read
+ * will fail because I2C is still suspended.
+ */
+ disable_irq(max14577->irq);
+ }
+
+ return 0;
+}
+
+static int max14577_resume(struct device *dev)
+{
+ struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+ struct max14577 *max14577 = i2c_get_clientdata(i2c);
+
+ if (device_may_wakeup(dev)) {
+ disable_irq_wake(max14577->irq);
+ enable_irq(max14577->irq);
+ }
+
+ return max14577_irq_resume(max14577);
+}
+#else
+#define max14577_suspend NULL
+#define max14577_resume NULL
+#endif /* CONFIG_PM */
+
+#ifdef CONFIG_OF
+static struct of_device_id max14577_dt_match[] = {
+ { .compatible = "maxim,max14577", },
+ {},
+};
+#else
+#define max14577_dt_match NULL
+#endif
+
+static SIMPLE_DEV_PM_OPS(max14577_pm, max14577_suspend, max14577_resume);
+
+static struct i2c_driver max14577_i2c_driver = {
+ .driver = {
+ .name = MAX14577_MFD_DEV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &max14577_pm,
+ .of_match_table = of_match_ptr(max14577_dt_match),
+ },
+ .probe = max14577_i2c_probe,
+ .remove = max14577_i2c_remove,
+ .id_table = max14577_i2c_id,
+};
+
+static int __init max14577_i2c_init(void)
+{
+ return i2c_add_driver(&max14577_i2c_driver);
+}
+subsys_initcall(max14577_i2c_init);
+
+static void __exit max14577_i2c_exit(void)
+{
+ i2c_del_driver(&max14577_i2c_driver);
+}
+module_exit(max14577_i2c_exit);
+
+MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>, Krzysztof Kozlowski <k.kozlowski@samsung.com>");
+MODULE_DESCRIPTION("MAXIM 14577 multi-function core driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/max14577-private.h b/include/linux/mfd/max14577-private.h
new file mode 100644
index 0000000..a6e72b5
--- /dev/null
+++ b/include/linux/mfd/max14577-private.h
@@ -0,0 +1,317 @@
+/*
+ * max14577-private.h - Common API for the Maxim 14577 internal sub chip
+ *
+ * Copyright (C) 2013 Samsung Electrnoics
+ * Chanwoo Choi <cw00.choi@samsung.com>
+ * Krzysztof Kozlowski <k.kozlowski@samsung.com>
+ *
+ * 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.
+ */
+
+#ifndef __MAX14577_PRIVATE_H__
+#define __MAX14577_PRIVATE_H__
+
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+
+#define MAX14577_REG_INVALID (0xff)
+
+/* Slave addr = 0x4A: Interrupt */
+enum max14577_reg {
+ MAX14577_REG_DEVICEID = 0x00,
+ MAX14577_REG_INT1 = 0x01,
+ MAX14577_REG_INT2 = 0x02,
+ MAX14577_REG_INT3 = 0x03,
+ MAX14577_REG_STATUS1 = 0x04,
+ MAX14577_REG_STATUS2 = 0x05,
+ MAX14577_REG_STATUS3 = 0x06,
+ MAX14577_REG_INTMASK1 = 0x07,
+ MAX14577_REG_INTMASK2 = 0x08,
+ MAX14577_REG_INTMASK3 = 0x09,
+ MAX14577_REG_CDETCTRL1 = 0x0A,
+ MAX14577_REG_RFU = 0x0B,
+ MAX14577_REG_CONTROL1 = 0x0C,
+ MAX14577_REG_CONTROL2 = 0x0D,
+ MAX14577_REG_CONTROL3 = 0x0E,
+ MAX14577_REG_CHGCTRL1 = 0x0F,
+ MAX14577_REG_CHGCTRL2 = 0x10,
+ MAX14577_REG_CHGCTRL3 = 0x11,
+ MAX14577_REG_CHGCTRL4 = 0x12,
+ MAX14577_REG_CHGCTRL5 = 0x13,
+ MAX14577_REG_CHGCTRL6 = 0x14,
+ MAX14577_REG_CHGCTRL7 = 0x15,
+
+ MAX14577_REG_END,
+};
+
+/* Slave addr = 0x4A: MUIC */
+enum max14577_muic_reg {
+ MAX14577_MUIC_REG_STATUS1 = 0x04,
+ MAX14577_MUIC_REG_STATUS2 = 0x05,
+ MAX14577_MUIC_REG_CONTROL1 = 0x0C,
+ MAX14577_MUIC_REG_CONTROL3 = 0x0E,
+
+ MAX14577_MUIC_REG_END,
+};
+
+enum max14577_muic_charger_type {
+ MAX14577_CHARGER_TYPE_NONE = 0,
+ MAX14577_CHARGER_TYPE_USB,
+ MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT,
+ MAX14577_CHARGER_TYPE_DEDICATED_CHG,
+ MAX14577_CHARGER_TYPE_SPECIAL_500MA,
+ MAX14577_CHARGER_TYPE_SPECIAL_1A,
+ MAX14577_CHARGER_TYPE_RESERVED,
+ MAX14577_CHARGER_TYPE_DEAD_BATTERY = 7,
+};
+
+/* MAX14577 STATUS1 register */
+#define STATUS1_ADC_SHIFT 0
+#define STATUS1_ADCLOW_SHIFT 5
+#define STATUS1_ADCERR_SHIFT 6
+#define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT)
+#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT)
+#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT)
+
+/* MAX14577 STATUS2 register */
+#define STATUS2_CHGTYP_SHIFT 0
+#define STATUS2_CHGDETRUN_SHIFT 3
+#define STATUS2_DCDTMR_SHIFT 4
+#define STATUS2_DBCHG_SHIFT 5
+#define STATUS2_VBVOLT_SHIFT 6
+#define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT)
+#define STATUS2_CHGDETRUN_MASK (0x1 << STATUS2_CHGDETRUN_SHIFT)
+#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT)
+#define STATUS2_DBCHG_MASK (0x1 << STATUS2_DBCHG_SHIFT)
+#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_SHIFT)
+
+/* MAX14577 CONTROL1 register */
+#define COMN1SW_SHIFT 0
+#define COMP2SW_SHIFT 3
+#define MICEN_SHIFT 6
+#define IDBEN_SHIFT 7
+#define COMN1SW_MASK (0x7 << COMN1SW_SHIFT)
+#define COMP2SW_MASK (0x7 << COMP2SW_SHIFT)
+#define MICEN_MASK (0x1 << MICEN_SHIFT)
+#define IDBEN_MASK (0x1 << IDBEN_SHIFT)
+#define CLEAR_IDBEN_MICEN_MASK (COMN1SW_MASK | COMP2SW_MASK)
+#define CTRL1_SW_USB ((1 << COMP2SW_SHIFT) \
+ | (1 << COMN1SW_SHIFT))
+#define CTRL1_SW_AUDIO ((2 << COMP2SW_SHIFT) \
+ | (2 << COMN1SW_SHIFT))
+#define CTRL1_SW_UART ((3 << COMP2SW_SHIFT) \
+ | (3 << COMN1SW_SHIFT))
+#define CTRL1_SW_OPEN ((0 << COMP2SW_SHIFT) \
+ | (0 << COMN1SW_SHIFT))
+
+/* MAX14577 CONTROL2 register */
+#define CTRL2_LOWPWR_SHIFT (0)
+#define CTRL2_ADCEN_SHIFT (1)
+#define CTRL2_CPEN_SHIFT (2)
+#define CTRL2_SFOUTASRT_SHIFT (3)
+#define CTRL2_SFOUTORD_SHIFT (4)
+#define CTRL2_ACCDET_SHIFT (5)
+#define CTRL2_USBCPINT_SHIFT (6)
+#define CTRL2_RCPS_SHIFT (7)
+#define CTRL2_LOWPWR_MASK (0x1 << CTRL2_LOWPWR_SHIFT)
+#define CTRL2_ADCEN_MASK (0x1 << CTRL2_ADCEN_SHIFT)
+#define CTRL2_CPEN_MASK (0x1 << CTRL2_CPEN_SHIFT)
+#define CTRL2_SFOUTASRT_MASK (0x1 << CTRL2_SFOUTASRT_SHIFT)
+#define CTRL2_SFOUTORD_MASK (0x1 << CTRL2_SFOUTORD_SHIFT)
+#define CTRL2_ACCDET_MASK (0x1 << CTRL2_ACCDET_SHIFT)
+#define CTRL2_USBCPINT_MASK (0x1 << CTRL2_USBCPINT_SHIFT)
+#define CTRL2_RCPS_MASK (0x1 << CTR2_RCPS_SHIFT)
+
+#define CTRL2_CPEN1_LOWPWR0 ((1 << CTRL2_CPEN_SHIFT) | \
+ (0 << CTRL2_LOWPWR_SHIFT))
+#define CTRL2_CPEN0_LOWPWR1 ((0 << CTRL2_CPEN_SHIFT) | \
+ (1 << CTRL2_LOWPWR_SHIFT))
+
+/* MAX14577 CONTROL3 register */
+#define CTRL3_JIGSET_SHIFT 0
+#define CTRL3_BOOTSET_SHIFT 2
+#define CTRL3_ADCDBSET_SHIFT 4
+#define CTRL3_JIGSET_MASK (0x3 << CTRL3_JIGSET_SHIFT)
+#define CTRL3_BOOTSET_MASK (0x3 << CTRL3_BOOTSET_SHIFT)
+#define CTRL3_ADCDBSET_MASK (0x3 << CTRL3_ADCDBSET_SHIFT)
+
+/* Slave addr = 0x4A: Charger */
+enum max14577_charger_reg {
+ MAX14577_CHG_REG_STATUS3 = 0x06,
+ MAX14577_CHG_REG_CHG_CTRL1 = 0x0F,
+ MAX14577_CHG_REG_CHG_CTRL2 = 0x10,
+ MAX14577_CHG_REG_CHG_CTRL3 = 0x11,
+ MAX14577_CHG_REG_CHG_CTRL4 = 0x12,
+ MAX14577_CHG_REG_CHG_CTRL5 = 0x13,
+ MAX14577_CHG_REG_CHG_CTRL6 = 0x14,
+ MAX14577_CHG_REG_CHG_CTRL7 = 0x15,
+
+ MAX14577_CHG_REG_END,
+};
+
+/* MAX14577 STATUS3 register */
+#define STATUS3_EOC_SHIFT 0
+#define STATUS3_CGMBC_SHIFT 1
+#define STATUS3_OVP_SHIFT 2
+#define STATUS3_MBCCHGERR_SHIFT 3
+#define STATUS3_EOC_MASK (0x1 << STATUS3_EOC_SHIFT)
+#define STATUS3_CGMBC_MASK (0x1 << STATUS3_CGMBC_SHIFT)
+#define STATUS3_OVP_MASK (0x1 << STATUS3_OVP_SHIFT)
+#define STATUS3_MBCCHGERR_MASK (0x1 << STATUS3_MBCCHGERR_SHIFT)
+
+/* MAX14577 CDETCTRL1 register */
+#define CDETCTRL1_CHGDETEN_SHIFT 0
+#define CDETCTRL1_CHGTYPMAN_SHIFT 1
+#define CDETCTRL1_DCDEN_SHIFT 2
+#define CDETCTRL1_DCD2SCT_SHIFT 3
+#define CDETCTRL1_DCHKTM_SHIFT 4
+#define CDETCTRL1_DBEXIT_SHIFT 5
+#define CDETCTRL1_DBIDLE_SHIFT 6
+#define CDETCTRL1_CDPDET_SHIFT 7
+#define CDETCTRL1_CHGDETEN_MASK (0x1 << CDETCTRL1_CHGDETEN_SHIFT)
+#define CDETCTRL1_CHGTYPMAN_MASK (0x1 << CDETCTRL1_CHGTYPMAN_SHIFT)
+#define CDETCTRL1_DCDEN_MASK (0x1 << CDETCTRL1_DCDEN_SHIFT)
+#define CDETCTRL1_DCD2SCT_MASK (0x1 << CDETCTRL1_DCD2SCT_SHIFT)
+#define CDETCTRL1_DCHKTM_MASK (0x1 << CDETCTRL1_DCHKTM_SHIFT)
+#define CDETCTRL1_DBEXIT_MASK (0x1 << CDETCTRL1_DBEXIT_SHIFT)
+#define CDETCTRL1_DBIDLE_MASK (0x1 << CDETCTRL1_DBIDLE_SHIFT)
+#define CDETCTRL1_CDPDET_MASK (0x1 << CDETCTRL1_CDPDET_SHIFT)
+
+/* MAX14577 CHGCTRL1 register */
+#define CHGCTRL1_TCHW_SHIFT 4
+#define CHGCTRL1_TCHW_MASK (0x7 << CHGCTRL1_TCHW_SHIFT)
+
+/* MAX14577 CHGCTRL2 register */
+#define CHGCTRL2_MBCHOSTEN_SHIFT 6
+#define CHGCTRL2_MBCHOSTEN_MASK (0x1 << CHGCTRL2_MBCHOSTEN_SHIFT)
+#define CHGCTRL2_VCHGR_RC_SHIFT 7
+#define CHGCTRL2_VCHGR_RC_MASK (0x1 << CHGCTRL2_VCHGR_RC_SHIFT)
+
+/* MAX14577 CHGCTRL3 register */
+#define CHGCTRL3_MBCCVWRC_SHIFT 0
+#define CHGCTRL3_MBCCVWRC_MASK (0xf << CHGCTRL3_MBCCVWRC_SHIFT)
+
+/* MAX14577 CHGCTRL4 register */
+#define CHGCTRL4_MBCICHWRCH_SHIFT 0
+#define CHGCTRL4_MBCICHWRCH_MASK (0xf << CHGCTRL4_MBCICHWRCH_SHIFT)
+#define CHGCTRL4_MBCICHWRCL_SHIFT 4
+#define CHGCTRL4_MBCICHWRCL_MASK (0x1 << CHGCTRL4_MBCICHWRCL_SHIFT)
+
+/* MAX14577 CHGCTRL5 register */
+#define CHGCTRL5_EOCS_SHIFT 0
+#define CHGCTRL5_EOCS_MASK (0xf << CHGCTRL5_EOCS_SHIFT)
+
+/* MAX14577 CHGCTRL6 register */
+#define CHGCTRL6_AUTOSTOP_SHIFT 5
+#define CHGCTRL6_AUTOSTOP_MASK (0x1 << CHGCTRL6_AUTOSTOP_SHIFT)
+
+/* MAX14577 CHGCTRL7 register */
+#define CHGCTRL7_OTPCGHCVS_SHIFT 0
+#define CHGCTRL7_OTPCGHCVS_MASK (0x3 << CHGCTRL7_OTPCGHCVS_SHIFT)
+
+/* MAX14577 regulator current limits (as in CHGCTRL4 register), uA */
+#define MAX14577_REGULATOR_CURRENT_LIMIT_MIN 90000
+#define MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START 200000
+#define MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP 50000
+#define MAX14577_REGULATOR_CURRENT_LIMIT_MAX 950000
+
+/* MAX14577 regulator SFOUT LDO voltage, fixed, uV */
+#define MAX14577_REGULATOR_SAFEOUT_VOLTAGE 4900000
+
+enum max14577_irq_source {
+ MAX14577_IRQ_INT1 = 0,
+ MAX14577_IRQ_INT2,
+ MAX14577_IRQ_INT3,
+
+ MAX14577_IRQ_REGS_NUM,
+};
+
+enum max14577_irq {
+ /* INT1 */
+ MAX14577_IRQ_INT1_ADC,
+ MAX14577_IRQ_INT1_ADCLOW,
+ MAX14577_IRQ_INT1_ADCERR,
+
+ /* INT2 */
+ MAX14577_IRQ_INT2_CHGTYP,
+ MAX14577_IRQ_INT2_CHGDETRUN,
+ MAX14577_IRQ_INT2_DCDTMR,
+ MAX14577_IRQ_INT2_DBCHG,
+ MAX14577_IRQ_INT2_VBVOLT,
+
+ /* INT3 */
+ MAX14577_IRQ_INT3_EOC,
+ MAX14577_IRQ_INT3_CGMBC,
+ MAX14577_IRQ_INT3_OVP,
+ MAX14577_IRQ_INT3_MBCCHGERR,
+
+ MAX14577_IRQ_NUM,
+};
+
+struct max14577 {
+ struct device *dev;
+ struct i2c_client *i2c; /* Slave addr = 0x4A */
+
+ struct regmap *regmap;
+
+ struct irq_domain *irq_domain;
+ int irq;
+ struct mutex irq_lock;
+ int irq_masks_cur[MAX14577_IRQ_REGS_NUM];
+ int irq_masks_cache[MAX14577_IRQ_REGS_NUM];
+
+ /* Device ID */
+ u8 vendor_id; /* Vendor Identification */
+ u8 device_id; /* Chip Version */
+
+ struct max14577_platform_data *pdata;
+};
+
+extern int max14577_irq_init(struct max14577 *max14577);
+extern void max14577_irq_exit(struct max14577 *max14577);
+extern int max14577_irq_resume(struct max14577 *max14577);
+
+/* MAX14577 shared regmap API function */
+static inline int max14577_read_reg(struct regmap *map, u8 reg, u8 *dest)
+{
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(map, reg, &val);
+ *dest = val;
+
+ return ret;
+}
+
+static inline int max14577_bulk_read(struct regmap *map, u8 reg, u8 *buf,
+ int count)
+{
+ return regmap_bulk_read(map, reg, buf, count);
+}
+
+static inline int max14577_write_reg(struct regmap *map, u8 reg, u8 value)
+{
+ return regmap_write(map, reg, value);
+}
+
+static inline int max14577_bulk_write(struct regmap *map, u8 reg, u8 *buf,
+ int count)
+{
+ return regmap_bulk_write(map, reg, buf, count);
+}
+
+static inline int max14577_update_reg(struct regmap *map, u8 reg, u8 mask,
+ u8 val)
+{
+ return regmap_update_bits(map, reg, mask, val);
+}
+
+#endif /* __MAX14577_PRIVATE_H__ */
diff --git a/include/linux/mfd/max14577.h b/include/linux/mfd/max14577.h
new file mode 100644
index 0000000..9514123
--- /dev/null
+++ b/include/linux/mfd/max14577.h
@@ -0,0 +1,72 @@
+/*
+ * max14577.h - Driver for the Maxim 14577
+ *
+ * Copyright (C) 2013 Samsung Electrnoics
+ * Chanwoo Choi <cw00.choi@samsung.com>
+ * Krzysztof Kozlowski <k.kozlowski@samsung.com>
+ *
+ * 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.
+ *
+ * This driver is based on max8997.h
+ *
+ * MAX14577 has MUIC, Charger devices.
+ * The devices share the same I2C bus and interrupt line
+ * included in this mfd driver.
+ */
+
+#ifndef __MAX14577_H__
+#define __MAX14577_H__
+
+#include <linux/mfd/max14577-private.h>
+#include <linux/regulator/consumer.h>
+
+#define MAX14577_MFD_DEV_NAME "max14577"
+
+/*
+ * MAX14577 Regulator
+ */
+
+/* MAX14577 regulator IDs */
+enum max14577_regulators {
+ MAX14577_SAFEOUT = 0,
+ MAX14577_CHARGER,
+
+ MAX14577_REG_MAX,
+};
+
+struct max14577_regulator_platform_data {
+ int id;
+ struct regulator_init_data *initdata;
+ struct device_node *of_node;
+};
+
+/*
+ * MAX14577 MFD platform data
+ */
+struct max14577_platform_data {
+ /* IRQ */
+ int irq_base;
+
+ /* current control GPIOs */
+ int gpio_pogo_vbatt_en;
+ int gpio_pogo_vbus_en;
+
+ /* current control GPIO control function */
+ int (*set_gpio_pogo_vbatt_en) (int gpio_val);
+ int (*set_gpio_pogo_vbus_en) (int gpio_val);
+
+ int (*set_gpio_pogo_cb) (int new_dev);
+
+ int num_regulators;
+ struct max14577_regulator_platform_data *regulators;
+};
+
+#endif /* __MAX14577_H__ */
--
1.7.9.5
^ permalink raw reply related [flat|nested] 16+ messages in thread
[parent not found: <1384956732-19526-2-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>]
* Re: [PATCH v2 1/5] mfd: max14577: Add max14577 MFD driver core
[not found] ` <1384956732-19526-2-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
@ 2013-11-20 15:31 ` Mark Brown
2013-11-21 10:31 ` Krzysztof Kozlowski
0 siblings, 1 reply; 16+ messages in thread
From: Mark Brown @ 2013-11-20 15:31 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Lee Jones,
Anton Vorontsov, David Woodhouse, Liam Girdwood, Grant Likely,
Rob Herring, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA, Pawel Moll, Stephen Warren,
Ian Campbell, Rob Landley, linux-doc-u79uwXL29TY76Z2rM5mHXA,
linux-arm-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r,
Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park
[-- Attachment #1: Type: text/plain, Size: 1292 bytes --]
On Wed, Nov 20, 2013 at 03:12:08PM +0100, Krzysztof Kozlowski wrote:
> +static irqreturn_t max14577_irq_thread(int irq, void *data)
> +{
> + struct max14577 *max14577 = data;
> + u8 irq_reg[MAX14577_IRQ_REGS_NUM] = {0};
> + u8 tmp_irq_reg[MAX14577_IRQ_REGS_NUM] = {};
> + int i, cur_irq;
> +
> + max14577_bulk_read(max14577->regmap, MAX14577_REG_INT1,
> + &tmp_irq_reg[MAX14577_IRQ_INT1],
> + MAX14577_IRQ_REGS_NUM);
All this interrupt handling looks like a straight copy of the regmap
code?
> +int max14577_irq_resume(struct max14577 *max14577)
> +{
> + int ret = 0;
> +
> + if (max14577->irq && max14577->irq_domain)
> + ret = max14577_irq_thread(0, max14577);
Are you sure that this is not going to race nastily if the interrupt
goes off during resume?
> +
> + return ret >= 0 ? 0 : ret;
This check isn't a triumph of legibility.
> + if (IS_ERR_OR_NULL(pdata)) {
> + dev_err(&i2c->dev, "No platform data found: %ld\n",
> + PTR_ERR(pdata));
> + return -EINVAL;
> + }
Why IS_ERR_OR_NULL().
> +#else
> +#define max14577_suspend NULL
> +#define max14577_resume NULL
> +#endif /* CONFIG_PM */
You don't need these since you use SIMPLE_DEV_PM_OPS().
> +#else
> +#define max14577_dt_match NULL
> +#endif
Similarly here.
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/5] mfd: max14577: Add max14577 MFD driver core
2013-11-20 15:31 ` Mark Brown
@ 2013-11-21 10:31 ` Krzysztof Kozlowski
0 siblings, 0 replies; 16+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-21 10:31 UTC (permalink / raw)
To: Mark Brown
Cc: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Lee Jones,
Anton Vorontsov, David Woodhouse, Liam Girdwood, Grant Likely,
Rob Herring, linux-kernel, devicetree, Pawel Moll, Stephen Warren,
Ian Campbell, Rob Landley, linux-doc, linux-arm-kernel,
Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park
On Wed, 2013-11-20 at 15:31 +0000, Mark Brown wrote:
> On Wed, Nov 20, 2013 at 03:12:08PM +0100, Krzysztof Kozlowski wrote:
>
> > +static irqreturn_t max14577_irq_thread(int irq, void *data)
> > +{
> > + struct max14577 *max14577 = data;
> > + u8 irq_reg[MAX14577_IRQ_REGS_NUM] = {0};
> > + u8 tmp_irq_reg[MAX14577_IRQ_REGS_NUM] = {};
> > + int i, cur_irq;
> > +
> > + max14577_bulk_read(max14577->regmap, MAX14577_REG_INT1,
> > + &tmp_irq_reg[MAX14577_IRQ_INT1],
> > + MAX14577_IRQ_REGS_NUM);
>
> All this interrupt handling looks like a straight copy of the regmap
> code?
You are right, I will convert it to regmap_irq_chip.
> > +int max14577_irq_resume(struct max14577 *max14577)
> > +{
> > + int ret = 0;
> > +
> > + if (max14577->irq && max14577->irq_domain)
> > + ret = max14577_irq_thread(0, max14577);
>
> Are you sure that this is not going to race nastily if the interrupt
> goes off during resume?
I believe this code came from max77693. At least it looks very similar.
Also it seems it was a fast workaround for interrupt occurring during
device resume (before I2C bus resume). Handling this interrupt failed
and interrupt registers were not cleared.
This code is not needed now, especially after using regmap IRQ handling.
>
> > +
> > + return ret >= 0 ? 0 : ret;
>
> This check isn't a triumph of legibility.
OK.
>
> > + if (IS_ERR_OR_NULL(pdata)) {
> > + dev_err(&i2c->dev, "No platform data found: %ld\n",
> > + PTR_ERR(pdata));
> > + return -EINVAL;
> > + }
>
> Why IS_ERR_OR_NULL().
Right, it should be just check for null.
>
> > +#else
> > +#define max14577_suspend NULL
> > +#define max14577_resume NULL
> > +#endif /* CONFIG_PM */
>
> You don't need these since you use SIMPLE_DEV_PM_OPS().
OK.
>
> > +#else
> > +#define max14577_dt_match NULL
> > +#endif
>
> Similarly here.
OK.
Thanks for comments.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/5] mfd: max14577: Add max14577 MFD driver core
2013-11-20 14:12 ` [PATCH v2 1/5] mfd: max14577: Add max14577 MFD driver core Krzysztof Kozlowski
[not found] ` <1384956732-19526-2-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
@ 2013-11-21 10:34 ` Lee Jones
2013-11-21 11:43 ` Krzysztof Kozlowski
1 sibling, 1 reply; 16+ messages in thread
From: Lee Jones @ 2013-11-21 10:34 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Anton Vorontsov,
David Woodhouse, Liam Girdwood, Mark Brown, Grant Likely,
Rob Herring, linux-kernel, devicetree, Pawel Moll, Stephen Warren,
Ian Campbell, Rob Landley, linux-doc, linux-arm-kernel,
Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park
<snip>
> +/**
> + * After resuming from suspend it may happen that IRQ is signalled but
> + * IRQ GPIO is not high. Also the interrupt registers won't have any data
> + * (all of them equal to 0x00).
> + *
> + * In such case retry few times reading the interrupt registers.
> + */
> +#define IRQ_READ_REG_RETRY_CNT 5
Where is this used?
<snip>
> +#define DECLARE_IRQ(idx, _group, _mask) \
> + [(idx)] = { .group = (_group), .mask = (_mask) }
I'm pretty sure the parentheses are supurfluous.
> +static const inline struct max14577_irq_data *
> irq_to_max14577_irq(struct max14577 *max14577, int irq)
We should tab this back out.
> +{
> + struct irq_data *data = irq_get_irq_data(irq);
> + return &max14577_irqs[data->hwirq];
Shouldn't you be using irq_create_mapping() or irq_find_mapping() here?
> +static void max14577_irq_mask(struct irq_data *data)
> +{
> + struct max14577 *max14577 = irq_get_chip_data(data->irq);
irq_data_get_irq_chip_data(data); ?
> + const struct max14577_irq_data *irq_data =
> + irq_to_max14577_irq(max14577, data->irq);
> +
> + if (!irq_data)
> + return;
> +
> + if (irq_data->group >= MAX14577_IRQ_REGS_NUM)
> + return;
> +
> + max14577->irq_masks_cur[irq_data->group] &= ~irq_data->mask;
> +}
<snip>
> +int max14577_irq_resume(struct max14577 *max14577)
> +{
> + int ret = 0;
> +
> + if (max14577->irq && max14577->irq_domain)
> + ret = max14577_irq_thread(0, max14577);
> +
> + return ret >= 0 ? 0 : ret;
Please open this out to something more easily comprehensible.
<snip>
> diff --git a/drivers/mfd/max14577.c b/drivers/mfd/max14577.c
> new file mode 100644
> index 0000000..bfc4eb5
> --- /dev/null
> +++ b/drivers/mfd/max14577.c
> @@ -0,0 +1,216 @@
> +/*
> + * max14577.c - mfd core driver for the Maxim 14577
> + *
> + * Copyright (C) 2013 Samsung Electrnoics
> + * Chanwoo Choi <cw00.choi@samsung.com>
> + * Krzysztof Kozlowski <k.kozlowski@samsung.com>
> + *
> + * 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.
> + *
> + * This driver is based on max8997.c
> + */
> +
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/max14577.h>
> +#include <linux/mfd/max14577-private.h>
> +
> +static struct mfd_cell max14577_devs[] = {
> + { .name = "max14577-muic", },
> + { .name = "max14577-regulator", },
> + { .name = "max14577-charger", },
> +};
> +
> +static bool max14577_volatile_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case MAX14577_REG_INT1 ... MAX14577_REG_STATUS3:
> + return true;
> + default:
> + break;
> + }
> + return false;
> +}
> +
> +static const struct regmap_config max14577_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .volatile_reg = max14577_volatile_reg,
> + .max_register = MAX14577_REG_END,
> +};
> +
> +static int max14577_i2c_probe(struct i2c_client *i2c,
> + const struct i2c_device_id *id)
> +{
> + struct max14577 *max14577;
> + struct max14577_platform_data *pdata = dev_get_platdata(&i2c->dev);
> + u8 reg_data;
> + int ret = 0;
> +
> + if (i2c->dev.of_node) {
Can you pull this out. It looks neater as the top as:
struct device_node *np = i2c->dev.of_node;
> + pdata = devm_kzalloc(&i2c->dev,
> + sizeof(struct max14577_platform_data),
I prefer:
sizeof(*pdata),
> + GFP_KERNEL);
> + if (!pdata)
> + return -ENOMEM;
> + i2c->dev.platform_data = pdata;
> + }
> +
> + if (IS_ERR_OR_NULL(pdata)) {
It's not likely to be an ERR, just do:
if (!pdata) {
> + dev_err(&i2c->dev, "No platform data found: %ld\n",
> + PTR_ERR(pdata));
> + return -EINVAL;
> + }
> +
> + max14577 = devm_kzalloc(&i2c->dev, sizeof(struct max14577), GFP_KERNEL);
> + if (!max14577)
> + return -ENOMEM;
> +
> + i2c_set_clientdata(i2c, max14577);
> + max14577->pdata = pdata;
How many different places do you want to store this?
> + max14577->dev = &i2c->dev;
You're storing dev here anyway.
Remove the pdata property and get it from dev when/if you require it.
> + max14577->i2c = i2c;
> + max14577->irq = i2c->irq;
> +
> + max14577->regmap = devm_regmap_init_i2c(i2c, &max14577_regmap_config);
> + if (IS_ERR(max14577->regmap)) {
> + ret = PTR_ERR(max14577->regmap);
> + dev_err(max14577->dev, "Failed to allocate register map: %d\n",
> + ret);
> + return ret;
> + }
> +
> + ret = max14577_read_reg(max14577->regmap, MAX14577_REG_DEVICEID,
> + ®_data);
> + if (ret) {
> + dev_err(max14577->dev, "Device not found on this channel: %d\n",
> + ret);
> + return ret;
> + }
> + max14577->vendor_id = (reg_data & 0x7);
> + max14577->device_id = ((reg_data & 0xF8) >> 0x3);
> + dev_info(max14577->dev, "Device ID: 0x%x, vendor: 0x%x\n",
> + max14577->device_id, max14577->vendor_id);
> +
> + ret = max14577_irq_init(max14577);
> + if (ret < 0)
> + return ret;
> +
> + ret = mfd_add_devices(max14577->dev, -1, max14577_devs,
> + ARRAY_SIZE(max14577_devs), NULL, 0, NULL);
You should be passing the irqdomain as the final parameter here.
> + if (ret < 0)
> + goto err_mfd;
> +
> + device_init_wakeup(max14577->dev, 1);
> +
> + return 0;
> +
> +err_mfd:
> + max14577_irq_exit(max14577);
> + return ret;
> +}
> +
> +static int max14577_i2c_remove(struct i2c_client *i2c)
> +{
> + struct max14577 *max14577 = i2c_get_clientdata(i2c);
> +
> + mfd_remove_devices(max14577->dev);
> + max14577_irq_exit(max14577);
> +
> + return 0;
> +}
> +
> +static const struct i2c_device_id max14577_i2c_id[] = {
> + { MAX14577_MFD_DEV_NAME, 0 },
Can you use the proper name here, these types of defines are pretty ugly.
<snip>
> +#ifdef CONFIG_OF
> +static struct of_device_id max14577_dt_match[] = {
> + { .compatible = "maxim,max14577", },
> + {},
> +};
> +#else
> +#define max14577_dt_match NULL
> +#endif
You don't need to do this and use of_match_ptr().
Remove the #ifdef.
> +static SIMPLE_DEV_PM_OPS(max14577_pm, max14577_suspend, max14577_resume);
> +
> +static struct i2c_driver max14577_i2c_driver = {
> + .driver = {
> + .name = MAX14577_MFD_DEV_NAME,
Can you use the proper name here, these types of defines are pretty ugly.
> + .owner = THIS_MODULE,
> + .pm = &max14577_pm,
> + .of_match_table = of_match_ptr(max14577_dt_match),
> + },
> + .probe = max14577_i2c_probe,
> + .remove = max14577_i2c_remove,
> + .id_table = max14577_i2c_id,
> +};
>>>>>>>>>>>>>>>>>>>>>>
> +static int __init max14577_i2c_init(void)
> +{
> + return i2c_add_driver(&max14577_i2c_driver);
> +}
> +subsys_initcall(max14577_i2c_init);
> +
> +static void __exit max14577_i2c_exit(void)
> +{
> + i2c_del_driver(&max14577_i2c_driver);
> +}
> +module_exit(max14577_i2c_exit);
>>>>>>>>>>>>>>>>>>>>>>
Remove all this and replace with:
module_i2c_driver(max14577_i2c_driver);
> +MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>, Krzysztof Kozlowski <k.kozlowski@samsung.com>");
> +MODULE_DESCRIPTION("MAXIM 14577 multi-function core driver");
> +MODULE_LICENSE("GPL");
<snip>
> diff --git a/include/linux/mfd/max14577.h b/include/linux/mfd/max14577.h
> new file mode 100644
> index 0000000..9514123
> --- /dev/null
> +++ b/include/linux/mfd/max14577.h
> @@ -0,0 +1,72 @@
<snip>
> +struct max14577_regulator_platform_data {
> + int id;
> + struct regulator_init_data *initdata;
> + struct device_node *of_node;
Do you ever set this? What's the point of it is it's set in the device?
> +};
<snip>
--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/5] mfd: max14577: Add max14577 MFD driver core
2013-11-21 10:34 ` Lee Jones
@ 2013-11-21 11:43 ` Krzysztof Kozlowski
2013-11-21 12:20 ` Lee Jones
0 siblings, 1 reply; 16+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-21 11:43 UTC (permalink / raw)
To: Lee Jones
Cc: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Anton Vorontsov,
David Woodhouse, Liam Girdwood, Mark Brown, Grant Likely,
Rob Herring, linux-kernel, devicetree, Pawel Moll, Stephen Warren,
Ian Campbell, Rob Landley, linux-doc, linux-arm-kernel,
Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park
On Thu, 2013-11-21 at 10:34 +0000, Lee Jones wrote:
> <snip>
>
> > +/**
> > + * After resuming from suspend it may happen that IRQ is signalled but
> > + * IRQ GPIO is not high. Also the interrupt registers won't have any data
> > + * (all of them equal to 0x00).
> > + *
> > + * In such case retry few times reading the interrupt registers.
> > + */
> > +#define IRQ_READ_REG_RETRY_CNT 5
>
> Where is this used?
>
> <snip>
>
> > +#define DECLARE_IRQ(idx, _group, _mask) \
> > + [(idx)] = { .group = (_group), .mask = (_mask) }
>
> I'm pretty sure the parentheses are supurfluous.
>
> > +static const inline struct max14577_irq_data *
> > irq_to_max14577_irq(struct max14577 *max14577, int irq)
>
> We should tab this back out.
>
> > +{
> > + struct irq_data *data = irq_get_irq_data(irq);
> > + return &max14577_irqs[data->hwirq];
>
> Shouldn't you be using irq_create_mapping() or irq_find_mapping() here?
>
> > +static void max14577_irq_mask(struct irq_data *data)
> > +{
> > + struct max14577 *max14577 = irq_get_chip_data(data->irq);
>
> irq_data_get_irq_chip_data(data); ?
>
> > + const struct max14577_irq_data *irq_data =
> > + irq_to_max14577_irq(max14577, data->irq);
> > +
> > + if (!irq_data)
> > + return;
> > +
> > + if (irq_data->group >= MAX14577_IRQ_REGS_NUM)
> > + return;
> > +
> > + max14577->irq_masks_cur[irq_data->group] &= ~irq_data->mask;
> > +}
>
> <snip>
>
> > +int max14577_irq_resume(struct max14577 *max14577)
> > +{
> > + int ret = 0;
> > +
> > + if (max14577->irq && max14577->irq_domain)
> > + ret = max14577_irq_thread(0, max14577);
> > +
> > + return ret >= 0 ? 0 : ret;
>
> Please open this out to something more easily comprehensible.
Thanks for pointing these out however after using regmap_irq_chip whole
file won't be needed anymore.
(...)
> > +static int max14577_i2c_probe(struct i2c_client *i2c,
> > + const struct i2c_device_id *id)
> > +{
> > + struct max14577 *max14577;
> > + struct max14577_platform_data *pdata = dev_get_platdata(&i2c->dev);
> > + u8 reg_data;
> > + int ret = 0;
> > +
> > + if (i2c->dev.of_node) {
>
> Can you pull this out. It looks neater as the top as:
> struct device_node *np = i2c->dev.of_node;
I am not sure if I understand you correctly. You would like to change
this to:
-------------------------
static int max14577_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct max14577 *max14577;
struct max14577_platform_data *pdata =
dev_get_platdata(&i2c->dev);
struct device_node *np = i2c->dev.of_node;
u8 reg_data;
int ret = 0;
if (np) {
pdata = devm_kzalloc(&i2c->dev,
sizeof(struct max14577_platform_data),
GFP_KERNEL);
-------------------------
The variable 'np' would be used only once.
>
> > + pdata = devm_kzalloc(&i2c->dev,
> > + sizeof(struct max14577_platform_data),
>
> I prefer:
> sizeof(*pdata),
OK. Line will be shorter. I will change this patter also in other
places.
>
> > + GFP_KERNEL);
> > + if (!pdata)
> > + return -ENOMEM;
> > + i2c->dev.platform_data = pdata;
> > + }
> > +
> > + if (IS_ERR_OR_NULL(pdata)) {
>
> It's not likely to be an ERR, just do:
> if (!pdata) {
OK.
> > + dev_err(&i2c->dev, "No platform data found: %ld\n",
> > + PTR_ERR(pdata));
> > + return -EINVAL;
> > + }
> > +
> > + max14577 = devm_kzalloc(&i2c->dev, sizeof(struct max14577), GFP_KERNEL);
> > + if (!max14577)
> > + return -ENOMEM;
> > +
> > + i2c_set_clientdata(i2c, max14577);
> > + max14577->pdata = pdata;
>
> How many different places do you want to store this?
>
> > + max14577->dev = &i2c->dev;
>
> You're storing dev here anyway.
>
> Remove the pdata property and get it from dev when/if you require it.
OK.
> > + max14577->i2c = i2c;
> > + max14577->irq = i2c->irq;
> > +
> > + max14577->regmap = devm_regmap_init_i2c(i2c, &max14577_regmap_config);
> > + if (IS_ERR(max14577->regmap)) {
> > + ret = PTR_ERR(max14577->regmap);
> > + dev_err(max14577->dev, "Failed to allocate register map: %d\n",
> > + ret);
> > + return ret;
> > + }
> > +
> > + ret = max14577_read_reg(max14577->regmap, MAX14577_REG_DEVICEID,
> > + ®_data);
> > + if (ret) {
> > + dev_err(max14577->dev, "Device not found on this channel: %d\n",
> > + ret);
> > + return ret;
> > + }
> > + max14577->vendor_id = (reg_data & 0x7);
> > + max14577->device_id = ((reg_data & 0xF8) >> 0x3);
> > + dev_info(max14577->dev, "Device ID: 0x%x, vendor: 0x%x\n",
> > + max14577->device_id, max14577->vendor_id);
> > +
> > + ret = max14577_irq_init(max14577);
> > + if (ret < 0)
> > + return ret;
> > +
> > + ret = mfd_add_devices(max14577->dev, -1, max14577_devs,
> > + ARRAY_SIZE(max14577_devs), NULL, 0, NULL);
>
> You should be passing the irqdomain as the final parameter here.
I replaced the IRQ handling with regmap_irq_chip so this would look like
this:
-------------------------
ret = mfd_add_devices(max14577->dev, -1, max14577_devs,
ARRAY_SIZE(max14577_devs), NULL, 0,
regmap_irq_get_domain(max14577->irq_data));
-------------------------
Am I correct?
> > + if (ret < 0)
> > + goto err_mfd;
> > +
> > + device_init_wakeup(max14577->dev, 1);
> > +
> > + return 0;
> > +
> > +err_mfd:
> > + max14577_irq_exit(max14577);
> > + return ret;
> > +}
> > +
> > +static int max14577_i2c_remove(struct i2c_client *i2c)
> > +{
> > + struct max14577 *max14577 = i2c_get_clientdata(i2c);
> > +
> > + mfd_remove_devices(max14577->dev);
> > + max14577_irq_exit(max14577);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct i2c_device_id max14577_i2c_id[] = {
> > + { MAX14577_MFD_DEV_NAME, 0 },
>
> Can you use the proper name here, these types of defines are pretty ugly.
OK.
> <snip>
>
> > +#ifdef CONFIG_OF
> > +static struct of_device_id max14577_dt_match[] = {
> > + { .compatible = "maxim,max14577", },
> > + {},
> > +};
> > +#else
> > +#define max14577_dt_match NULL
> > +#endif
>
> You don't need to do this and use of_match_ptr().
>
> Remove the #ifdef.
Yes, Mark also pointed this.
>
> > +static SIMPLE_DEV_PM_OPS(max14577_pm, max14577_suspend, max14577_resume);
> > +
> > +static struct i2c_driver max14577_i2c_driver = {
> > + .driver = {
> > + .name = MAX14577_MFD_DEV_NAME,
>
> Can you use the proper name here, these types of defines are pretty ugly.
OK.
>
> > + .owner = THIS_MODULE,
> > + .pm = &max14577_pm,
> > + .of_match_table = of_match_ptr(max14577_dt_match),
> > + },
> > + .probe = max14577_i2c_probe,
> > + .remove = max14577_i2c_remove,
> > + .id_table = max14577_i2c_id,
> > +};
>
> >>>>>>>>>>>>>>>>>>>>>>
>
> > +static int __init max14577_i2c_init(void)
> > +{
> > + return i2c_add_driver(&max14577_i2c_driver);
> > +}
> > +subsys_initcall(max14577_i2c_init);
> > +
> > +static void __exit max14577_i2c_exit(void)
> > +{
> > + i2c_del_driver(&max14577_i2c_driver);
> > +}
> > +module_exit(max14577_i2c_exit);
>
> >>>>>>>>>>>>>>>>>>>>>>
>
> Remove all this and replace with:
> module_i2c_driver(max14577_i2c_driver);
The subsys_initcall is needed. Marek Szyprowski replied to this here:
http://thread.gmane.org/gmane.linux.drivers.devicetree/51903/focus=1594651
(...)
> > +struct max14577_regulator_platform_data {
> > + int id;
> > + struct regulator_init_data *initdata;
> > + struct device_node *of_node;
>
> Do you ever set this? What's the point of it is it's set in the device?
Do you mean the whole struct max14577_regulator_platform_data or only
some member of it (of_node?)?
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/5] mfd: max14577: Add max14577 MFD driver core
2013-11-21 11:43 ` Krzysztof Kozlowski
@ 2013-11-21 12:20 ` Lee Jones
2013-11-21 13:34 ` Krzysztof Kozlowski
0 siblings, 1 reply; 16+ messages in thread
From: Lee Jones @ 2013-11-21 12:20 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Anton Vorontsov,
David Woodhouse, Liam Girdwood, Mark Brown, Grant Likely,
Rob Herring, linux-kernel, devicetree, Pawel Moll, Stephen Warren,
Ian Campbell, Rob Landley, linux-doc, linux-arm-kernel,
Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park
> Thanks for pointing these out however after using regmap_irq_chip whole
> file won't be needed anymore.
That's fine.
> > > +static int max14577_i2c_probe(struct i2c_client *i2c,
> > > + const struct i2c_device_id *id)
> > > +{
> > > + struct max14577 *max14577;
> > > + struct max14577_platform_data *pdata = dev_get_platdata(&i2c->dev);
> > > + u8 reg_data;
> > > + int ret = 0;
> > > +
> > > + if (i2c->dev.of_node) {
> >
> > Can you pull this out. It looks neater as the top as:
> > struct device_node *np = i2c->dev.of_node;
>
> I am not sure if I understand you correctly. You would like to change
> this to:
> -------------------------
> static int max14577_i2c_probe(struct i2c_client *i2c,
> const struct i2c_device_id *id)
> {
> struct max14577 *max14577;
> struct max14577_platform_data *pdata =
> dev_get_platdata(&i2c->dev);
> struct device_node *np = i2c->dev.of_node;
> u8 reg_data;
> int ret = 0;
>
> if (np) {
> pdata = devm_kzalloc(&i2c->dev,
> sizeof(struct max14577_platform_data),
> GFP_KERNEL);
> -------------------------
> The variable 'np' would be used only once.
I know, but it's more in keeping with the rest of the subsystem.
> > > +
> > > + ret = mfd_add_devices(max14577->dev, -1, max14577_devs,
> > > + ARRAY_SIZE(max14577_devs), NULL, 0, NULL);
> >
> > You should be passing the irqdomain as the final parameter here.
>
> I replaced the IRQ handling with regmap_irq_chip so this would look like
> this:
> -------------------------
> ret = mfd_add_devices(max14577->dev, -1, max14577_devs,
> ARRAY_SIZE(max14577_devs), NULL, 0,
> regmap_irq_get_domain(max14577->irq_data));
> -------------------------
> Am I correct?
if regmap_irq_get_domain() returns an irqdomain, then yes.
> > > + return i2c_add_driver(&max14577_i2c_driver);
> > > +}
> > > +subsys_initcall(max14577_i2c_init);
> > > +
> > > +static void __exit max14577_i2c_exit(void)
> > > +{
> > > + i2c_del_driver(&max14577_i2c_driver);
> > > +}
> > > +module_exit(max14577_i2c_exit);
> >
> > >>>>>>>>>>>>>>>>>>>>>>
> >
> > Remove all this and replace with:
> > module_i2c_driver(max14577_i2c_driver);
>
> The subsys_initcall is needed. Marek Szyprowski replied to this here:
> http://thread.gmane.org/gmane.linux.drivers.devicetree/51903/focus=1594651
Okay, but this will need to be changed when USB Gadget starts to
support -EPROBE_DEFER
> > > +struct max14577_regulator_platform_data {
> > > + int id;
> > > + struct regulator_init_data *initdata;
> > > + struct device_node *of_node;
> >
> > Do you ever set this? What's the point of it is it's set in the device?
>
> Do you mean the whole struct max14577_regulator_platform_data or only
> some member of it (of_node?)?
Initially only the of_node. Usually MFD children are able to call back
into their parent to fetch these details. Also mfd_add_device() goes
out of its way to fill in the child's own of_node.
--
Lee Jones
Linaro STMicroelectronics Landing Team Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 1/5] mfd: max14577: Add max14577 MFD driver core
2013-11-21 12:20 ` Lee Jones
@ 2013-11-21 13:34 ` Krzysztof Kozlowski
0 siblings, 0 replies; 16+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-21 13:34 UTC (permalink / raw)
To: Lee Jones
Cc: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Anton Vorontsov,
David Woodhouse, Liam Girdwood, Mark Brown, Grant Likely,
Rob Herring, linux-kernel, devicetree, Pawel Moll, Stephen Warren,
Ian Campbell, Rob Landley, linux-doc, linux-arm-kernel,
Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park
On Thu, 2013-11-21 at 12:20 +0000, Lee Jones wrote:
(...)
> > > > +struct max14577_regulator_platform_data {
> > > > + int id;
> > > > + struct regulator_init_data *initdata;
> > > > + struct device_node *of_node;
> > >
> > > Do you ever set this? What's the point of it is it's set in the device?
> >
> > Do you mean the whole struct max14577_regulator_platform_data or only
> > some member of it (of_node?)?
>
> Initially only the of_node. Usually MFD children are able to call back
> into their parent to fetch these details. Also mfd_add_device() goes
> out of its way to fill in the child's own of_node.
The of_node is needed for regulator driver. It is passed in struct regulator_config
to devm_regulator_register():
config.of_node = pdata->regulators[i].of_node;
info->regulators[i] = devm_regulator_register(&pdev->dev,
&supported_regulators[id], &config);
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 2/5] extcon: max14577: Add extcon-max14577 driver to support MUIC device
2013-11-20 14:12 [PATCH v2 0/5] mfd: max14577: Add max14577 MFD drivers Krzysztof Kozlowski
2013-11-20 14:12 ` [PATCH v2 1/5] mfd: max14577: Add max14577 MFD driver core Krzysztof Kozlowski
@ 2013-11-20 14:12 ` Krzysztof Kozlowski
2013-11-20 14:12 ` [PATCH v2 3/5] charger: max14577: Add charger support for Maxim 14577 Krzysztof Kozlowski
` (2 subsequent siblings)
4 siblings, 0 replies; 16+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-20 14:12 UTC (permalink / raw)
To: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Lee Jones,
Anton Vorontsov, David Woodhouse, Liam Girdwood, Mark Brown,
Grant Likely, Rob Herring, linux-kernel, devicetree, Pawel Moll,
Stephen Warren, Ian Campbell, Rob Landley, linux-doc,
linux-arm-kernel
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park,
Krzysztof Kozlowski
From: Chanwoo Choi <cw00.choi@samsung.com>
This patch supports Maxim MAX14577 MUIC(Micro USB Interface Controller)
device by using EXTCON subsystem to handle various external connectors.
The max14577 device uses regmap method for i2c communication and
supports irq domain.
Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
Signed-off-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
drivers/extcon/Kconfig | 10 +
drivers/extcon/Makefile | 1 +
drivers/extcon/extcon-max14577.c | 806 ++++++++++++++++++++++++++++++++++++++
3 files changed, 817 insertions(+)
create mode 100644 drivers/extcon/extcon-max14577.c
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index f1d54a3..bdb5a00 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -31,6 +31,16 @@ config EXTCON_ADC_JACK
help
Say Y here to enable extcon device driver based on ADC values.
+config EXTCON_MAX14577
+ tristate "MAX14577 EXTCON Support"
+ depends on MFD_MAX14577
+ select IRQ_DOMAIN
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for the MUIC device of
+ Maxim MAX14577 PMIC. The MAX14577 MUIC is a USB port accessory
+ detector and switch.
+
config EXTCON_MAX77693
tristate "MAX77693 EXTCON Support"
depends on MFD_MAX77693 && INPUT
diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
index 759fdae..43eccc0 100644
--- a/drivers/extcon/Makefile
+++ b/drivers/extcon/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_OF_EXTCON) += of_extcon.o
obj-$(CONFIG_EXTCON) += extcon-class.o
obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o
obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o
+obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o
obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o
obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o
obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o
diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c
new file mode 100644
index 0000000..31e0109
--- /dev/null
+++ b/drivers/extcon/extcon-max14577.c
@@ -0,0 +1,806 @@
+/*
+ * extcon-max14577.c - MAX14577 extcon driver to support MAX14577 MUIC
+ *
+ * Copyright (C) 2013 Samsung Electrnoics
+ * Chanwoo Choi <cw00.choi@samsung.com>
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/max14577.h>
+#include <linux/mfd/max14577-private.h>
+#include <linux/extcon.h>
+#include <linux/irqdomain.h>
+
+#define DEV_NAME "max14577-muic"
+#define DELAY_MS_DEFAULT 17000 /* unit: millisecond */
+
+enum max14577_muic_adc_debounce_time {
+ ADC_DEBOUNCE_TIME_5MS = 0,
+ ADC_DEBOUNCE_TIME_10MS,
+ ADC_DEBOUNCE_TIME_25MS,
+ ADC_DEBOUNCE_TIME_38_62MS,
+};
+
+enum max14577_muic_status {
+ MAX14577_MUIC_STATUS1 = 0,
+ MAX14577_MUIC_STATUS2 = 1,
+ MAX14577_MUIC_STATUS_END,
+};
+
+struct max14577_muic_info {
+ struct device *dev;
+ struct max14577 *max14577;
+ struct extcon_dev *edev;
+ int prev_cable_type;
+ int prev_chg_type;
+ u8 status[MAX14577_MUIC_STATUS_END];
+
+ bool irq_adc;
+ bool irq_chg;
+ struct work_struct irq_work;
+ struct mutex mutex;
+
+ /*
+ * Use delayed workqueue to detect cable state and then
+ * notify cable state to notifiee/platform through uevent.
+ * After completing the booting of platform, the extcon provider
+ * driver should notify cable state to upper layer.
+ */
+ struct delayed_work wq_detcable;
+
+ /*
+ * Default usb/uart path whether UART/USB or AUX_UART/AUX_USB
+ * h/w path of COMP2/COMN1 on CONTROL1 register.
+ */
+ int path_usb;
+ int path_uart;
+};
+
+enum max14577_muic_cable_group {
+ MAX14577_CABLE_GROUP_ADC = 0,
+ MAX14577_CABLE_GROUP_CHG,
+ MAX14577_CABLE_GROUP_VBVOLT,
+};
+
+/**
+ * struct max14577_muic_irq
+ * @irq: the index of irq list of MUIC device.
+ * @name: the name of irq.
+ * @virq: the virtual irq to use irq domain
+ */
+struct max14577_muic_irq {
+ unsigned int irq;
+ const char *name;
+ unsigned int virq;
+};
+
+static struct max14577_muic_irq muic_irqs[] = {
+ { MAX14577_IRQ_INT1_ADC, "muic-ADC" },
+ { MAX14577_IRQ_INT1_ADCLOW, "muic-ADCLOW" },
+ { MAX14577_IRQ_INT1_ADCERR, "muic-ADCError" },
+ { MAX14577_IRQ_INT2_CHGTYP, "muic-CHGTYP" },
+ { MAX14577_IRQ_INT2_CHGDETRUN, "muic-CHGDETRUN" },
+ { MAX14577_IRQ_INT2_DCDTMR, "muic-DCDTMR" },
+ { MAX14577_IRQ_INT2_DBCHG, "muic-DBCHG" },
+ { MAX14577_IRQ_INT2_VBVOLT, "muic-VBVOLT" },
+};
+
+/* Define supported accessory type */
+enum max14577_muic_acc_type {
+ MAX14577_MUIC_ADC_GROUND = 0x0,
+ MAX14577_MUIC_ADC_SEND_END_BUTTON,
+ MAX14577_MUIC_ADC_REMOTE_S1_BUTTON,
+ MAX14577_MUIC_ADC_REMOTE_S2_BUTTON,
+ MAX14577_MUIC_ADC_REMOTE_S3_BUTTON,
+ MAX14577_MUIC_ADC_REMOTE_S4_BUTTON,
+ MAX14577_MUIC_ADC_REMOTE_S5_BUTTON,
+ MAX14577_MUIC_ADC_REMOTE_S6_BUTTON,
+ MAX14577_MUIC_ADC_REMOTE_S7_BUTTON,
+ MAX14577_MUIC_ADC_REMOTE_S8_BUTTON,
+ MAX14577_MUIC_ADC_REMOTE_S9_BUTTON,
+ MAX14577_MUIC_ADC_REMOTE_S10_BUTTON,
+ MAX14577_MUIC_ADC_REMOTE_S11_BUTTON,
+ MAX14577_MUIC_ADC_REMOTE_S12_BUTTON,
+ MAX14577_MUIC_ADC_RESERVED_ACC_1,
+ MAX14577_MUIC_ADC_RESERVED_ACC_2,
+ MAX14577_MUIC_ADC_RESERVED_ACC_3,
+ MAX14577_MUIC_ADC_RESERVED_ACC_4,
+ MAX14577_MUIC_ADC_RESERVED_ACC_5,
+ MAX14577_MUIC_ADC_AUDIO_DEVICE_TYPE2,
+ MAX14577_MUIC_ADC_PHONE_POWERED_DEV,
+ MAX14577_MUIC_ADC_TTY_CONVERTER,
+ MAX14577_MUIC_ADC_UART_CABLE,
+ MAX14577_MUIC_ADC_CEA936A_TYPE1_CHG,
+ MAX14577_MUIC_ADC_FACTORY_MODE_USB_OFF,
+ MAX14577_MUIC_ADC_FACTORY_MODE_USB_ON,
+ MAX14577_MUIC_ADC_AV_CABLE_NOLOAD,
+ MAX14577_MUIC_ADC_CEA936A_TYPE2_CHG,
+ MAX14577_MUIC_ADC_FACTORY_MODE_UART_OFF,
+ MAX14577_MUIC_ADC_FACTORY_MODE_UART_ON,
+ MAX14577_MUIC_ADC_AUDIO_DEVICE_TYPE1, /* with Remote and Simple Ctrl */
+ MAX14577_MUIC_ADC_OPEN,
+};
+
+/* max14577 MUIC device support below list of accessories(external connector) */
+enum {
+ EXTCON_CABLE_USB = 0,
+ EXTCON_CABLE_TA,
+ EXTCON_CABLE_FAST_CHARGER,
+ EXTCON_CABLE_SLOW_CHARGER,
+ EXTCON_CABLE_CHARGE_DOWNSTREAM,
+ EXTCON_CABLE_JIG_USB_ON,
+ EXTCON_CABLE_JIG_USB_OFF,
+ EXTCON_CABLE_JIG_UART_OFF,
+ EXTCON_CABLE_JIG_UART_ON,
+
+ _EXTCON_CABLE_NUM,
+};
+
+static const char *max14577_extcon_cable[] = {
+ [EXTCON_CABLE_USB] = "USB",
+ [EXTCON_CABLE_TA] = "TA",
+ [EXTCON_CABLE_FAST_CHARGER] = "Fast-charger",
+ [EXTCON_CABLE_SLOW_CHARGER] = "Slow-charger",
+ [EXTCON_CABLE_CHARGE_DOWNSTREAM] = "Charge-downstream",
+ [EXTCON_CABLE_JIG_USB_ON] = "JIG-USB-ON",
+ [EXTCON_CABLE_JIG_USB_OFF] = "JIG-USB-OFF",
+ [EXTCON_CABLE_JIG_UART_OFF] = "JIG-UART-OFF",
+ [EXTCON_CABLE_JIG_UART_ON] = "JIG-UART-ON",
+
+ NULL,
+};
+
+/*
+ * max14577_muic_set_debounce_time - Set the debounce time of ADC
+ * @info: the instance including private data of max14577 MUIC
+ * @time: the debounce time of ADC
+ */
+static int max14577_muic_set_debounce_time(struct max14577_muic_info *info,
+ enum max14577_muic_adc_debounce_time time)
+{
+ u8 ret;
+
+ switch (time) {
+ case ADC_DEBOUNCE_TIME_5MS:
+ case ADC_DEBOUNCE_TIME_10MS:
+ case ADC_DEBOUNCE_TIME_25MS:
+ case ADC_DEBOUNCE_TIME_38_62MS:
+ ret = max14577_update_reg(info->max14577->regmap,
+ MAX14577_MUIC_REG_CONTROL3,
+ CTRL3_ADCDBSET_MASK,
+ time << CTRL3_ADCDBSET_SHIFT);
+ if (ret) {
+ dev_err(info->dev, "failed to set ADC debounce time\n");
+ return ret;
+ }
+ break;
+ default:
+ dev_err(info->dev, "invalid ADC debounce time\n");
+ return -EINVAL;
+ }
+
+ return 0;
+};
+
+/*
+ * max14577_muic_set_path - Set hardware line according to attached cable
+ * @info: the instance including private data of max14577 MUIC
+ * @value: the path according to attached cable
+ * @attached: the state of cable (true:attached, false:detached)
+ *
+ * The max14577 MUIC device share outside H/W line among a varity of cables
+ * so, this function set internal path of H/W line according to the type of
+ * attached cable.
+ */
+static int max14577_muic_set_path(struct max14577_muic_info *info,
+ u8 val, bool attached)
+{
+ int ret = 0;
+ u8 ctrl1, ctrl2 = 0;
+
+ /* Set open state to path before changing hw path */
+ ret = max14577_update_reg(info->max14577->regmap,
+ MAX14577_MUIC_REG_CONTROL1,
+ CLEAR_IDBEN_MICEN_MASK, CTRL1_SW_OPEN);
+ if (ret < 0) {
+ dev_err(info->dev, "failed to update MUIC register\n");
+ return ret;
+ }
+
+ if (attached)
+ ctrl1 = val;
+ else
+ ctrl1 = CTRL1_SW_OPEN;
+
+ ret = max14577_update_reg(info->max14577->regmap,
+ MAX14577_MUIC_REG_CONTROL1,
+ CLEAR_IDBEN_MICEN_MASK, ctrl1);
+ if (ret < 0) {
+ dev_err(info->dev, "failed to update MUIC register\n");
+ return ret;
+ }
+
+ if (attached)
+ ctrl2 |= CTRL2_CPEN_MASK; /* LowPwr=0, CPEn=1 */
+ else
+ ctrl2 |= CTRL2_LOWPWR_MASK; /* LowPwr=1, CPEn=0 */
+
+ ret = max14577_update_reg(info->max14577->regmap,
+ MAX14577_REG_CONTROL2,
+ CTRL2_LOWPWR_MASK | CTRL2_CPEN_MASK, ctrl2);
+ if (ret < 0) {
+ dev_err(info->dev, "failed to update MUIC register\n");
+ return ret;
+ }
+
+ dev_dbg(info->dev,
+ "CONTROL1 : 0x%02x, CONTROL2 : 0x%02x, state : %s\n",
+ ctrl1, ctrl2, attached ? "attached" : "detached");
+
+ return 0;
+}
+
+/*
+ * max14577_muic_get_cable_type - Return cable type and check cable state
+ * @info: the instance including private data of max14577 MUIC
+ * @group: the path according to attached cable
+ * @attached: store cable state and return
+ *
+ * This function check the cable state either attached or detached,
+ * and then divide precise type of cable according to cable group.
+ * - max14577_CABLE_GROUP_ADC
+ * - max14577_CABLE_GROUP_CHG
+ */
+static int max14577_muic_get_cable_type(struct max14577_muic_info *info,
+ enum max14577_muic_cable_group group, bool *attached)
+{
+ int cable_type = 0;
+ int adc;
+ int vbvolt;
+ int chg_type;
+
+ switch (group) {
+ case MAX14577_CABLE_GROUP_ADC:
+ /*
+ * Read ADC value to check cable type and decide cable state
+ * according to cable type
+ */
+ adc = info->status[MAX14577_MUIC_STATUS1] & STATUS1_ADC_MASK;
+ adc >>= STATUS1_ADC_SHIFT;
+
+ /*
+ * Check current cable state/cable type and store cable type
+ * (info->prev_cable_type) for handling cable when cable is
+ * detached.
+ */
+ if (adc == MAX14577_MUIC_ADC_OPEN) {
+ *attached = false;
+
+ cable_type = info->prev_cable_type;
+ info->prev_cable_type = MAX14577_MUIC_ADC_OPEN;
+ } else {
+ *attached = true;
+
+ cable_type = info->prev_cable_type = adc;
+ }
+ break;
+ case MAX14577_CABLE_GROUP_CHG:
+ /*
+ * Read charger type to check cable type and decide cable state
+ * according to type of charger cable.
+ */
+ chg_type = info->status[MAX14577_MUIC_STATUS2] &
+ STATUS2_CHGTYP_MASK;
+ chg_type >>= STATUS2_CHGTYP_SHIFT;
+
+ if (chg_type == MAX14577_CHARGER_TYPE_NONE) {
+ *attached = false;
+
+ cable_type = info->prev_chg_type;
+ info->prev_chg_type = MAX14577_CHARGER_TYPE_NONE;
+ } else {
+ *attached = true;
+
+ /*
+ * Check current cable state/cable type and store cable
+ * type(info->prev_chg_type) for handling cable when
+ * charger cable is detached.
+ */
+ cable_type = info->prev_chg_type = chg_type;
+ }
+
+ break;
+ case MAX14577_CABLE_GROUP_VBVOLT:
+ /*
+ * Read ADC value to check cable type and decide cable state
+ * according to cable type
+ */
+ adc = info->status[MAX14577_MUIC_STATUS1] & STATUS1_ADC_MASK;
+ adc >>= STATUS1_ADC_SHIFT;
+ chg_type = info->status[MAX14577_MUIC_STATUS2] &
+ STATUS2_CHGTYP_MASK;
+ chg_type >>= STATUS2_CHGTYP_SHIFT;
+
+ if (adc == MAX14577_MUIC_ADC_OPEN
+ && chg_type == MAX14577_CHARGER_TYPE_NONE)
+ *attached = false;
+ else
+ *attached = true;
+
+ /*
+ * Read vbvolt field, if vbvolt is 1,
+ * this cable is used for charging.
+ */
+ vbvolt = info->status[MAX14577_MUIC_STATUS2] &
+ STATUS2_VBVOLT_MASK;
+ vbvolt >>= STATUS2_VBVOLT_SHIFT;
+
+ cable_type = vbvolt;
+
+ break;
+ default:
+ dev_err(info->dev, "Unknown cable group (%d)\n", group);
+ cable_type = -EINVAL;
+ break;
+ }
+
+ return cable_type;
+}
+
+static int max14577_muic_jig_handler(struct max14577_muic_info *info,
+ int cable_type, bool attached)
+{
+ char cable_name[32];
+ int ret = 0;
+ int vbvolt;
+ bool unused;
+ u8 path = CTRL1_SW_OPEN;
+
+ vbvolt = max14577_muic_get_cable_type(info, MAX14577_CABLE_GROUP_VBVOLT,
+ &unused);
+
+ dev_dbg(info->dev,
+ "external connector is %s (adc:0x%02x)\n",
+ attached ? "attached" : "detached", cable_type);
+
+ switch (cable_type) {
+ case MAX14577_MUIC_ADC_FACTORY_MODE_USB_OFF: /* ADC_JIG_USB_OFF */
+ /* PATH:AP_USB */
+ strcpy(cable_name, "JIG-USB-OFF");
+ path = CTRL1_SW_USB;
+ break;
+ case MAX14577_MUIC_ADC_FACTORY_MODE_USB_ON: /* ADC_JIG_USB_ON */
+ /*
+ * PATH:AP_USB
+ * This ADC may happen also when USB cable (VBVolt on) is
+ * attached with factory button set to D/L mode.
+ * In this case the extcon_name must be "USB" for proper
+ * hard-reset of device.
+ */
+
+ if (vbvolt)
+ strcpy(cable_name, "USB");
+ else
+ strcpy(cable_name, "JIG-USB-ON");
+
+ path = CTRL1_SW_USB;
+ break;
+ case MAX14577_MUIC_ADC_FACTORY_MODE_UART_OFF: /* ADC_JIG_UART_OFF */
+ /* PATH:AP_UART */
+ strcpy(cable_name, "JIG-UART-OFF");
+ path = CTRL1_SW_UART;
+ break;
+ default:
+ dev_err(info->dev, "failed to detect %s jig cable\n",
+ attached ? "attached" : "detached");
+ return -EINVAL;
+ }
+
+ ret = max14577_muic_set_path(info, path, attached);
+ if (ret < 0)
+ return ret;
+
+ extcon_set_cable_state(info->edev, cable_name, attached);
+
+ return 0;
+}
+
+static int max14577_muic_adc_handler(struct max14577_muic_info *info)
+{
+ int cable_type;
+ bool attached;
+ int ret = 0;
+
+ /* Check accessory state which is either detached or attached */
+ cable_type = max14577_muic_get_cable_type(info,
+ MAX14577_CABLE_GROUP_ADC, &attached);
+
+ dev_dbg(info->dev,
+ "external connector is %s (adc:0x%02x, prev_adc:0x%x)\n",
+ attached ? "attached" : "detached", cable_type,
+ info->prev_cable_type);
+
+ switch (cable_type) {
+ case MAX14577_MUIC_ADC_FACTORY_MODE_USB_OFF:
+ case MAX14577_MUIC_ADC_FACTORY_MODE_USB_ON:
+ case MAX14577_MUIC_ADC_FACTORY_MODE_UART_OFF:
+ /* JIG */
+ ret = max14577_muic_jig_handler(info, cable_type, attached);
+ if (ret < 0)
+ return ret;
+ break;
+ case MAX14577_MUIC_ADC_GROUND:
+ case MAX14577_MUIC_ADC_SEND_END_BUTTON:
+ case MAX14577_MUIC_ADC_REMOTE_S1_BUTTON:
+ case MAX14577_MUIC_ADC_REMOTE_S2_BUTTON:
+ case MAX14577_MUIC_ADC_REMOTE_S3_BUTTON:
+ case MAX14577_MUIC_ADC_REMOTE_S4_BUTTON:
+ case MAX14577_MUIC_ADC_REMOTE_S5_BUTTON:
+ case MAX14577_MUIC_ADC_REMOTE_S6_BUTTON:
+ case MAX14577_MUIC_ADC_REMOTE_S7_BUTTON:
+ case MAX14577_MUIC_ADC_REMOTE_S8_BUTTON:
+ case MAX14577_MUIC_ADC_REMOTE_S9_BUTTON:
+ case MAX14577_MUIC_ADC_REMOTE_S10_BUTTON:
+ case MAX14577_MUIC_ADC_REMOTE_S11_BUTTON:
+ case MAX14577_MUIC_ADC_REMOTE_S12_BUTTON:
+ case MAX14577_MUIC_ADC_RESERVED_ACC_1:
+ case MAX14577_MUIC_ADC_RESERVED_ACC_2:
+ case MAX14577_MUIC_ADC_RESERVED_ACC_3:
+ case MAX14577_MUIC_ADC_RESERVED_ACC_4:
+ case MAX14577_MUIC_ADC_RESERVED_ACC_5:
+ case MAX14577_MUIC_ADC_AUDIO_DEVICE_TYPE2:
+ case MAX14577_MUIC_ADC_PHONE_POWERED_DEV:
+ case MAX14577_MUIC_ADC_TTY_CONVERTER:
+ case MAX14577_MUIC_ADC_UART_CABLE:
+ case MAX14577_MUIC_ADC_CEA936A_TYPE1_CHG:
+ case MAX14577_MUIC_ADC_AV_CABLE_NOLOAD:
+ case MAX14577_MUIC_ADC_CEA936A_TYPE2_CHG:
+ case MAX14577_MUIC_ADC_FACTORY_MODE_UART_ON:
+ case MAX14577_MUIC_ADC_AUDIO_DEVICE_TYPE1:
+ /*
+ * This accessory isn't used in general case if it is specially
+ * needed to detect additional accessory, should implement
+ * proper operation when this accessory is attached/detached.
+ */
+ dev_info(info->dev,
+ "accessory is %s but it isn't used (adc:0x%x)\n",
+ attached ? "attached" : "detached", cable_type);
+ return -EAGAIN;
+ default:
+ dev_err(info->dev,
+ "failed to detect %s accessory (adc:0x%x)\n",
+ attached ? "attached" : "detached", cable_type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int max14577_muic_chg_handler(struct max14577_muic_info *info)
+{
+ int chg_type;
+ bool attached;
+ int ret = 0;
+
+ chg_type = max14577_muic_get_cable_type(info,
+ MAX14577_CABLE_GROUP_CHG, &attached);
+
+ dev_dbg(info->dev,
+ "external connector is %s(chg_type:0x%x, prev_chg_type:0x%x)\n",
+ attached ? "attached" : "detached",
+ chg_type, info->prev_chg_type);
+
+ switch (chg_type) {
+ case MAX14577_CHARGER_TYPE_USB:
+ /* PATH:AP_USB */
+ ret = max14577_muic_set_path(info, info->path_usb, attached);
+ if (ret < 0)
+ return ret;
+
+ extcon_set_cable_state(info->edev, "USB", attached);
+ break;
+ case MAX14577_CHARGER_TYPE_DEDICATED_CHG:
+ extcon_set_cable_state(info->edev, "TA", attached);
+ break;
+ case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT:
+ extcon_set_cable_state(info->edev,
+ "Charge-downstream", attached);
+ break;
+ case MAX14577_CHARGER_TYPE_SPECIAL_500MA:
+ extcon_set_cable_state(info->edev, "Slow-charger", attached);
+ break;
+ case MAX14577_CHARGER_TYPE_SPECIAL_1A:
+ extcon_set_cable_state(info->edev, "Fast-charger", attached);
+ break;
+ case MAX14577_CHARGER_TYPE_NONE:
+ case MAX14577_CHARGER_TYPE_DEAD_BATTERY:
+ break;
+ default:
+ dev_err(info->dev,
+ "failed to detect %s accessory (chg_type:0x%x)\n",
+ attached ? "attached" : "detached", chg_type);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void max14577_muic_irq_work(struct work_struct *work)
+{
+ struct max14577_muic_info *info = container_of(work,
+ struct max14577_muic_info, irq_work);
+ int ret = 0;
+
+ if (!info->edev)
+ return;
+
+ mutex_lock(&info->mutex);
+
+ ret = max14577_bulk_read(info->max14577->regmap,
+ MAX14577_MUIC_REG_STATUS1, info->status, 2);
+ if (ret) {
+ dev_err(info->dev, "failed to read MUIC register\n");
+ mutex_unlock(&info->mutex);
+ return;
+ }
+
+ if (info->irq_adc) {
+ ret = max14577_muic_adc_handler(info);
+ info->irq_adc = false;
+ }
+ if (info->irq_chg) {
+ ret = max14577_muic_chg_handler(info);
+ info->irq_chg = false;
+ }
+
+ if (ret < 0)
+ dev_err(info->dev, "failed to handle MUIC interrupt\n");
+
+ mutex_unlock(&info->mutex);
+
+ return;
+}
+
+static irqreturn_t max14577_muic_irq_handler(int irq, void *data)
+{
+ struct max14577_muic_info *info = data;
+ int i, irq_type = -1;
+
+ /*
+ * We may be called multiple times for different nested IRQ-s.
+ * Including changes in INT1_ADC and INT2_CGHTYP at once.
+ * However we only need to know whether it was ADC, charger
+ * or both interrupts so decode IRQ and turn on proper flags.
+ */
+ for (i = 0; i < ARRAY_SIZE(muic_irqs); i++)
+ if (irq == muic_irqs[i].virq)
+ irq_type = muic_irqs[i].irq;
+
+ switch (irq_type) {
+ case MAX14577_IRQ_INT1_ADC:
+ case MAX14577_IRQ_INT1_ADCLOW:
+ case MAX14577_IRQ_INT1_ADCERR:
+ /* Handle all of accessory except for
+ type of charger accessory */
+ info->irq_adc = true;
+ break;
+ case MAX14577_IRQ_INT2_CHGTYP:
+ case MAX14577_IRQ_INT2_CHGDETRUN:
+ case MAX14577_IRQ_INT2_DCDTMR:
+ case MAX14577_IRQ_INT2_DBCHG:
+ case MAX14577_IRQ_INT2_VBVOLT:
+ /* Handle charger accessory */
+ info->irq_chg = true;
+ break;
+ default:
+ dev_err(info->dev, "muic interrupt: irq %d occurred, skipped\n",
+ irq_type);
+ return IRQ_HANDLED;
+ }
+ schedule_work(&info->irq_work);
+
+ return IRQ_HANDLED;
+}
+
+static int max14577_muic_detect_accessory(struct max14577_muic_info *info)
+{
+ int ret = 0;
+ int adc;
+ int chg_type;
+ bool attached;
+
+ mutex_lock(&info->mutex);
+
+ /* Read STATUSx register to detect accessory */
+ ret = max14577_bulk_read(info->max14577->regmap,
+ MAX14577_MUIC_REG_STATUS1, info->status, 2);
+ if (ret) {
+ dev_err(info->dev, "failed to read MUIC register\n");
+ mutex_unlock(&info->mutex);
+ return ret;
+ }
+
+ adc = max14577_muic_get_cable_type(info, MAX14577_CABLE_GROUP_ADC,
+ &attached);
+ if (attached && adc != MAX14577_MUIC_ADC_OPEN) {
+ ret = max14577_muic_adc_handler(info);
+ if (ret < 0) {
+ dev_err(info->dev, "Cannot detect accessory\n");
+ mutex_unlock(&info->mutex);
+ return ret;
+ }
+ }
+
+ chg_type = max14577_muic_get_cable_type(info, MAX14577_CABLE_GROUP_CHG,
+ &attached);
+ if (attached && chg_type != MAX14577_CHARGER_TYPE_NONE) {
+ ret = max14577_muic_chg_handler(info);
+ if (ret < 0) {
+ dev_err(info->dev, "Cannot detect charger accessory\n");
+ mutex_unlock(&info->mutex);
+ return ret;
+ }
+ }
+
+ mutex_unlock(&info->mutex);
+
+ return 0;
+}
+
+static void max14577_muic_detect_cable_wq(struct work_struct *work)
+{
+ struct max14577_muic_info *info = container_of(to_delayed_work(work),
+ struct max14577_muic_info, wq_detcable);
+
+ max14577_muic_detect_accessory(info);
+}
+
+static int max14577_muic_probe(struct platform_device *pdev)
+{
+ struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
+ struct max14577_muic_info *info;
+ int delay_jiffies;
+ int ret;
+ int i;
+ u8 id;
+
+ info = devm_kzalloc(&pdev->dev, sizeof(struct max14577_muic_info),
+ GFP_KERNEL);
+ if (!info) {
+ dev_err(&pdev->dev, "failed to allocate memory\n");
+ return -ENOMEM;
+ }
+ info->dev = &pdev->dev;
+ info->max14577 = max14577;
+
+ platform_set_drvdata(pdev, info);
+ mutex_init(&info->mutex);
+
+ INIT_WORK(&info->irq_work, max14577_muic_irq_work);
+
+ /* Support irq domain for max14577 MUIC device */
+ for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) {
+ struct max14577_muic_irq *muic_irq = &muic_irqs[i];
+ unsigned int virq = 0;
+
+ virq = irq_create_mapping(max14577->irq_domain, muic_irq->irq);
+ if (!virq) {
+ ret = -EINVAL;
+ goto err_irq;
+ }
+ muic_irq->virq = virq;
+
+ ret = request_threaded_irq(virq, NULL,
+ max14577_muic_irq_handler,
+ IRQF_NO_SUSPEND,
+ muic_irq->name, info);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed: irq request (IRQ: %d,"
+ " error :%d)\n",
+ muic_irq->irq, ret);
+ goto err_irq;
+ }
+ }
+
+ /* Initialize extcon device */
+ info->edev = devm_kzalloc(&pdev->dev, sizeof(struct extcon_dev),
+ GFP_KERNEL);
+ if (!info->edev) {
+ dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
+ ret = -ENOMEM;
+ goto err_irq;
+ }
+ info->edev->name = DEV_NAME;
+ info->edev->supported_cable = max14577_extcon_cable;
+ ret = extcon_dev_register(info->edev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register extcon device\n");
+ goto err_irq;
+ }
+
+ /* Default h/w line path */
+ info->path_usb = CTRL1_SW_USB;
+ info->path_uart = CTRL1_SW_UART;
+ delay_jiffies = msecs_to_jiffies(DELAY_MS_DEFAULT);
+
+ /* Set initial path for UART */
+ max14577_muic_set_path(info, info->path_uart, true);
+
+ /* Check revision number of MUIC device*/
+ ret = max14577_read_reg(info->max14577->regmap,
+ MAX14577_REG_DEVICEID, &id);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to read revision number\n");
+ goto err_extcon;
+ }
+ dev_info(info->dev, "device ID : 0x%x\n", id);
+
+ /* Set ADC debounce time */
+ max14577_muic_set_debounce_time(info, ADC_DEBOUNCE_TIME_25MS);
+
+ /*
+ * Detect accessory after completing the initialization of platform
+ *
+ * - Use delayed workqueue to detect cable state and then
+ * notify cable state to notifiee/platform through uevent.
+ * After completing the booting of platform, the extcon provider
+ * driver should notify cable state to upper layer.
+ */
+ INIT_DELAYED_WORK(&info->wq_detcable, max14577_muic_detect_cable_wq);
+ schedule_delayed_work(&info->wq_detcable, delay_jiffies);
+
+ return ret;
+
+err_extcon:
+ extcon_dev_unregister(info->edev);
+err_irq:
+ while (--i >= 0)
+ free_irq(muic_irqs[i].virq, info);
+ return ret;
+}
+
+static int max14577_muic_remove(struct platform_device *pdev)
+{
+ struct max14577_muic_info *info = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(muic_irqs); i++)
+ free_irq(muic_irqs[i].virq, info);
+ cancel_work_sync(&info->irq_work);
+ extcon_dev_unregister(info->edev);
+
+ return 0;
+}
+
+static struct platform_driver max14577_muic_driver = {
+ .driver = {
+ .name = DEV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = max14577_muic_probe,
+ .remove = max14577_muic_remove,
+};
+
+module_platform_driver(max14577_muic_driver);
+
+MODULE_DESCRIPTION("Maxim max14577 Extcon driver");
+MODULE_AUTHOR("Chanwoo Choi <cw00.choi@samsung.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:extcon-max14577");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 3/5] charger: max14577: Add charger support for Maxim 14577
2013-11-20 14:12 [PATCH v2 0/5] mfd: max14577: Add max14577 MFD drivers Krzysztof Kozlowski
2013-11-20 14:12 ` [PATCH v2 1/5] mfd: max14577: Add max14577 MFD driver core Krzysztof Kozlowski
2013-11-20 14:12 ` [PATCH v2 2/5] extcon: max14577: Add extcon-max14577 driver to support MUIC device Krzysztof Kozlowski
@ 2013-11-20 14:12 ` Krzysztof Kozlowski
2013-11-20 14:12 ` [PATCH v2 4/5] regulator: max14577: Add regulator driver " Krzysztof Kozlowski
2013-11-20 14:12 ` [PATCH v2 5/5] mfd: max14577: Add device tree bindings document Krzysztof Kozlowski
4 siblings, 0 replies; 16+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-20 14:12 UTC (permalink / raw)
To: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Lee Jones,
Anton Vorontsov, David Woodhouse, Liam Girdwood, Mark Brown,
Grant Likely, Rob Herring, linux-kernel, devicetree, Pawel Moll,
Stephen Warren, Ian Campbell, Rob Landley, linux-doc,
linux-arm-kernel
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park,
Krzysztof Kozlowski
MAX14577 chip is a multi-function device which includes MUIC, charger
and voltage regulator. The driver is located in drivers/mfd.
This patch supports battery charging control of MAX14577 chip and
provides power supply class information to userspace.
Signed-off-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
drivers/power/Kconfig | 7 +
drivers/power/Makefile | 1 +
drivers/power/max14577_charger.c | 323 ++++++++++++++++++++++++++++++++++++++
3 files changed, 331 insertions(+)
create mode 100644 drivers/power/max14577_charger.c
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 5e2054a..9b6d466 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -316,6 +316,13 @@ config CHARGER_MANAGER
runtime and in suspend-to-RAM by waking up the system periodically
with help of suspend_again support.
+config CHARGER_MAX14577
+ tristate "Maxim MAX14577 MUIC battery charger driver"
+ depends on MFD_MAX14577
+ help
+ Say Y to enable support for the battery charger control sysfs and
+ platform data of MAX14577 MUICs.
+
config CHARGER_MAX8997
tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver"
depends on MFD_MAX8997 && REGULATOR_MAX8997
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 372b4e8..ee54a3e 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o
obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
+obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o
diff --git a/drivers/power/max14577_charger.c b/drivers/power/max14577_charger.c
new file mode 100644
index 0000000..b364c6e
--- /dev/null
+++ b/drivers/power/max14577_charger.c
@@ -0,0 +1,323 @@
+/*
+ * max14577_charger.c - Battery charger driver for the Maxim 14577
+ *
+ * Copyright (C) 2013 Samsung Electronics
+ * Krzysztof Kozlowski <k.kozlowski@samsung.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/max14577-private.h>
+
+struct max14577_charger {
+ struct device *dev;
+ struct max14577 *max14577;
+ struct power_supply charger;
+
+ unsigned int charging_state;
+ unsigned int battery_state;
+};
+
+static int max14577_get_charger_state(struct max14577_charger *chg)
+{
+ struct regmap *rmap = chg->max14577->regmap;
+ int state = POWER_SUPPLY_STATUS_DISCHARGING;
+ u8 reg_data;
+
+ /*
+ * Charging occurs only if:
+ * - CHGCTRL2/MBCHOSTEN == 1
+ * - STATUS2/CGMBC == 1
+ *
+ * TODO:
+ * - handle FULL after Top-off timer (EOC register may be off
+ * and the charger won't be charging although MBCHOSTEN is on)
+ * - handle properly dead-battery charging (respect timer)
+ * - handle timers (fast-charge and prequal) /MBCCHGERR/
+ */
+ max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2, ®_data);
+ if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0)
+ goto state_set;
+
+ max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data);
+ if (reg_data & STATUS3_CGMBC_MASK) {
+ /* Charger or USB-cable is connected */
+ if (reg_data & STATUS3_EOC_MASK)
+ state = POWER_SUPPLY_STATUS_FULL;
+ else
+ state = POWER_SUPPLY_STATUS_CHARGING;
+ goto state_set;
+ }
+
+state_set:
+ chg->charging_state = state;
+ return state;
+}
+
+/*
+ * Supported charge types:
+ * - POWER_SUPPLY_CHARGE_TYPE_NONE
+ * - POWER_SUPPLY_CHARGE_TYPE_FAST
+ */
+static int max14577_get_charge_type(struct max14577_charger *chg)
+{
+ /*
+ * TODO: CHARGE_TYPE_TRICKLE (VCHGR_RC or EOC)?
+ * As spec says:
+ * [after reaching EOC interrupt]
+ * "When the battery is fully charged, the 30-minute (typ)
+ * top-off timer starts. The device continues to trickle
+ * charge the battery until the top-off timer runs out."
+ */
+ if (max14577_get_charger_state(chg) == POWER_SUPPLY_STATUS_CHARGING)
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+}
+
+static int max14577_get_online(struct max14577_charger *chg)
+{
+ struct regmap *rmap = chg->max14577->regmap;
+ u8 reg_data;
+
+ max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data);
+ reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT);
+ switch (reg_data) {
+ case MAX14577_CHARGER_TYPE_USB:
+ case MAX14577_CHARGER_TYPE_DEDICATED_CHG:
+ case MAX14577_CHARGER_TYPE_SPECIAL_500MA:
+ case MAX14577_CHARGER_TYPE_SPECIAL_1A:
+ case MAX14577_CHARGER_TYPE_DEAD_BATTERY:
+ return 1;
+ case MAX14577_CHARGER_TYPE_NONE:
+ case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT:
+ case MAX14577_CHARGER_TYPE_RESERVED:
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Supported health statuses:
+ * - POWER_SUPPLY_HEALTH_DEAD
+ * - POWER_SUPPLY_HEALTH_OVERVOLTAGE
+ * - POWER_SUPPLY_HEALTH_GOOD
+ */
+static int max14577_get_battery_health(struct max14577_charger *chg)
+{
+ struct regmap *rmap = chg->max14577->regmap;
+ int state = POWER_SUPPLY_HEALTH_GOOD;
+ u8 reg_data;
+
+ max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data);
+ reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT);
+ if (reg_data == MAX14577_CHARGER_TYPE_DEAD_BATTERY) {
+ state = POWER_SUPPLY_HEALTH_DEAD;
+ goto state_set;
+ }
+
+ max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data);
+ if (reg_data & STATUS3_OVP_MASK) {
+ state = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ goto state_set;
+ }
+
+state_set:
+ chg->battery_state = state;
+ return state;
+}
+
+/*
+ * Always returns 1.
+ * The max14577 chip doesn't report any status of battery presence.
+ * Lets assume that it will always be used with some battery.
+ */
+static int max14577_get_present(struct max14577_charger *chg)
+{
+ return 1;
+}
+
+/*
+ * Sets charger registers to proper and safe default values.
+ * Some of these values are equal to defaults in MAX14577E
+ * data sheet but there are minor differences.
+ */
+static void max14577_charger_reg_init(struct max14577_charger *chg)
+{
+ struct regmap *rmap = chg->max14577->regmap;
+ u8 reg_data;
+
+ /*
+ * Charger-Type Manual Detection, default off (set CHGTYPMAN to 0)
+ * Charger-Detection Enable, default on (set CHGDETEN to 1)
+ * Combined mask of CHGDETEN and CHGTYPMAN will zero the CHGTYPMAN bit
+ */
+ reg_data = 0x1 << CDETCTRL1_CHGDETEN_SHIFT;
+ max14577_update_reg(rmap, MAX14577_REG_CDETCTRL1,
+ CDETCTRL1_CHGDETEN_MASK | CDETCTRL1_CHGTYPMAN_MASK,
+ reg_data);
+
+ /* Battery Fast-Charge Timer, from SM-V700: 6hrs */
+ reg_data = 0x3 << CHGCTRL1_TCHW_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL1, reg_data);
+
+ /*
+ * Wall-Adapter Rapid Charge, default on
+ * Battery-Charger, default on
+ */
+ reg_data = 0x1 << CHGCTRL2_VCHGR_RC_SHIFT;
+ reg_data |= 0x1 << CHGCTRL2_MBCHOSTEN_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL2, reg_data);
+
+ /* Battery-Charger Constant Voltage (CV) Mode, from SM-V700: 4.35V */
+ reg_data = 0xf << CHGCTRL3_MBCCVWRC_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL3, reg_data);
+
+ /*
+ * Fast Battery-Charge Current Low, default 200-950mA
+ * Fast Battery-Charge Current High, from SM-V700: 450mA
+ */
+ reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT;
+ reg_data |= 0x5 << CHGCTRL4_MBCICHWRCH_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL4, reg_data);
+
+ /* End-of-Charge Current, from SM-V700: 50mA */
+ reg_data = 0x0 << CHGCTRL5_EOCS_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL5, reg_data);
+
+ /* Auto Charging Stop, default off */
+ reg_data = 0x0 << CHGCTRL6_AUTOSTOP_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL6, reg_data);
+
+ /* Overvoltage-Protection Threshold, from SM-V700: 6.5V */
+ reg_data = 0x2 << CHGCTRL7_OTPCGHCVS_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL7, reg_data);
+}
+
+/* Support property from charger */
+static enum power_supply_property max14577_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static const char *model_name = "MAX14577";
+static const char *manufacturer = "Maxim Integrated";
+static int max14577_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max14577_charger *chg = container_of(psy,
+ struct max14577_charger,
+ charger);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = max14577_get_charger_state(chg);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = max14577_get_charge_type(chg);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = max14577_get_battery_health(chg);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = max14577_get_present(chg);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = max14577_get_online(chg);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = model_name;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = manufacturer;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int max14577_charger_probe(struct platform_device *pdev)
+{
+ struct max14577_charger *chg;
+ struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
+ int ret;
+
+ chg = devm_kzalloc(&pdev->dev,
+ sizeof(struct max14577_charger), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, chg);
+ chg->dev = &pdev->dev;
+ chg->max14577 = max14577;
+
+ max14577_charger_reg_init(chg);
+
+ chg->charger.name = "max14577-charger",
+ chg->charger.type = POWER_SUPPLY_TYPE_BATTERY,
+ chg->charger.properties = max14577_charger_props,
+ chg->charger.num_properties = ARRAY_SIZE(max14577_charger_props),
+ chg->charger.get_property = max14577_charger_get_property,
+
+ ret = power_supply_register(&pdev->dev, &chg->charger);
+ if (ret) {
+ dev_err(&pdev->dev, "failed: power supply register\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int max14577_charger_remove(struct platform_device *pdev)
+{
+ struct max14577_charger *chg = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&chg->charger);
+
+ return 0;
+}
+
+static struct platform_driver max14577_charger_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "max14577-charger",
+ },
+ .probe = max14577_charger_probe,
+ .remove = max14577_charger_remove,
+};
+
+static int __init max14577_charger_init(void)
+{
+ return platform_driver_register(&max14577_charger_driver);
+}
+
+static void __exit max14577_charger_exit(void)
+{
+ platform_driver_unregister(&max14577_charger_driver);
+}
+
+module_init(max14577_charger_init);
+module_exit(max14577_charger_exit);
+
+MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>");
+MODULE_DESCRIPTION("MAXIM 14577 charger driver");
+MODULE_LICENSE("GPL");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v2 4/5] regulator: max14577: Add regulator driver for Maxim 14577
2013-11-20 14:12 [PATCH v2 0/5] mfd: max14577: Add max14577 MFD drivers Krzysztof Kozlowski
` (2 preceding siblings ...)
2013-11-20 14:12 ` [PATCH v2 3/5] charger: max14577: Add charger support for Maxim 14577 Krzysztof Kozlowski
@ 2013-11-20 14:12 ` Krzysztof Kozlowski
2013-11-20 15:35 ` Mark Brown
2013-11-20 17:54 ` Bartlomiej Zolnierkiewicz
2013-11-20 14:12 ` [PATCH v2 5/5] mfd: max14577: Add device tree bindings document Krzysztof Kozlowski
4 siblings, 2 replies; 16+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-20 14:12 UTC (permalink / raw)
To: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Lee Jones,
Anton Vorontsov, David Woodhouse, Liam Girdwood, Mark Brown,
Grant Likely, Rob Herring, linux-kernel, devicetree, Pawel Moll,
Stephen Warren, Ian Campbell, Rob Landley, linux-doc,
linux-arm-kernel
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park,
Krzysztof Kozlowski
MAX14577 chip is a multi-function device which includes MUIC,
charger and voltage regulator. The driver is located in drivers/mfd.
This patch adds regulator driver for MAX14577 chip. There are two
regulators in this chip:
1. Safeout LDO with constant voltage output of 4.9V. It can be only
enabled or disabled.
2. Current regulator for the charger. It provides current from 90mA up
to 950mA.
Driver supports Device Tree.
Signed-off-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
drivers/regulator/Kconfig | 7 ++
drivers/regulator/Makefile | 1 +
drivers/regulator/max14577.c | 282 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 290 insertions(+)
create mode 100644 drivers/regulator/max14577.c
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index ce785f4..11ee053 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -249,6 +249,13 @@ config REGULATOR_LP8788
help
This driver supports LP8788 voltage regulator chip.
+config REGULATOR_MAX14577
+ tristate "Maxim 14577 regulator"
+ depends on MFD_MAX14577
+ help
+ This driver controls a Maxim 14577 regulator via I2C bus.
+ The regulators include safeout LDO and current regulator 'CHARGER'.
+
config REGULATOR_MAX1586
tristate "Maxim 1586/1587 voltage regulator"
depends on I2C
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 01c597e..654bd43 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_REGULATOR_LP872X) += lp872x.o
obj-$(CONFIG_REGULATOR_LP8788) += lp8788-buck.o
obj-$(CONFIG_REGULATOR_LP8788) += lp8788-ldo.o
obj-$(CONFIG_REGULATOR_LP8755) += lp8755.o
+obj-$(CONFIG_REGULATOR_MAX14577) += max14577.o
obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o
obj-$(CONFIG_REGULATOR_MAX8649) += max8649.o
obj-$(CONFIG_REGULATOR_MAX8660) += max8660.o
diff --git a/drivers/regulator/max14577.c b/drivers/regulator/max14577.c
new file mode 100644
index 0000000..f449e57
--- /dev/null
+++ b/drivers/regulator/max14577.c
@@ -0,0 +1,282 @@
+/*
+ * max14577.c - Regulator driver for the Maxim 14577
+ *
+ * Copyright (C) 2013 Samsung Electronics
+ * Krzysztof Kozlowski <k.kozlowski@samsung.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/mfd/max14577.h>
+#include <linux/mfd/max14577-private.h>
+#include <linux/regulator/of_regulator.h>
+
+struct max14577_regulator {
+ struct device *dev;
+ struct max14577 *max14577;
+ int num_regulators;
+ struct regulator_dev **regulators;
+};
+
+static int max14577_reg_is_enabled(struct regulator_dev *rdev)
+{
+ int rid = rdev_get_id(rdev);
+ struct regmap *rmap = rdev->regmap;
+ u8 reg_data;
+
+ switch (rid) {
+ case MAX14577_CHARGER:
+ max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2, ®_data);
+ if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0)
+ return 0;
+ max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data);
+ if ((reg_data & STATUS3_CGMBC_MASK) == 0)
+ return 0;
+ /* MBCHOSTEN and CGMBC are on */
+ return 1;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int max14577_reg_get_current_limit(struct regulator_dev *rdev)
+{
+ u8 reg_data;
+ struct regmap *rmap = rdev->regmap;
+
+ if (rdev_get_id(rdev) != MAX14577_CHARGER)
+ return -EINVAL;
+
+ max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL4, ®_data);
+
+ if ((reg_data & CHGCTRL4_MBCICHWRCL_MASK) == 0)
+ return MAX14577_REGULATOR_CURRENT_LIMIT_MIN;
+
+ reg_data = ((reg_data & CHGCTRL4_MBCICHWRCH_MASK) >>
+ CHGCTRL4_MBCICHWRCH_SHIFT);
+ return MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START +
+ reg_data * MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP;
+}
+
+static int max14577_reg_set_current_limit(struct regulator_dev *rdev,
+ int min_uA, int max_uA)
+{
+ int i, current_bits = 0xf;
+ u8 reg_data;
+
+ if (rdev_get_id(rdev) != MAX14577_CHARGER)
+ return -EINVAL;
+
+ if (min_uA > MAX14577_REGULATOR_CURRENT_LIMIT_MAX ||
+ max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_MIN)
+ return -EINVAL;
+
+ if (max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START) {
+ /* Less than 200 mA, so set 90mA (turn only Low Bit off) */
+ u8 reg_data = 0x0 << CHGCTRL4_MBCICHWRCL_SHIFT;
+ return max14577_update_reg(rdev->regmap,
+ MAX14577_CHG_REG_CHG_CTRL4,
+ CHGCTRL4_MBCICHWRCL_MASK, reg_data);
+ }
+
+ /* max_uA is in range: <LIMIT_HIGH_START, inifinite>, so search for
+ * valid current starting from LIMIT_MAX. */
+ for (i = MAX14577_REGULATOR_CURRENT_LIMIT_MAX;
+ i >= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START;
+ i -= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP) {
+ if (i <= max_uA)
+ break;
+ current_bits--;
+ }
+ BUG_ON(current_bits < 0); /* Cannot happen */
+ /* Turn Low Bit on (use range 200mA-950 mA) */
+ reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT;
+ /* and set proper High Bits */
+ reg_data |= current_bits << CHGCTRL4_MBCICHWRCH_SHIFT;
+
+ return max14577_update_reg(rdev->regmap, MAX14577_CHG_REG_CHG_CTRL4,
+ CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK,
+ reg_data);
+}
+
+static struct regulator_ops max14577_safeout_ops = {
+ .is_enabled = regulator_is_enabled_regmap,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .list_voltage = regulator_list_voltage_linear,
+};
+
+static struct regulator_ops max14577_charger_ops = {
+ .is_enabled = max14577_reg_is_enabled,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .get_current_limit = max14577_reg_get_current_limit,
+ .set_current_limit = max14577_reg_set_current_limit,
+};
+
+static struct regulator_desc supported_regulators[] = {
+ {
+ .name = "SAFEOUT",
+ .id = MAX14577_SAFEOUT,
+ .ops = &max14577_safeout_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .n_voltages = 1,
+ .min_uV = MAX14577_REGULATOR_SAFEOUT_VOLTAGE,
+ .enable_reg = MAX14577_REG_CONTROL2,
+ .enable_mask = CTRL2_SFOUTORD_MASK,
+ }, {
+ .name = "CHARGER",
+ .id = MAX14577_CHARGER,
+ .ops = &max14577_charger_ops,
+ .type = REGULATOR_CURRENT,
+ .owner = THIS_MODULE,
+ .enable_reg = MAX14577_CHG_REG_CHG_CTRL2,
+ .enable_mask = CHGCTRL2_MBCHOSTEN_MASK,
+ }
+};
+
+#ifdef CONFIG_OF
+static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev,
+ struct max14577_platform_data *pdata)
+{
+ struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
+ struct device_node *np;
+ struct max14577_regulator_platform_data *reg_pdata;
+ struct of_regulator_match rmatch;
+ int i, ret, cnt = 0;
+
+ np = of_get_child_by_name(max14577->dev->of_node, "regulators");
+ if (!np)
+ return -EINVAL;
+
+ reg_pdata = devm_kzalloc(&pdev->dev, sizeof(*reg_pdata) *
+ ARRAY_SIZE(supported_regulators), GFP_KERNEL);
+ if (!reg_pdata)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(supported_regulators); i++) {
+ rmatch.name = supported_regulators[i].name;
+ ret = of_regulator_match(&pdev->dev, np, &rmatch, 1);
+ if (ret != 1)
+ continue;
+ dev_dbg(&pdev->dev, "Found regulator %d/%s\n",
+ supported_regulators[i].id,
+ supported_regulators[i].name);
+ reg_pdata[cnt].id = supported_regulators[i].id;
+ reg_pdata[cnt].initdata = rmatch.init_data;
+ reg_pdata[cnt].of_node = rmatch.of_node;
+ cnt++;
+ }
+
+ pdata->num_regulators = cnt;
+ pdata->regulators = reg_pdata;
+
+ return 0;
+}
+#else
+static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev,
+ struct max14577_platform_data *pdata)
+{
+ return 0;
+}
+#endif
+
+static int max14577_regulator_probe(struct platform_device *pdev)
+{
+ struct max14577_regulator *info;
+ struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
+ struct max14577_platform_data *pdata = dev_get_platdata(max14577->dev);
+ int i, size;
+ struct regulator_config config = {};
+
+ if (!pdata) {
+ /* Parent must provide pdata */
+ dev_err(&pdev->dev, "No MFD driver platform data found.\n");
+ return -ENODEV;
+ }
+
+ if (max14577->dev->of_node) {
+ int ret = max14577_regulator_dt_parse_pdata(pdev, pdata);
+ if (ret)
+ return ret;
+ }
+
+ info = devm_kzalloc(&pdev->dev, sizeof(struct max14577_regulator),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ size = sizeof(struct regulator_dev *) * pdata->num_regulators;
+ info->regulators = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+ if (!info->regulators) {
+ dev_err(&pdev->dev, "Cannot allocate memory for regulators\n");
+ return -ENOMEM;
+ }
+
+ info->dev = &pdev->dev;
+ info->max14577 = max14577;
+ info->num_regulators = pdata->num_regulators;
+
+ config.dev = &pdev->dev;
+ config.regmap = max14577->regmap;
+ config.driver_data = info;
+ platform_set_drvdata(pdev, info);
+
+ for (i = 0; i < pdata->num_regulators; i++) {
+ int id = pdata->regulators[i].id;
+
+ config.init_data = pdata->regulators[i].initdata;
+ config.of_node = pdata->regulators[i].of_node;
+
+ info->regulators[i] = devm_regulator_register(&pdev->dev,
+ &supported_regulators[id], &config);
+ if (IS_ERR(info->regulators[i])) {
+ int ret = PTR_ERR(info->regulators[i]);
+ dev_err(&pdev->dev,
+ "Regulator init failed for ID %d with error: %d\n",
+ id, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static struct platform_driver max14577_regulator_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "max14577-regulator",
+ },
+ .probe = max14577_regulator_probe,
+};
+
+static int __init max14577_regulator_init(void)
+{
+ BUILD_BUG_ON(MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START +
+ MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf !=
+ MAX14577_REGULATOR_CURRENT_LIMIT_MAX);
+ return platform_driver_register(&max14577_regulator_driver);
+}
+subsys_initcall(max14577_regulator_init);
+
+static void __exit max14577_regulator_exit(void)
+{
+ platform_driver_unregister(&max14577_regulator_driver);
+}
+module_exit(max14577_regulator_exit);
+
+MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>");
+MODULE_DESCRIPTION("MAXIM 14577 regulator driver");
+MODULE_LICENSE("GPL");
--
1.7.9.5
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/5] regulator: max14577: Add regulator driver for Maxim 14577
2013-11-20 14:12 ` [PATCH v2 4/5] regulator: max14577: Add regulator driver " Krzysztof Kozlowski
@ 2013-11-20 15:35 ` Mark Brown
2013-11-20 17:54 ` Bartlomiej Zolnierkiewicz
1 sibling, 0 replies; 16+ messages in thread
From: Mark Brown @ 2013-11-20 15:35 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Lee Jones,
Anton Vorontsov, David Woodhouse, Liam Girdwood, Grant Likely,
Rob Herring, linux-kernel, devicetree, Pawel Moll, Stephen Warren,
Ian Campbell, Rob Landley, linux-doc, linux-arm-kernel,
Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park
[-- Attachment #1: Type: text/plain, Size: 438 bytes --]
On Wed, Nov 20, 2013 at 03:12:11PM +0100, Krzysztof Kozlowski wrote:
> + size = sizeof(struct regulator_dev *) * pdata->num_regulators;
> + info->regulators = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
> + if (!info->regulators) {
> + dev_err(&pdev->dev, "Cannot allocate memory for regulators\n");
> + return -ENOMEM;
> + }
The set of regulators in the silicon doesn't vary - you should
unconditionally register all the regulators.
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/5] regulator: max14577: Add regulator driver for Maxim 14577
2013-11-20 14:12 ` [PATCH v2 4/5] regulator: max14577: Add regulator driver " Krzysztof Kozlowski
2013-11-20 15:35 ` Mark Brown
@ 2013-11-20 17:54 ` Bartlomiej Zolnierkiewicz
2013-11-20 17:58 ` Bartlomiej Zolnierkiewicz
1 sibling, 1 reply; 16+ messages in thread
From: Bartlomiej Zolnierkiewicz @ 2013-11-20 17:54 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Lee Jones,
Anton Vorontsov, David Woodhouse, Liam Girdwood, Mark Brown,
Grant Likely, Rob Herring, linux-kernel, devicetree, Pawel Moll,
Stephen Warren, Ian Campbell, Rob Landley, linux-doc,
linux-arm-kernel, Marek Szyprowski, Kyungmin Park
Hi Krzysztof,
On Wednesday, November 20, 2013 03:12:11 PM Krzysztof Kozlowski wrote:
> MAX14577 chip is a multi-function device which includes MUIC,
> charger and voltage regulator. The driver is located in drivers/mfd.
>
> This patch adds regulator driver for MAX14577 chip. There are two
> regulators in this chip:
> 1. Safeout LDO with constant voltage output of 4.9V. It can be only
> enabled or disabled.
> 2. Current regulator for the charger. It provides current from 90mA up
> to 950mA.
> Driver supports Device Tree.
>
> Signed-off-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
> ---
> drivers/regulator/Kconfig | 7 ++
> drivers/regulator/Makefile | 1 +
> drivers/regulator/max14577.c | 282 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 290 insertions(+)
> create mode 100644 drivers/regulator/max14577.c
>
> diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
> index ce785f4..11ee053 100644
> --- a/drivers/regulator/Kconfig
> +++ b/drivers/regulator/Kconfig
> @@ -249,6 +249,13 @@ config REGULATOR_LP8788
> help
> This driver supports LP8788 voltage regulator chip.
>
> +config REGULATOR_MAX14577
> + tristate "Maxim 14577 regulator"
> + depends on MFD_MAX14577
> + help
> + This driver controls a Maxim 14577 regulator via I2C bus.
> + The regulators include safeout LDO and current regulator 'CHARGER'.
> +
> config REGULATOR_MAX1586
> tristate "Maxim 1586/1587 voltage regulator"
> depends on I2C
> diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
> index 01c597e..654bd43 100644
> --- a/drivers/regulator/Makefile
> +++ b/drivers/regulator/Makefile
> @@ -35,6 +35,7 @@ obj-$(CONFIG_REGULATOR_LP872X) += lp872x.o
> obj-$(CONFIG_REGULATOR_LP8788) += lp8788-buck.o
> obj-$(CONFIG_REGULATOR_LP8788) += lp8788-ldo.o
> obj-$(CONFIG_REGULATOR_LP8755) += lp8755.o
> +obj-$(CONFIG_REGULATOR_MAX14577) += max14577.o
> obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o
> obj-$(CONFIG_REGULATOR_MAX8649) += max8649.o
> obj-$(CONFIG_REGULATOR_MAX8660) += max8660.o
> diff --git a/drivers/regulator/max14577.c b/drivers/regulator/max14577.c
> new file mode 100644
> index 0000000..f449e57
> --- /dev/null
> +++ b/drivers/regulator/max14577.c
> @@ -0,0 +1,282 @@
> +/*
> + * max14577.c - Regulator driver for the Maxim 14577
> + *
> + * Copyright (C) 2013 Samsung Electronics
> + * Krzysztof Kozlowski <k.kozlowski@samsung.com>
> + *
> + * 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.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/regulator/driver.h>
> +#include <linux/mfd/max14577.h>
> +#include <linux/mfd/max14577-private.h>
> +#include <linux/regulator/of_regulator.h>
> +
> +struct max14577_regulator {
> + struct device *dev;
> + struct max14577 *max14577;
> + int num_regulators;
> + struct regulator_dev **regulators;
> +};
> +
> +static int max14577_reg_is_enabled(struct regulator_dev *rdev)
> +{
> + int rid = rdev_get_id(rdev);
> + struct regmap *rmap = rdev->regmap;
> + u8 reg_data;
> +
> + switch (rid) {
> + case MAX14577_CHARGER:
> + max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2, ®_data);
> + if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0)
> + return 0;
> + max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data);
> + if ((reg_data & STATUS3_CGMBC_MASK) == 0)
> + return 0;
> + /* MBCHOSTEN and CGMBC are on */
> + return 1;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int max14577_reg_get_current_limit(struct regulator_dev *rdev)
> +{
> + u8 reg_data;
> + struct regmap *rmap = rdev->regmap;
> +
> + if (rdev_get_id(rdev) != MAX14577_CHARGER)
> + return -EINVAL;
> +
> + max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL4, ®_data);
> +
> + if ((reg_data & CHGCTRL4_MBCICHWRCL_MASK) == 0)
> + return MAX14577_REGULATOR_CURRENT_LIMIT_MIN;
> +
> + reg_data = ((reg_data & CHGCTRL4_MBCICHWRCH_MASK) >>
> + CHGCTRL4_MBCICHWRCH_SHIFT);
> + return MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START +
> + reg_data * MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP;
> +}
> +
> +static int max14577_reg_set_current_limit(struct regulator_dev *rdev,
> + int min_uA, int max_uA)
> +{
> + int i, current_bits = 0xf;
> + u8 reg_data;
> +
> + if (rdev_get_id(rdev) != MAX14577_CHARGER)
> + return -EINVAL;
> +
> + if (min_uA > MAX14577_REGULATOR_CURRENT_LIMIT_MAX ||
> + max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_MIN)
> + return -EINVAL;
> +
> + if (max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START) {
> + /* Less than 200 mA, so set 90mA (turn only Low Bit off) */
> + u8 reg_data = 0x0 << CHGCTRL4_MBCICHWRCL_SHIFT;
> + return max14577_update_reg(rdev->regmap,
> + MAX14577_CHG_REG_CHG_CTRL4,
> + CHGCTRL4_MBCICHWRCL_MASK, reg_data);
> + }
> +
> + /* max_uA is in range: <LIMIT_HIGH_START, inifinite>, so search for
> + * valid current starting from LIMIT_MAX. */
> + for (i = MAX14577_REGULATOR_CURRENT_LIMIT_MAX;
> + i >= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START;
> + i -= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP) {
> + if (i <= max_uA)
> + break;
> + current_bits--;
> + }
> + BUG_ON(current_bits < 0); /* Cannot happen */
> + /* Turn Low Bit on (use range 200mA-950 mA) */
> + reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT;
> + /* and set proper High Bits */
> + reg_data |= current_bits << CHGCTRL4_MBCICHWRCH_SHIFT;
> +
> + return max14577_update_reg(rdev->regmap, MAX14577_CHG_REG_CHG_CTRL4,
> + CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK,
> + reg_data);
> +}
> +
> +static struct regulator_ops max14577_safeout_ops = {
> + .is_enabled = regulator_is_enabled_regmap,
> + .enable = regulator_enable_regmap,
> + .disable = regulator_disable_regmap,
> + .list_voltage = regulator_list_voltage_linear,
> +};
> +
> +static struct regulator_ops max14577_charger_ops = {
> + .is_enabled = max14577_reg_is_enabled,
> + .enable = regulator_enable_regmap,
> + .disable = regulator_disable_regmap,
> + .get_current_limit = max14577_reg_get_current_limit,
> + .set_current_limit = max14577_reg_set_current_limit,
> +};
> +
> +static struct regulator_desc supported_regulators[] = {
> + {
> + .name = "SAFEOUT",
> + .id = MAX14577_SAFEOUT,
> + .ops = &max14577_safeout_ops,
> + .type = REGULATOR_VOLTAGE,
> + .owner = THIS_MODULE,
> + .n_voltages = 1,
> + .min_uV = MAX14577_REGULATOR_SAFEOUT_VOLTAGE,
> + .enable_reg = MAX14577_REG_CONTROL2,
> + .enable_mask = CTRL2_SFOUTORD_MASK,
> + }, {
> + .name = "CHARGER",
> + .id = MAX14577_CHARGER,
> + .ops = &max14577_charger_ops,
> + .type = REGULATOR_CURRENT,
> + .owner = THIS_MODULE,
> + .enable_reg = MAX14577_CHG_REG_CHG_CTRL2,
> + .enable_mask = CHGCTRL2_MBCHOSTEN_MASK,
> + }
> +};
> +
> +#ifdef CONFIG_OF
> +static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev,
> + struct max14577_platform_data *pdata)
> +{
> + struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
> + struct device_node *np;
> + struct max14577_regulator_platform_data *reg_pdata;
> + struct of_regulator_match rmatch;
> + int i, ret, cnt = 0;
> +
> + np = of_get_child_by_name(max14577->dev->of_node, "regulators");
> + if (!np)
> + return -EINVAL;
> +
> + reg_pdata = devm_kzalloc(&pdev->dev, sizeof(*reg_pdata) *
> + ARRAY_SIZE(supported_regulators), GFP_KERNEL);
> + if (!reg_pdata)
> + return -ENOMEM;
> +
> + for (i = 0; i < ARRAY_SIZE(supported_regulators); i++) {
> + rmatch.name = supported_regulators[i].name;
> + ret = of_regulator_match(&pdev->dev, np, &rmatch, 1);
> + if (ret != 1)
> + continue;
> + dev_dbg(&pdev->dev, "Found regulator %d/%s\n",
> + supported_regulators[i].id,
> + supported_regulators[i].name);
> + reg_pdata[cnt].id = supported_regulators[i].id;
> + reg_pdata[cnt].initdata = rmatch.init_data;
> + reg_pdata[cnt].of_node = rmatch.of_node;
> + cnt++;
> + }
> +
> + pdata->num_regulators = cnt;
> + pdata->regulators = reg_pdata;
> +
> + return 0;
> +}
> +#else
> +static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev,
> + struct max14577_platform_data *pdata)
> +{
> + return 0;
> +}
> +#endif
> +
> +static int max14577_regulator_probe(struct platform_device *pdev)
> +{
> + struct max14577_regulator *info;
> + struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
> + struct max14577_platform_data *pdata = dev_get_platdata(max14577->dev);
> + int i, size;
> + struct regulator_config config = {};
> +
> + if (!pdata) {
> + /* Parent must provide pdata */
> + dev_err(&pdev->dev, "No MFD driver platform data found.\n");
> + return -ENODEV;
> + }
> +
> + if (max14577->dev->of_node) {
> + int ret = max14577_regulator_dt_parse_pdata(pdev, pdata);
> + if (ret)
> + return ret;
> + }
> +
> + info = devm_kzalloc(&pdev->dev, sizeof(struct max14577_regulator),
> + GFP_KERNEL);
> + if (!info)
> + return -ENOMEM;
> +
> + size = sizeof(struct regulator_dev *) * pdata->num_regulators;
> + info->regulators = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
> + if (!info->regulators) {
> + dev_err(&pdev->dev, "Cannot allocate memory for regulators\n");
> + return -ENOMEM;
> + }
> +
> + info->dev = &pdev->dev;
> + info->max14577 = max14577;
> + info->num_regulators = pdata->num_regulators;
> +
> + config.dev = &pdev->dev;
> + config.regmap = max14577->regmap;
> + config.driver_data = info;
> + platform_set_drvdata(pdev, info);
I don't see any code in this driver that reads from "info" so unless
I'm missing something obvious "info" is redundant and can be removed
altogether.
Best regards,
--
Bartlomiej Zolnierkiewicz
Samsung R&D Institute Poland
Samsung Electronics
> + for (i = 0; i < pdata->num_regulators; i++) {
> + int id = pdata->regulators[i].id;
> +
> + config.init_data = pdata->regulators[i].initdata;
> + config.of_node = pdata->regulators[i].of_node;
> +
> + info->regulators[i] = devm_regulator_register(&pdev->dev,
> + &supported_regulators[id], &config);
> + if (IS_ERR(info->regulators[i])) {
> + int ret = PTR_ERR(info->regulators[i]);
> + dev_err(&pdev->dev,
> + "Regulator init failed for ID %d with error: %d\n",
> + id, ret);
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static struct platform_driver max14577_regulator_driver = {
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "max14577-regulator",
> + },
> + .probe = max14577_regulator_probe,
> +};
> +
> +static int __init max14577_regulator_init(void)
> +{
> + BUILD_BUG_ON(MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START +
> + MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf !=
> + MAX14577_REGULATOR_CURRENT_LIMIT_MAX);
> + return platform_driver_register(&max14577_regulator_driver);
> +}
> +subsys_initcall(max14577_regulator_init);
> +
> +static void __exit max14577_regulator_exit(void)
> +{
> + platform_driver_unregister(&max14577_regulator_driver);
> +}
> +module_exit(max14577_regulator_exit);
> +
> +MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>");
> +MODULE_DESCRIPTION("MAXIM 14577 regulator driver");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/5] regulator: max14577: Add regulator driver for Maxim 14577
2013-11-20 17:54 ` Bartlomiej Zolnierkiewicz
@ 2013-11-20 17:58 ` Bartlomiej Zolnierkiewicz
2013-11-21 14:27 ` Krzysztof Kozlowski
0 siblings, 1 reply; 16+ messages in thread
From: Bartlomiej Zolnierkiewicz @ 2013-11-20 17:58 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Lee Jones,
Anton Vorontsov, David Woodhouse, Liam Girdwood, Mark Brown,
Grant Likely, Rob Herring, linux-kernel, devicetree, Pawel Moll,
Stephen Warren, Ian Campbell, Rob Landley, linux-doc,
linux-arm-kernel, Marek Szyprowski, Kyungmin Park
On Wednesday, November 20, 2013 06:54:34 PM Bartlomiej Zolnierkiewicz wrote:
>
> Hi Krzysztof,
>
> On Wednesday, November 20, 2013 03:12:11 PM Krzysztof Kozlowski wrote:
> > MAX14577 chip is a multi-function device which includes MUIC,
> > charger and voltage regulator. The driver is located in drivers/mfd.
> >
> > This patch adds regulator driver for MAX14577 chip. There are two
> > regulators in this chip:
> > 1. Safeout LDO with constant voltage output of 4.9V. It can be only
> > enabled or disabled.
> > 2. Current regulator for the charger. It provides current from 90mA up
> > to 950mA.
> > Driver supports Device Tree.
> >
> > Signed-off-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
> > Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
> > ---
> > drivers/regulator/Kconfig | 7 ++
> > drivers/regulator/Makefile | 1 +
> > drivers/regulator/max14577.c | 282 ++++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 290 insertions(+)
> > create mode 100644 drivers/regulator/max14577.c
> >
> > diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
> > index ce785f4..11ee053 100644
> > --- a/drivers/regulator/Kconfig
> > +++ b/drivers/regulator/Kconfig
> > @@ -249,6 +249,13 @@ config REGULATOR_LP8788
> > help
> > This driver supports LP8788 voltage regulator chip.
> >
> > +config REGULATOR_MAX14577
> > + tristate "Maxim 14577 regulator"
> > + depends on MFD_MAX14577
> > + help
> > + This driver controls a Maxim 14577 regulator via I2C bus.
> > + The regulators include safeout LDO and current regulator 'CHARGER'.
> > +
> > config REGULATOR_MAX1586
> > tristate "Maxim 1586/1587 voltage regulator"
> > depends on I2C
> > diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
> > index 01c597e..654bd43 100644
> > --- a/drivers/regulator/Makefile
> > +++ b/drivers/regulator/Makefile
> > @@ -35,6 +35,7 @@ obj-$(CONFIG_REGULATOR_LP872X) += lp872x.o
> > obj-$(CONFIG_REGULATOR_LP8788) += lp8788-buck.o
> > obj-$(CONFIG_REGULATOR_LP8788) += lp8788-ldo.o
> > obj-$(CONFIG_REGULATOR_LP8755) += lp8755.o
> > +obj-$(CONFIG_REGULATOR_MAX14577) += max14577.o
> > obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o
> > obj-$(CONFIG_REGULATOR_MAX8649) += max8649.o
> > obj-$(CONFIG_REGULATOR_MAX8660) += max8660.o
> > diff --git a/drivers/regulator/max14577.c b/drivers/regulator/max14577.c
> > new file mode 100644
> > index 0000000..f449e57
> > --- /dev/null
> > +++ b/drivers/regulator/max14577.c
> > @@ -0,0 +1,282 @@
> > +/*
> > + * max14577.c - Regulator driver for the Maxim 14577
> > + *
> > + * Copyright (C) 2013 Samsung Electronics
> > + * Krzysztof Kozlowski <k.kozlowski@samsung.com>
> > + *
> > + * 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.
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/regulator/driver.h>
> > +#include <linux/mfd/max14577.h>
> > +#include <linux/mfd/max14577-private.h>
> > +#include <linux/regulator/of_regulator.h>
> > +
> > +struct max14577_regulator {
> > + struct device *dev;
> > + struct max14577 *max14577;
> > + int num_regulators;
> > + struct regulator_dev **regulators;
> > +};
> > +
> > +static int max14577_reg_is_enabled(struct regulator_dev *rdev)
> > +{
> > + int rid = rdev_get_id(rdev);
> > + struct regmap *rmap = rdev->regmap;
> > + u8 reg_data;
> > +
> > + switch (rid) {
> > + case MAX14577_CHARGER:
> > + max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2, ®_data);
> > + if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0)
> > + return 0;
> > + max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data);
> > + if ((reg_data & STATUS3_CGMBC_MASK) == 0)
> > + return 0;
> > + /* MBCHOSTEN and CGMBC are on */
> > + return 1;
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int max14577_reg_get_current_limit(struct regulator_dev *rdev)
> > +{
> > + u8 reg_data;
> > + struct regmap *rmap = rdev->regmap;
> > +
> > + if (rdev_get_id(rdev) != MAX14577_CHARGER)
> > + return -EINVAL;
> > +
> > + max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL4, ®_data);
> > +
> > + if ((reg_data & CHGCTRL4_MBCICHWRCL_MASK) == 0)
> > + return MAX14577_REGULATOR_CURRENT_LIMIT_MIN;
> > +
> > + reg_data = ((reg_data & CHGCTRL4_MBCICHWRCH_MASK) >>
> > + CHGCTRL4_MBCICHWRCH_SHIFT);
> > + return MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START +
> > + reg_data * MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP;
> > +}
> > +
> > +static int max14577_reg_set_current_limit(struct regulator_dev *rdev,
> > + int min_uA, int max_uA)
> > +{
> > + int i, current_bits = 0xf;
> > + u8 reg_data;
> > +
> > + if (rdev_get_id(rdev) != MAX14577_CHARGER)
> > + return -EINVAL;
> > +
> > + if (min_uA > MAX14577_REGULATOR_CURRENT_LIMIT_MAX ||
> > + max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_MIN)
> > + return -EINVAL;
> > +
> > + if (max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START) {
> > + /* Less than 200 mA, so set 90mA (turn only Low Bit off) */
> > + u8 reg_data = 0x0 << CHGCTRL4_MBCICHWRCL_SHIFT;
> > + return max14577_update_reg(rdev->regmap,
> > + MAX14577_CHG_REG_CHG_CTRL4,
> > + CHGCTRL4_MBCICHWRCL_MASK, reg_data);
> > + }
> > +
> > + /* max_uA is in range: <LIMIT_HIGH_START, inifinite>, so search for
> > + * valid current starting from LIMIT_MAX. */
> > + for (i = MAX14577_REGULATOR_CURRENT_LIMIT_MAX;
> > + i >= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START;
> > + i -= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP) {
> > + if (i <= max_uA)
> > + break;
> > + current_bits--;
> > + }
> > + BUG_ON(current_bits < 0); /* Cannot happen */
> > + /* Turn Low Bit on (use range 200mA-950 mA) */
> > + reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT;
> > + /* and set proper High Bits */
> > + reg_data |= current_bits << CHGCTRL4_MBCICHWRCH_SHIFT;
> > +
> > + return max14577_update_reg(rdev->regmap, MAX14577_CHG_REG_CHG_CTRL4,
> > + CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK,
> > + reg_data);
> > +}
> > +
> > +static struct regulator_ops max14577_safeout_ops = {
> > + .is_enabled = regulator_is_enabled_regmap,
> > + .enable = regulator_enable_regmap,
> > + .disable = regulator_disable_regmap,
> > + .list_voltage = regulator_list_voltage_linear,
> > +};
> > +
> > +static struct regulator_ops max14577_charger_ops = {
> > + .is_enabled = max14577_reg_is_enabled,
> > + .enable = regulator_enable_regmap,
> > + .disable = regulator_disable_regmap,
> > + .get_current_limit = max14577_reg_get_current_limit,
> > + .set_current_limit = max14577_reg_set_current_limit,
> > +};
> > +
> > +static struct regulator_desc supported_regulators[] = {
> > + {
> > + .name = "SAFEOUT",
> > + .id = MAX14577_SAFEOUT,
> > + .ops = &max14577_safeout_ops,
> > + .type = REGULATOR_VOLTAGE,
> > + .owner = THIS_MODULE,
> > + .n_voltages = 1,
> > + .min_uV = MAX14577_REGULATOR_SAFEOUT_VOLTAGE,
> > + .enable_reg = MAX14577_REG_CONTROL2,
> > + .enable_mask = CTRL2_SFOUTORD_MASK,
> > + }, {
> > + .name = "CHARGER",
> > + .id = MAX14577_CHARGER,
> > + .ops = &max14577_charger_ops,
> > + .type = REGULATOR_CURRENT,
> > + .owner = THIS_MODULE,
> > + .enable_reg = MAX14577_CHG_REG_CHG_CTRL2,
> > + .enable_mask = CHGCTRL2_MBCHOSTEN_MASK,
> > + }
> > +};
> > +
> > +#ifdef CONFIG_OF
> > +static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev,
> > + struct max14577_platform_data *pdata)
> > +{
> > + struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
> > + struct device_node *np;
> > + struct max14577_regulator_platform_data *reg_pdata;
> > + struct of_regulator_match rmatch;
> > + int i, ret, cnt = 0;
> > +
> > + np = of_get_child_by_name(max14577->dev->of_node, "regulators");
> > + if (!np)
> > + return -EINVAL;
> > +
> > + reg_pdata = devm_kzalloc(&pdev->dev, sizeof(*reg_pdata) *
> > + ARRAY_SIZE(supported_regulators), GFP_KERNEL);
> > + if (!reg_pdata)
> > + return -ENOMEM;
> > +
> > + for (i = 0; i < ARRAY_SIZE(supported_regulators); i++) {
> > + rmatch.name = supported_regulators[i].name;
> > + ret = of_regulator_match(&pdev->dev, np, &rmatch, 1);
> > + if (ret != 1)
> > + continue;
> > + dev_dbg(&pdev->dev, "Found regulator %d/%s\n",
> > + supported_regulators[i].id,
> > + supported_regulators[i].name);
> > + reg_pdata[cnt].id = supported_regulators[i].id;
> > + reg_pdata[cnt].initdata = rmatch.init_data;
> > + reg_pdata[cnt].of_node = rmatch.of_node;
> > + cnt++;
> > + }
> > +
> > + pdata->num_regulators = cnt;
> > + pdata->regulators = reg_pdata;
> > +
> > + return 0;
> > +}
> > +#else
> > +static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev,
> > + struct max14577_platform_data *pdata)
> > +{
> > + return 0;
> > +}
> > +#endif
> > +
> > +static int max14577_regulator_probe(struct platform_device *pdev)
> > +{
> > + struct max14577_regulator *info;
> > + struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
> > + struct max14577_platform_data *pdata = dev_get_platdata(max14577->dev);
> > + int i, size;
> > + struct regulator_config config = {};
> > +
> > + if (!pdata) {
> > + /* Parent must provide pdata */
> > + dev_err(&pdev->dev, "No MFD driver platform data found.\n");
> > + return -ENODEV;
> > + }
> > +
> > + if (max14577->dev->of_node) {
> > + int ret = max14577_regulator_dt_parse_pdata(pdev, pdata);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + info = devm_kzalloc(&pdev->dev, sizeof(struct max14577_regulator),
> > + GFP_KERNEL);
> > + if (!info)
> > + return -ENOMEM;
> > +
> > + size = sizeof(struct regulator_dev *) * pdata->num_regulators;
> > + info->regulators = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
> > + if (!info->regulators) {
> > + dev_err(&pdev->dev, "Cannot allocate memory for regulators\n");
> > + return -ENOMEM;
> > + }
> > +
> > + info->dev = &pdev->dev;
> > + info->max14577 = max14577;
> > + info->num_regulators = pdata->num_regulators;
> > +
> > + config.dev = &pdev->dev;
> > + config.regmap = max14577->regmap;
> > + config.driver_data = info;
> > + platform_set_drvdata(pdev, info);
>
> I don't see any code in this driver that reads from "info" so unless
Err..
> I'm missing something obvious "info" is redundant and can be removed
> altogether.
>
> Best regards,
> --
> Bartlomiej Zolnierkiewicz
> Samsung R&D Institute Poland
> Samsung Electronics
>
> > + for (i = 0; i < pdata->num_regulators; i++) {
> > + int id = pdata->regulators[i].id;
> > +
> > + config.init_data = pdata->regulators[i].initdata;
> > + config.of_node = pdata->regulators[i].of_node;
> > +
> > + info->regulators[i] = devm_regulator_register(&pdev->dev,
> > + &supported_regulators[id], &config);
> > + if (IS_ERR(info->regulators[i])) {
This code actually reads from "info" but it can be fixed trivially
to not require "info".
Best regards,
--
Bartlomiej Zolnierkiewicz
Samsung R&D Institute Poland
Samsung Electronics
> > + int ret = PTR_ERR(info->regulators[i]);
> > + dev_err(&pdev->dev,
> > + "Regulator init failed for ID %d with error: %d\n",
> > + id, ret);
> > + return ret;
> > + }
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static struct platform_driver max14577_regulator_driver = {
> > + .driver = {
> > + .owner = THIS_MODULE,
> > + .name = "max14577-regulator",
> > + },
> > + .probe = max14577_regulator_probe,
> > +};
> > +
> > +static int __init max14577_regulator_init(void)
> > +{
> > + BUILD_BUG_ON(MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START +
> > + MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf !=
> > + MAX14577_REGULATOR_CURRENT_LIMIT_MAX);
> > + return platform_driver_register(&max14577_regulator_driver);
> > +}
> > +subsys_initcall(max14577_regulator_init);
> > +
> > +static void __exit max14577_regulator_exit(void)
> > +{
> > + platform_driver_unregister(&max14577_regulator_driver);
> > +}
> > +module_exit(max14577_regulator_exit);
> > +
> > +MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>");
> > +MODULE_DESCRIPTION("MAXIM 14577 regulator driver");
> > +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v2 4/5] regulator: max14577: Add regulator driver for Maxim 14577
2013-11-20 17:58 ` Bartlomiej Zolnierkiewicz
@ 2013-11-21 14:27 ` Krzysztof Kozlowski
0 siblings, 0 replies; 16+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-21 14:27 UTC (permalink / raw)
To: Bartlomiej Zolnierkiewicz
Cc: devicetree, Ian Campbell, Samuel Ortiz, Pawel Moll,
Stephen Warren, Lee Jones, Mark Brown, Anton Vorontsov, linux-doc,
Liam Girdwood, Rob Herring, linux-kernel, Chanwoo Choi,
Kyungmin Park, MyungJoo Ham, Rob Landley, Grant Likely,
David Woodhouse, linux-arm-kernel, Marek Szyprowski
On Wed, 2013-11-20 at 18:58 +0100, Bartlomiej Zolnierkiewicz wrote:
> Err..
>
> > I'm missing something obvious "info" is redundant and can be removed
> > altogether.
> >
> > Best regards,
> > --
> > Bartlomiej Zolnierkiewicz
> > Samsung R&D Institute Poland
> > Samsung Electronics
> >
> > > + for (i = 0; i < pdata->num_regulators; i++) {
> > > + int id = pdata->regulators[i].id;
> > > +
> > > + config.init_data = pdata->regulators[i].initdata;
> > > + config.of_node = pdata->regulators[i].of_node;
> > > +
> > > + info->regulators[i] = devm_regulator_register(&pdev->dev,
> > > + &supported_regulators[id], &config);
> > > + if (IS_ERR(info->regulators[i])) {
>
> This code actually reads from "info" but it can be fixed trivially
> to not require "info".
You're right, it can be removed. The info was a left-over from 3.10
where there isn't devm_regulator_register(). Thanks for review.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v2 5/5] mfd: max14577: Add device tree bindings document
2013-11-20 14:12 [PATCH v2 0/5] mfd: max14577: Add max14577 MFD drivers Krzysztof Kozlowski
` (3 preceding siblings ...)
2013-11-20 14:12 ` [PATCH v2 4/5] regulator: max14577: Add regulator driver " Krzysztof Kozlowski
@ 2013-11-20 14:12 ` Krzysztof Kozlowski
4 siblings, 0 replies; 16+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-20 14:12 UTC (permalink / raw)
To: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Lee Jones,
Anton Vorontsov, David Woodhouse, Liam Girdwood, Mark Brown,
Grant Likely, Rob Herring, linux-kernel, devicetree, Pawel Moll,
Stephen Warren, Ian Campbell, Rob Landley, linux-doc,
linux-arm-kernel
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park,
Krzysztof Kozlowski
Add document describing device tree bindings for MAX14577 MFD driver.
Signed-off-by: Krzysztof Kozlowski <k.kozlowski@samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com>
---
Documentation/devicetree/bindings/mfd/max14577.txt | 48 ++++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/max14577.txt
diff --git a/Documentation/devicetree/bindings/mfd/max14577.txt b/Documentation/devicetree/bindings/mfd/max14577.txt
new file mode 100644
index 0000000..20d3866
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/max14577.txt
@@ -0,0 +1,48 @@
+MAXIM MAX14577 multi-function device
+
+MAX14577 is a Multi-function device with Micro-USB Interface Circuit, Li+
+Battery Charger and SFOUT LDO output for powering USB devices. It is
+interfaced to host controller using I2C.
+
+Required properties:
+- compatible : Must be "maxim,max14577".
+- reg : I2C slave address for the max14577 chip.
+- interrupts : IRQ line for the max14577 chip.
+- interrupt-parent : The parent interrupt controller.
+
+Optional properties:
+- regulators : Each regulator should be represented by a child node of the
+ "regulators" node:
+
+ regulator-name {
+ regulator-name = CHARGER/SAFEOUT;
+ /* standard regulator bindings here */
+ };
+
+ Valid names for regulator are: "CHARGER" and "SAFEOUT". The SAFEOUT
+ is a constant voltage regulator so there is no need to specify voltages
+ for it.
+
+ [*] refer Documentation/devicetree/bindings/regulator/regulator.txt
+
+Example:
+ max14577@25 {
+ compatible = "maxim,max14577";
+ reg = <0x25>;
+ interrupt-parent = <&gpx1>;
+ interrupts = <5 0>;
+
+ regulators {
+ safeout_reg: safeout@1 {
+ regulator-compatible = "SAFEOUT";
+ regulator-name = "SAFEOUT";
+ };
+ charger_reg: charger@0 {
+ regulator-compatible = "CHARGER";
+ regulator-name = "CHARGER";
+ regulator-min-microamp = <90000>;
+ regulator-max-microamp = <950000>;
+ regulator-boot-on;
+ };
+ };
+ };
--
1.7.9.5
^ permalink raw reply related [flat|nested] 16+ messages in thread