* [PATCH 0/4] mfd: max14577: Add max14577 MFD drivers
@ 2013-11-13 7:40 Krzysztof Kozlowski
2013-11-13 7:40 ` [PATCH 1/4] mfd: max14577: Add max14577 MFD driver core Krzysztof Kozlowski
` (3 more replies)
0 siblings, 4 replies; 15+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-13 7:40 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-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park,
Krzysztof Kozlowski
Hi,
This patchset adds drivers for MAXIM 14577 chip. The 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.
The patchset consists of following drivers:
1. MFD core driver.
2. Extcon driver for the MUIC (Micro USB Interface Controller).
3. Charger driver using power supply class.
4. Regulator driver for SFOUT and charger.
The patchset is rebased on latest Linus' tree (v3.12-4849-g10d0c97) however
testing was mostly done on 3.10. Except minor change in extcon_dev_register()
function the patchset cleanly applies to 3.10 and 3.12.
Best regards,
Krzysztof Kozlowski
Chanwoo Choi (2):
mfd: max14577: Add max14577 MFD driver core
extcon: max77693: Add extcon-max14577 driver to support MUIC device
Krzysztof Kozlowski (2):
charger: max14577: Add charger support for Maxim 14577
regulator: max14577: Add regulator driver for Maxim 14577
drivers/extcon/Kconfig | 10 +
drivers/extcon/Makefile | 1 +
drivers/extcon/extcon-max14577.c | 806 ++++++++++++++++++++++++++++++++++
drivers/mfd/Kconfig | 13 +
drivers/mfd/Makefile | 1 +
drivers/mfd/max14577-irq.c | 283 ++++++++++++
drivers/mfd/max14577.c | 268 +++++++++++
drivers/power/Kconfig | 7 +
drivers/power/Makefile | 1 +
drivers/power/max14577_charger.c | 327 ++++++++++++++
drivers/regulator/Kconfig | 8 +
drivers/regulator/Makefile | 1 +
drivers/regulator/max14577.c | 365 +++++++++++++++
include/linux/mfd/max14577-private.h | 291 ++++++++++++
include/linux/mfd/max14577.h | 76 ++++
15 files changed, 2458 insertions(+)
create mode 100644 drivers/extcon/extcon-max14577.c
create mode 100644 drivers/mfd/max14577-irq.c
create mode 100644 drivers/mfd/max14577.c
create mode 100644 drivers/power/max14577_charger.c
create mode 100644 drivers/regulator/max14577.c
create mode 100644 include/linux/mfd/max14577-private.h
create mode 100644 include/linux/mfd/max14577.h
--
1.7.9.5
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 1/4] mfd: max14577: Add max14577 MFD driver core
2013-11-13 7:40 [PATCH 0/4] mfd: max14577: Add max14577 MFD drivers Krzysztof Kozlowski
@ 2013-11-13 7:40 ` Krzysztof Kozlowski
[not found] ` <1384328457-5147-2-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
2013-11-13 13:13 ` Mark Brown
[not found] ` <1384328457-5147-1-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
` (2 subsequent siblings)
3 siblings, 2 replies; 15+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-13 7:40 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
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 | 283 +++++++++++++++++++++++++++++++++
drivers/mfd/max14577.c | 268 +++++++++++++++++++++++++++++++
include/linux/mfd/max14577-private.h | 291 ++++++++++++++++++++++++++++++++++
include/linux/mfd/max14577.h | 76 +++++++++
6 files changed, 932 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 914c3d1..f2a1a76 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -309,6 +309,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 15b905c..548c87d 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..9cd8012
--- /dev/null
+++ b/drivers/mfd/max14577-irq.c
@@ -0,0 +1,283 @@
+/*
+ * max14577-irq.c - MFD Interrupt controller support for MAX14577
+ *
+ * Copyright (C) 2013 Samsung Electronics Co.Ltd
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * This driver is based on max8997-irq.c
+ */
+
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.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 ||
+ IS_ERR_OR_NULL(max14577->regmap))
+ 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;
+ struct max14577_platform_data *pdata = max14577->pdata;
+ u8 irq_reg[MAX14577_IRQ_REGS_NUM] = {0};
+ u8 tmp_irq_reg[MAX14577_IRQ_REGS_NUM] = {};
+ int i, gpio_val, cur_irq, retry = IRQ_READ_REG_RETRY_CNT;
+
+ dev_dbg(max14577->dev, "IRQ GPIO pre-state: 0x%02x\n",
+ gpio_get_value(pdata->irq_gpio));
+
+ do {
+ 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];
+
+ dev_info(max14577->dev, "Got interrupts [1:0x%02x, 2:0x%02x, 3:0x%02x]\n",
+ irq_reg[MAX14577_IRQ_INT1], irq_reg[MAX14577_IRQ_INT2],
+ irq_reg[MAX14577_IRQ_INT3]);
+
+ gpio_val = gpio_get_value(pdata->irq_gpio);
+
+ if (gpio_get_value(pdata->irq_gpio) == 0)
+ dev_warn(max14577->dev, "IRQ GPIO is not high, retry reading interrupt registers\n");
+ } while (gpio_val == 0 && --retry > 0);
+
+ 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 max14577_platform_data *pdata = max14577->pdata;
+ struct irq_domain *domain;
+ int i, ret;
+
+ if (!pdata->irq_gpio) {
+ dev_warn(max14577->dev, "No interrupt specified.\n");
+ pdata->irq_base = 0;
+ return 0;
+ }
+
+ mutex_init(&max14577->irq_lock);
+
+ max14577->irq = gpio_to_irq(pdata->irq_gpio);
+ ret = gpio_request(pdata->irq_gpio, "max14577_irq");
+ if (ret) {
+ dev_err(max14577->dev, "Failed requesting GPIO %d: %d\n",
+ pdata->irq_gpio, ret);
+ goto err;
+ }
+ gpio_direction_input(pdata->irq_gpio);
+ gpio_free(pdata->irq_gpio);
+
+ /* 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 (IS_ERR_OR_NULL(max14577->regmap))
+ continue;
+ 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..ef48a19
--- /dev/null
+++ b/drivers/mfd/max14577.c
@@ -0,0 +1,268 @@
+/*
+ * max14577.c - mfd core driver for the Maxim 14577
+ *
+ * Copyright (C) 2013 Samsung Electrnoics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * This driver is based on max8997.c
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/regmap.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/max14577.h>
+#include <linux/mfd/max14577-private.h>
+#include <linux/of_gpio.h>
+
+/* TODO: Same as max77693 */
+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;
+}
+EXPORT_SYMBOL_GPL(max14577_read_reg);
+
+int max14577_bulk_read(struct regmap *map, u8 reg, u8 *buf, int count)
+{
+ return regmap_bulk_read(map, reg, buf, count);
+}
+EXPORT_SYMBOL_GPL(max14577_bulk_read);
+
+int max14577_write_reg(struct regmap *map, u8 reg, u8 value)
+{
+ return regmap_write(map, reg, value);
+}
+EXPORT_SYMBOL_GPL(max14577_write_reg);
+
+int max14577_bulk_write(struct regmap *map, u8 reg, u8 *buf, int count)
+{
+ return regmap_bulk_write(map, reg, buf, count);
+}
+EXPORT_SYMBOL_GPL(max14577_bulk_write);
+
+int max14577_update_reg(struct regmap *map, u8 reg, u8 mask, u8 val)
+{
+ return regmap_update_bits(map, reg, mask, val);
+}
+EXPORT_SYMBOL_GPL(max14577_update_reg);
+
+static struct mfd_cell max14577_devs[] = {
+ { .name = "max14577-muic",
+ .of_compatible = "maxim,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,
+};
+
+#ifdef CONFIG_OF
+static struct max14577_platform_data *max14577_i2c_parse_dt(struct device *dev)
+{
+ struct max14577_platform_data *pdata;
+ struct device_node *np = dev->of_node;
+
+ pdata = devm_kzalloc(dev, sizeof(struct max14577_platform_data),
+ GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ pdata->irq_gpio = of_get_named_gpio(np, "irq_gpio", 0);
+ if (!gpio_is_valid(pdata->irq_gpio)) {
+ dev_err(dev, "Invalid irq_gpio in DT\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (of_property_read_bool(np, "wakeup"))
+ pdata->wakeup = true;
+
+ return pdata;
+}
+#else
+#define max14577_i2c_parse_dt NULL
+#endif
+
+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 (!pdata && i2c->dev.of_node) {
+ pdata = max14577_i2c_parse_dt(&i2c->dev);
+ 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 == NULL)
+ 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, pdata->wakeup);
+
+ 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);
+
+ 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);
+
+ 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
+
+const struct dev_pm_ops max14577_pm = {
+ .suspend = max14577_suspend,
+ .resume = 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_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..42589a0
--- /dev/null
+++ b/include/linux/mfd/max14577-private.h
@@ -0,0 +1,291 @@
+/*
+ * max14577-private.h - Common API for the Maxim 14577 internal sub chip
+ *
+ * Copyright (C) 2013 Samsung Electrnoics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __MAX14577_PRIVATE_H__
+#define __MAX14577_PRIVATE_H__
+
+#include <linux/i2c.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 i2c API function */
+extern int max14577_read_reg(struct regmap *map, u8 reg, u8 *dest);
+extern int max14577_bulk_read(struct regmap *map, u8 reg, u8 *buf,
+ int count);
+extern int max14577_write_reg(struct regmap *map, u8 reg, u8 value);
+extern int max14577_bulk_write(struct regmap *map, u8 reg, u8 *buf,
+ int count);
+extern int max14577_update_reg(struct regmap *map, u8 reg, u8 mask, u8 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..73b6e06
--- /dev/null
+++ b/include/linux/mfd/max14577.h
@@ -0,0 +1,76 @@
+/*
+ * max14577.h - Driver for the Maxim 14577
+ *
+ * Copyright (C) 2013 Samsung Electrnoics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * 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;
+ int irq_gpio;
+ bool wakeup;
+
+ /* 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] 15+ messages in thread
* [PATCH 2/4] extcon: max77693: Add extcon-max14577 driver to support MUIC device
[not found] ` <1384328457-5147-1-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
@ 2013-11-13 7:40 ` Krzysztof Kozlowski
0 siblings, 0 replies; 15+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-13 7:40 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-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA
Cc: Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park,
Krzysztof Kozlowski
From: Chanwoo Choi <cw00.choi-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
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-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Krzysztof Kozlowski <k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
Signed-off-by: Kyungmin Park <kyungmin.park-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
---
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..e629d81
--- /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-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
+ *
+ * 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_info(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_info(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_info(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_info(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-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:extcon-max14577");
--
1.7.9.5
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 3/4] charger: max14577: Add charger support for Maxim 14577
2013-11-13 7:40 [PATCH 0/4] mfd: max14577: Add max14577 MFD drivers Krzysztof Kozlowski
2013-11-13 7:40 ` [PATCH 1/4] mfd: max14577: Add max14577 MFD driver core Krzysztof Kozlowski
[not found] ` <1384328457-5147-1-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
@ 2013-11-13 7:40 ` Krzysztof Kozlowski
2013-11-13 7:40 ` [PATCH 4/4] regulator: max14577: Add regulator driver " Krzysztof Kozlowski
3 siblings, 0 replies; 15+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-13 7:40 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
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 | 327 ++++++++++++++++++++++++++++++++++++++
3 files changed, 335 insertions(+)
create mode 100644 drivers/power/max14577_charger.c
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index e6f92b45..fb789df 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 a4b7417..aa30084 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..614be6d
--- /dev/null
+++ b/drivers/power/max14577_charger.c
@@ -0,0 +1,327 @@
+/*
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#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 Timerr, 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: 450 mA
+ */
+ 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] 15+ messages in thread
* [PATCH 4/4] regulator: max14577: Add regulator driver for Maxim 14577
2013-11-13 7:40 [PATCH 0/4] mfd: max14577: Add max14577 MFD drivers Krzysztof Kozlowski
` (2 preceding siblings ...)
2013-11-13 7:40 ` [PATCH 3/4] charger: max14577: Add charger support for Maxim 14577 Krzysztof Kozlowski
@ 2013-11-13 7:40 ` Krzysztof Kozlowski
[not found] ` <1384328457-5147-5-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
3 siblings, 1 reply; 15+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-13 7:40 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
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 | 365 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 373 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..e05c445
--- /dev/null
+++ b/drivers/regulator/max14577.c
@@ -0,0 +1,365 @@
+/*
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#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_calc_charge_current(struct regmap *rmap)
+{
+ u8 reg_data;
+ 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 inline int max14577_reg_set_safeout(struct regmap *rmap, int on)
+{
+ u8 reg_data = (on ? 0x1 << CTRL2_SFOUTORD_SHIFT : 0x0);
+
+ return max14577_update_reg(rmap, MAX14577_REG_CONTROL2,
+ CTRL2_SFOUTORD_MASK, reg_data);
+}
+
+static inline int max14577_reg_set_charging(struct regmap *rmap, int on)
+{
+ u8 reg_data = (on ? 0x1 << CHGCTRL2_MBCHOSTEN_SHIFT : 0x0);
+
+ return max14577_update_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2,
+ CHGCTRL2_MBCHOSTEN_MASK, reg_data);
+}
+
+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;
+ case MAX14577_SAFEOUT:
+ max14577_read_reg(rmap, MAX14577_REG_CONTROL2, ®_data);
+ return (reg_data & CTRL2_SFOUTORD_MASK) != 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int max14577_reg_enable(struct regulator_dev *rdev)
+{
+ int rid = rdev_get_id(rdev);
+
+ switch (rid) {
+ case MAX14577_CHARGER:
+ return max14577_reg_set_charging(rdev->regmap, 1);
+ case MAX14577_SAFEOUT:
+ return max14577_reg_set_safeout(rdev->regmap, 1);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int max14577_reg_disable(struct regulator_dev *rdev)
+{
+ int rid = rdev_get_id(rdev);
+
+ switch (rid) {
+ case MAX14577_CHARGER:
+ return max14577_reg_set_charging(rdev->regmap, 0);
+ case MAX14577_SAFEOUT:
+ return max14577_reg_set_safeout(rdev->regmap, 0);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int max14577_reg_get_current_limit(struct regulator_dev *rdev)
+{
+ if (rdev_get_id(rdev) != MAX14577_CHARGER)
+ return -EINVAL;
+
+ return max14577_reg_calc_charge_current(rdev->regmap);
+}
+
+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;
+ struct max14577_regulator *max14577 = rdev_get_drvdata(rdev);
+
+ if (rdev_get_id(rdev) != MAX14577_CHARGER)
+ return -EINVAL;
+
+ dev_info(max14577->dev, "Requested current <%d, %d>\n", min_uA, max_uA);
+ 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;
+ dev_info(max14577->dev, "Setting current %d uA\n",
+ MAX14577_REGULATOR_CURRENT_LIMIT_MIN);
+ 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;
+ dev_info(max14577->dev, "Setting current %d uA (REG: 0x%x)\n", i,
+ reg_data);
+
+ 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 = max14577_reg_is_enabled,
+ .enable = max14577_reg_enable,
+ .disable = max14577_reg_disable,
+ .list_voltage = regulator_list_voltage_linear,
+};
+
+static struct regulator_ops max14577_charger_ops = {
+ .is_enabled = max14577_reg_is_enabled,
+ .enable = max14577_reg_enable,
+ .disable = max14577_reg_disable,
+ .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,
+ }, {
+ .name = "CHARGER",
+ .id = MAX14577_CHARGER,
+ .ops = &max14577_charger_ops,
+ .type = REGULATOR_CURRENT,
+ .owner = THIS_MODULE,
+ }
+};
+
+#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, regulators_size = ARRAY_SIZE(supported_regulators);
+
+ np = of_find_node_by_name(max14577->dev->of_node, "regulators");
+ if (!np)
+ return -EINVAL;
+
+ pdata->num_regulators = of_get_child_count(np);
+ if (pdata->num_regulators == 0)
+ return -ENODEV;
+
+ reg_pdata = devm_kzalloc(&pdev->dev, sizeof(*reg_pdata) *
+ pdata->num_regulators, GFP_KERNEL);
+ if (!reg_pdata)
+ return -ENOMEM;
+
+ dev_info(&pdev->dev, "Parsing %d regulator(s) from supplied DT\n",
+ pdata->num_regulators);
+ for (i = 0; i < regulators_size; i++) {
+ rmatch.name = supported_regulators[i].name;
+ ret = of_regulator_match(&pdev->dev, np, &rmatch, 1);
+ if (ret != 1)
+ continue;
+ dev_info(&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->regulators = reg_pdata;
+ pdata->num_regulators = cnt;
+ dev_info(&pdev->dev, "Found %d supported regulator(s)\n",
+ pdata->num_regulators);
+
+ 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 *max14577_reg;
+ struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
+ struct max14577_platform_data *pdata = dev_get_platdata(max14577->dev);
+ int i, ret, size;
+ struct regulator_config config = {};
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "No platform data found.\n");
+ return -ENODEV;
+ }
+
+ if (max14577->dev->of_node) {
+ ret = max14577_regulator_dt_parse_pdata(pdev, pdata);
+ if (ret)
+ return ret;
+ }
+
+ max14577_reg = devm_kzalloc(&pdev->dev,
+ sizeof(struct max14577_regulator), GFP_KERNEL);
+ if (!max14577_reg)
+ return -ENOMEM;
+
+ size = sizeof(struct regulator_dev *) * pdata->num_regulators;
+ max14577_reg->regulators = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
+ if (!max14577_reg->regulators) {
+ dev_err(&pdev->dev, "Cannot allocate memory for regulators\n");
+ return -ENOMEM;
+ }
+
+ max14577_reg->dev = &pdev->dev;
+ max14577_reg->max14577 = max14577;
+ max14577_reg->num_regulators = pdata->num_regulators;
+
+ config.dev = &pdev->dev;
+ config.regmap = max14577->regmap;
+ config.driver_data = max14577_reg;
+ platform_set_drvdata(pdev, max14577_reg);
+
+ 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;
+
+ max14577_reg->regulators[i] =
+ regulator_register(&supported_regulators[id], &config);
+ if (IS_ERR(max14577_reg->regulators[i])) {
+ ret = PTR_ERR(max14577_reg->regulators[i]);
+ dev_err(&pdev->dev,
+ "Regulator init failed for ID %d with error: %d\n",
+ id, ret);
+ max14577_reg->regulators[i] = NULL;
+ goto err;
+ }
+ }
+
+ return 0;
+ err:
+ while (--i >= 0)
+ regulator_unregister(max14577_reg->regulators[i]);
+
+ return ret;
+}
+
+static int max14577_regulator_remove(struct platform_device *pdev)
+{
+ struct max14577_regulator *max14577_reg = platform_get_drvdata(pdev);
+ struct regulator_dev **regulators = max14577_reg->regulators;
+ int i;
+
+ for (i = 0; i < max14577_reg->num_regulators; i++)
+ if (regulators[i])
+ regulator_unregister(regulators[i]);
+
+ return 0;
+}
+
+static struct platform_driver max14577_regulator_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "max14577-regulator",
+ },
+ .probe = max14577_regulator_probe,
+ .remove = max14577_regulator_remove,
+};
+
+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] 15+ messages in thread
* Re: [PATCH 1/4] mfd: max14577: Add max14577 MFD driver core
[not found] ` <1384328457-5147-2-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
@ 2013-11-13 11:50 ` Mark Rutland
[not found] ` <20131113115018.GE21713-NuALmloUBlrZROr8t4l/smS4ubULX0JqMm0uRHvK7Nw@public.gmane.org>
0 siblings, 1 reply; 15+ messages in thread
From: Mark Rutland @ 2013-11-13 11:50 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-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
rob.herring-bsGFqQB8/DxBDgjK7y7TUQ@public.gmane.org,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park
On Wed, Nov 13, 2013 at 07:40:54AM +0000, Krzysztof Kozlowski wrote:
> From: Chanwoo Choi <cw00.choi-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
>
> 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-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Krzysztof Kozlowski <k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> Signed-off-by: Kyungmin Park <kyungmin.park-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
> ---
> drivers/mfd/Kconfig | 13 ++
> drivers/mfd/Makefile | 1 +
> drivers/mfd/max14577-irq.c | 283 +++++++++++++++++++++++++++++++++
> drivers/mfd/max14577.c | 268 +++++++++++++++++++++++++++++++
> include/linux/mfd/max14577-private.h | 291 ++++++++++++++++++++++++++++++++++
> include/linux/mfd/max14577.h | 76 +++++++++
> 6 files changed, 932 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
I see some of_* calls in the code, but no documentation of the binding.
As this has more than just a compatible, interrupts, and reg, it needs
its own binding document.
[...]
> +static struct mfd_cell max14577_devs[] = {
> + { .name = "max14577-muic",
> + .of_compatible = "maxim,max14577-muic", },
The compatible string looks fine to me, but should be documented.
[...]
> +#ifdef CONFIG_OF
> +static struct max14577_platform_data *max14577_i2c_parse_dt(struct device *dev)
> +{
> + struct max14577_platform_data *pdata;
> + struct device_node *np = dev->of_node;
> +
> + pdata = devm_kzalloc(dev, sizeof(struct max14577_platform_data),
> + GFP_KERNEL);
> + if (!pdata)
> + return ERR_PTR(-ENOMEM);
> +
> + pdata->irq_gpio = of_get_named_gpio(np, "irq_gpio", 0);
In general, '-' is preferred to '_' in property names. This should be
irq-gpio.
What exactly is this used for? I know that others have strong opinions
about how gpio/irq interaction should be handled.
> + if (!gpio_is_valid(pdata->irq_gpio)) {
> + dev_err(dev, "Invalid irq_gpio in DT\n");
> + return ERR_PTR(-EINVAL);
> + }
> +
> + if (of_property_read_bool(np, "wakeup"))
> + pdata->wakeup = true;
What does this mean? It seems like a remarkably generic name for
something that I'd guess is _very_ specific to this particular binding.
It might be worth prefixing it, and is certainly worth having a more
precise name.
Thanks,
Mark.
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/4] mfd: max14577: Add max14577 MFD driver core
2013-11-13 7:40 ` [PATCH 1/4] mfd: max14577: Add max14577 MFD driver core Krzysztof Kozlowski
[not found] ` <1384328457-5147-2-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
@ 2013-11-13 13:13 ` Mark Brown
[not found] ` <20131113131328.GF878-GFdadSzt00ze9xe1eoZjHA@public.gmane.org>
1 sibling, 1 reply; 15+ messages in thread
From: Mark Brown @ 2013-11-13 13:13 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, Bartlomiej Zolnierkiewicz,
Marek Szyprowski, Kyungmin Park
[-- Attachment #1: Type: text/plain, Size: 2907 bytes --]
On Wed, Nov 13, 2013 at 08:40:54AM +0100, Krzysztof Kozlowski wrote:
> +/**
> + * 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
What is the cause here? This smells like an unreliable workaround for
some other behaviour. In general this all looks very like standard
regmap code.
> + for (i = 0; i < MAX14577_IRQ_REGS_NUM; i++) {
> + u8 mask_reg = max14577_mask_reg[i];
> +
> + if (mask_reg == MAX14577_REG_INVALID ||
> + IS_ERR_OR_NULL(max14577->regmap))
> + continue;
Why would this code even be running if you don't have a register map?
> + dev_info(max14577->dev, "Got interrupts [1:0x%02x, 2:0x%02x, 3:0x%02x]\n",
> + irq_reg[MAX14577_IRQ_INT1], irq_reg[MAX14577_IRQ_INT2],
> + irq_reg[MAX14577_IRQ_INT3]);
This is far too noisy, dev_dbg() at most.
> + gpio_val = gpio_get_value(pdata->irq_gpio);
> +
> + if (gpio_get_value(pdata->irq_gpio) == 0)
> + dev_warn(max14577->dev, "IRQ GPIO is not high, retry reading interrupt registers\n");
> + } while (gpio_val == 0 && --retry > 0);
This looks very strange...
> + max14577->irq = gpio_to_irq(pdata->irq_gpio);
> + ret = gpio_request(pdata->irq_gpio, "max14577_irq");
> + if (ret) {
> + dev_err(max14577->dev, "Failed requesting GPIO %d: %d\n",
> + pdata->irq_gpio, ret);
> + goto err;
> + }
> + gpio_direction_input(pdata->irq_gpio);
> + gpio_free(pdata->irq_gpio);
This means the GPIO handling code that was present in the handling is
broken, it's trying to use the GPIO after it was freed.
> + ret = request_threaded_irq(max14577->irq, NULL, max14577_irq_thread,
> + IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
> + "max14577-irq", max14577);
Are you *positive* this is a falling triggered IRQ? All the code to do
with spinning reading the GPIO state during handling makes it look like
this is in fact an active low interrupt and a lot of the code in here is
working around trying to handle that as the wrong kind of IRQ.
> +int max14577_bulk_write(struct regmap *map, u8 reg, u8 *buf, int count)
> +{
> + return regmap_bulk_write(map, reg, buf, count);
> +}
> +EXPORT_SYMBOL_GPL(max14577_bulk_write);
Given that these are basically all trivial wrappers around regmap they
probably ought to be static inlines in the header.
> +static struct max14577_platform_data *max14577_i2c_parse_dt(struct device *dev)
> +{
There's no DT binding document?
> +const struct dev_pm_ops max14577_pm = {
> + .suspend = max14577_suspend,
> + .resume = max14577_resume,
> +};
SET_SYSTEM_SLEEP_PM_OPS().
> +static int __init max14577_i2c_init(void)
> +{
> + return i2c_add_driver(&max14577_i2c_driver);
> +}
> +subsys_initcall(max14577_i2c_init);
Why not module_i2c_driver?
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/4] regulator: max14577: Add regulator driver for Maxim 14577
[not found] ` <1384328457-5147-5-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
@ 2013-11-13 13:23 ` Mark Brown
2013-11-14 9:17 ` Krzysztof Kozlowski
0 siblings, 1 reply; 15+ messages in thread
From: Mark Brown @ 2013-11-13 13:23 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, Bartlomiej Zolnierkiewicz,
Marek Szyprowski, Kyungmin Park
[-- Attachment #1: Type: text/plain, Size: 2739 bytes --]
On Wed, Nov 13, 2013 at 08:40:57AM +0100, Krzysztof Kozlowski wrote:
> +static inline int max14577_reg_set_safeout(struct regmap *rmap, int on)
> +{
> + u8 reg_data = (on ? 0x1 << CTRL2_SFOUTORD_SHIFT : 0x0);
The ternery operator is rarely helpful for legibility.
> + case MAX14577_SAFEOUT:
> + max14577_read_reg(rmap, MAX14577_REG_CONTROL2, ®_data);
> + return (reg_data & CTRL2_SFOUTORD_MASK) != 0;
regulator_is_enabled_regmap() and the other helpers should be used for
this regulator.
> +static int max14577_reg_disable(struct regulator_dev *rdev)
> +{
> + int rid = rdev_get_id(rdev);
> +
> + switch (rid) {
> + case MAX14577_CHARGER:
> + return max14577_reg_set_charging(rdev->regmap, 0);
> + case MAX14577_SAFEOUT:
> + return max14577_reg_set_safeout(rdev->regmap, 0);
> + default:
> + return -EINVAL;
> + }
> +}
This sort of indirection isn't adding anything, just make the ops
directly callable (and in the case of SAFEOUT use the helpers).
> +static int max14577_reg_get_current_limit(struct regulator_dev *rdev)
> +{
> + if (rdev_get_id(rdev) != MAX14577_CHARGER)
> + return -EINVAL;
> +
> + return max14577_reg_calc_charge_current(rdev->regmap);
Just inline the function, it's only called once.
> + if (rdev_get_id(rdev) != MAX14577_CHARGER)
> + return -EINVAL;
> +
> + dev_info(max14577->dev, "Requested current <%d, %d>\n", min_uA, max_uA);
Don't spam the logs like this, there's problems like this throughout the
rest of the code.
> +static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev,
> + struct max14577_platform_data *pdata)
No DT binding document.
> +static int max14577_regulator_probe(struct platform_device *pdev)
> +{
> + struct max14577_regulator *max14577_reg;
> + struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
> + struct max14577_platform_data *pdata = dev_get_platdata(max14577->dev);
> + int i, ret, size;
> + struct regulator_config config = {};
> +
> + if (!pdata) {
> + dev_err(&pdev->dev, "No platform data found.\n");
> + return -ENODEV;
> + }
> +
> + if (max14577->dev->of_node) {
> + ret = max14577_regulator_dt_parse_pdata(pdev, pdata);
> + if (ret)
> + return ret;
> + }
The driver insists on platform data but then requires DT...
> + size = sizeof(struct regulator_dev *) * pdata->num_regulators;
> + max14577_reg->regulators = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
You shouldn't be getting the number of regulators from platform data,
the number of regulators physically present in the silicon isn't going
to vary.
> + max14577_reg->regulators[i] =
> + regulator_register(&supported_regulators[id], &config);
devm_regulator_register().
> +subsys_initcall(max14577_regulator_init);
Why is this subsys_initcall()?
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/4] mfd: max14577: Add max14577 MFD driver core
[not found] ` <20131113131328.GF878-GFdadSzt00ze9xe1eoZjHA@public.gmane.org>
@ 2013-11-14 1:33 ` Kyungmin Park
[not found] ` <CAH9JG2U9VDnRW9JZ7j7OyRmVhSXWJ5Yp_gNqUPK=xCST7SY=xg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2013-11-14 8:15 ` Krzysztof Kozlowski
1 sibling, 1 reply; 15+ messages in thread
From: Kyungmin Park @ 2013-11-14 1:33 UTC (permalink / raw)
To: Mark Brown
Cc: Krzysztof Kozlowski, MyungJoo Ham, Chanwoo Choi, Samuel Ortiz,
Lee Jones, Anton Vorontsov, David Woodhouse, Liam Girdwood,
Grant Likely, Rob Herring, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA, Bartlomiej Zolnierkiewicz,
Marek Szyprowski
On Wed, Nov 13, 2013 at 10:13 PM, Mark Brown <broonie-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> wrote:
> On Wed, Nov 13, 2013 at 08:40:54AM +0100, Krzysztof Kozlowski wrote:
>
>> +/**
>> + * 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
>
> What is the cause here? This smells like an unreliable workaround for
> some other behaviour. In general this all looks very like standard
> regmap code.
>
>> + for (i = 0; i < MAX14577_IRQ_REGS_NUM; i++) {
>> + u8 mask_reg = max14577_mask_reg[i];
>> +
>> + if (mask_reg == MAX14577_REG_INVALID ||
>> + IS_ERR_OR_NULL(max14577->regmap))
>> + continue;
>
> Why would this code even be running if you don't have a register map?
>
>> + dev_info(max14577->dev, "Got interrupts [1:0x%02x, 2:0x%02x, 3:0x%02x]\n",
>> + irq_reg[MAX14577_IRQ_INT1], irq_reg[MAX14577_IRQ_INT2],
>> + irq_reg[MAX14577_IRQ_INT3]);
>
> This is far too noisy, dev_dbg() at most.
>
>> + gpio_val = gpio_get_value(pdata->irq_gpio);
>> +
>> + if (gpio_get_value(pdata->irq_gpio) == 0)
>> + dev_warn(max14577->dev, "IRQ GPIO is not high, retry reading interrupt registers\n");
>> + } while (gpio_val == 0 && --retry > 0);
>
> This looks very strange...
>
>> + max14577->irq = gpio_to_irq(pdata->irq_gpio);
>> + ret = gpio_request(pdata->irq_gpio, "max14577_irq");
>> + if (ret) {
>> + dev_err(max14577->dev, "Failed requesting GPIO %d: %d\n",
>> + pdata->irq_gpio, ret);
>> + goto err;
>> + }
>> + gpio_direction_input(pdata->irq_gpio);
>> + gpio_free(pdata->irq_gpio);
>
> This means the GPIO handling code that was present in the handling is
> broken, it's trying to use the GPIO after it was freed.
>
>> + ret = request_threaded_irq(max14577->irq, NULL, max14577_irq_thread,
>> + IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
>> + "max14577-irq", max14577);
>
> Are you *positive* this is a falling triggered IRQ? All the code to do
> with spinning reading the GPIO state during handling makes it look like
> this is in fact an active low interrupt and a lot of the code in here is
> working around trying to handle that as the wrong kind of IRQ.
It's not work with level triggering. as wm8994, it requires edge
triggering. previous time I send RFC patch to handle edge triggering
at regmap.
>
>> +int max14577_bulk_write(struct regmap *map, u8 reg, u8 *buf, int count)
>> +{
>> + return regmap_bulk_write(map, reg, buf, count);
>> +}
>> +EXPORT_SYMBOL_GPL(max14577_bulk_write);
>
> Given that these are basically all trivial wrappers around regmap they
> probably ought to be static inlines in the header.
>
>> +static struct max14577_platform_data *max14577_i2c_parse_dt(struct device *dev)
>> +{
>
> There's no DT binding document?
>
>> +const struct dev_pm_ops max14577_pm = {
>> + .suspend = max14577_suspend,
>> + .resume = max14577_resume,
>> +};
>
> SET_SYSTEM_SLEEP_PM_OPS().
>
>> +static int __init max14577_i2c_init(void)
>> +{
>> + return i2c_add_driver(&max14577_i2c_driver);
>> +}
>> +subsys_initcall(max14577_i2c_init);
>
> Why not module_i2c_driver?
there's ordering issue, it should provide regulator which is used
others before USB probe. if not, it failed to use USB.
Other PMICs use also subsys_initcall for this reason.
Thank you,
Kyungmin Park
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/4] mfd: max14577: Add max14577 MFD driver core
[not found] ` <20131113131328.GF878-GFdadSzt00ze9xe1eoZjHA@public.gmane.org>
2013-11-14 1:33 ` Kyungmin Park
@ 2013-11-14 8:15 ` Krzysztof Kozlowski
1 sibling, 0 replies; 15+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-14 8:15 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-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA, Bartlomiej Zolnierkiewicz,
Marek Szyprowski, Kyungmin Park
Hi,
On Wed, 2013-11-13 at 13:13 +0000, Mark Brown wrote:
> On Wed, Nov 13, 2013 at 08:40:54AM +0100, Krzysztof Kozlowski wrote:
>
> > +/**
> > + * 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
>
> What is the cause here? This smells like an unreliable workaround for
> some other behaviour. In general this all looks very like standard
> regmap code.
>
> > + for (i = 0; i < MAX14577_IRQ_REGS_NUM; i++) {
> > + u8 mask_reg = max14577_mask_reg[i];
> > +
> > + if (mask_reg == MAX14577_REG_INVALID ||
> > + IS_ERR_OR_NULL(max14577->regmap))
> > + continue;
>
> Why would this code even be running if you don't have a register map?
>
> > + dev_info(max14577->dev, "Got interrupts [1:0x%02x, 2:0x%02x, 3:0x%02x]\n",
> > + irq_reg[MAX14577_IRQ_INT1], irq_reg[MAX14577_IRQ_INT2],
> > + irq_reg[MAX14577_IRQ_INT3]);
>
> This is far too noisy, dev_dbg() at most.
>
> > + gpio_val = gpio_get_value(pdata->irq_gpio);
> > +
> > + if (gpio_get_value(pdata->irq_gpio) == 0)
> > + dev_warn(max14577->dev, "IRQ GPIO is not high, retry reading interrupt registers\n");
> > + } while (gpio_val == 0 && --retry > 0);
>
> This looks very strange...
>
> > + max14577->irq = gpio_to_irq(pdata->irq_gpio);
> > + ret = gpio_request(pdata->irq_gpio, "max14577_irq");
> > + if (ret) {
> > + dev_err(max14577->dev, "Failed requesting GPIO %d: %d\n",
> > + pdata->irq_gpio, ret);
> > + goto err;
> > + }
> > + gpio_direction_input(pdata->irq_gpio);
> > + gpio_free(pdata->irq_gpio);
>
> This means the GPIO handling code that was present in the handling is
> broken, it's trying to use the GPIO after it was freed.
>
> > + ret = request_threaded_irq(max14577->irq, NULL, max14577_irq_thread,
> > + IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
> > + "max14577-irq", max14577);
>
> Are you *positive* this is a falling triggered IRQ? All the code to do
> with spinning reading the GPIO state during handling makes it look like
> this is in fact an active low interrupt and a lot of the code in here is
> working around trying to handle that as the wrong kind of IRQ.
>
> > +int max14577_bulk_write(struct regmap *map, u8 reg, u8 *buf, int count)
> > +{
> > + return regmap_bulk_write(map, reg, buf, count);
> > +}
> > +EXPORT_SYMBOL_GPL(max14577_bulk_write);
>
> Given that these are basically all trivial wrappers around regmap they
> probably ought to be static inlines in the header.
>
> > +static struct max14577_platform_data *max14577_i2c_parse_dt(struct device *dev)
> > +{
>
> There's no DT binding document?
>
> > +const struct dev_pm_ops max14577_pm = {
> > + .suspend = max14577_suspend,
> > + .resume = max14577_resume,
> > +};
>
> SET_SYSTEM_SLEEP_PM_OPS().
>
> > +static int __init max14577_i2c_init(void)
> > +{
> > + return i2c_add_driver(&max14577_i2c_driver);
> > +}
> > +subsys_initcall(max14577_i2c_init);
>
> Why not module_i2c_driver?
Thanks for review. I'll the fix issues and send later new version of
patch.
Best regards,
Krzysztof
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/4] mfd: max14577: Add max14577 MFD driver core
[not found] ` <20131113115018.GE21713-NuALmloUBlrZROr8t4l/smS4ubULX0JqMm0uRHvK7Nw@public.gmane.org>
@ 2013-11-14 8:32 ` Krzysztof Kozlowski
0 siblings, 0 replies; 15+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-14 8:32 UTC (permalink / raw)
To: Mark Rutland
Cc: MyungJoo Ham, Chanwoo Choi, Samuel Ortiz, Lee Jones,
Anton Vorontsov, David Woodhouse, Liam Girdwood, Mark Brown,
grant.likely-QSEj5FYQhm4dnm+yROfE0A@public.gmane.org,
rob.herring-bsGFqQB8/DxBDgjK7y7TUQ@public.gmane.org,
linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
devicetree-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
Bartlomiej Zolnierkiewicz, Marek Szyprowski, Kyungmin Park
Hi,
On Wed, 2013-11-13 at 11:50 +0000, Mark Rutland wrote:
> I see some of_* calls in the code, but no documentation of the binding.
> As this has more than just a compatible, interrupts, and reg, it needs
> its own binding document.
>
> [...]
>
> > +static struct mfd_cell max14577_devs[] = {
> > + { .name = "max14577-muic",
> > + .of_compatible = "maxim,max14577-muic", },
>
> The compatible string looks fine to me, but should be documented.
Sure, I will add documentation.
> [...]
>
> > +#ifdef CONFIG_OF
> > +static struct max14577_platform_data *max14577_i2c_parse_dt(struct device *dev)
> > +{
> > + struct max14577_platform_data *pdata;
> > + struct device_node *np = dev->of_node;
> > +
> > + pdata = devm_kzalloc(dev, sizeof(struct max14577_platform_data),
> > + GFP_KERNEL);
> > + if (!pdata)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + pdata->irq_gpio = of_get_named_gpio(np, "irq_gpio", 0);
>
> In general, '-' is preferred to '_' in property names. This should be
> irq-gpio.
OK.
> What exactly is this used for? I know that others have strong opinions
> about how gpio/irq interaction should be handled.
Later also Mark Brown raised this. I'll leave this to discussion with
Kyungmin Park as I'm not entirely the author of the code.
>
> > + if (!gpio_is_valid(pdata->irq_gpio)) {
> > + dev_err(dev, "Invalid irq_gpio in DT\n");
> > + return ERR_PTR(-EINVAL);
> > + }
> > +
> > + if (of_property_read_bool(np, "wakeup"))
> > + pdata->wakeup = true;
>
> What does this mean? It seems like a remarkably generic name for
> something that I'd guess is _very_ specific to this particular binding.
>
> It might be worth prefixing it, and is certainly worth having a more
> precise name.
It is used for marking device as wake up source. This is same code as in
other max drivers used on Exynos boards (max77693 and max77686).
Thanks for review,
Krzysztof
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 4/4] regulator: max14577: Add regulator driver for Maxim 14577
2013-11-13 13:23 ` Mark Brown
@ 2013-11-14 9:17 ` Krzysztof Kozlowski
0 siblings, 0 replies; 15+ messages in thread
From: Krzysztof Kozlowski @ 2013-11-14 9:17 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, Bartlomiej Zolnierkiewicz,
Marek Szyprowski, Kyungmin Park
On Wed, 2013-11-13 at 13:23 +0000, Mark Brown wrote:
> On Wed, Nov 13, 2013 at 08:40:57AM +0100, Krzysztof Kozlowski wrote:
>
> > +static inline int max14577_reg_set_safeout(struct regmap *rmap, int on)
> > +{
> > + u8 reg_data = (on ? 0x1 << CTRL2_SFOUTORD_SHIFT : 0x0);
>
> The ternery operator is rarely helpful for legibility.
>
> > + case MAX14577_SAFEOUT:
> > + max14577_read_reg(rmap, MAX14577_REG_CONTROL2, ®_data);
> > + return (reg_data & CTRL2_SFOUTORD_MASK) != 0;
>
> regulator_is_enabled_regmap() and the other helpers should be used for
> this regulator.
>
> > +static int max14577_reg_disable(struct regulator_dev *rdev)
> > +{
> > + int rid = rdev_get_id(rdev);
> > +
> > + switch (rid) {
> > + case MAX14577_CHARGER:
> > + return max14577_reg_set_charging(rdev->regmap, 0);
> > + case MAX14577_SAFEOUT:
> > + return max14577_reg_set_safeout(rdev->regmap, 0);
> > + default:
> > + return -EINVAL;
> > + }
> > +}
>
> This sort of indirection isn't adding anything, just make the ops
> directly callable (and in the case of SAFEOUT use the helpers).
>
> > +static int max14577_reg_get_current_limit(struct regulator_dev *rdev)
> > +{
> > + if (rdev_get_id(rdev) != MAX14577_CHARGER)
> > + return -EINVAL;
> > +
> > + return max14577_reg_calc_charge_current(rdev->regmap);
>
> Just inline the function, it's only called once.
>
> > + if (rdev_get_id(rdev) != MAX14577_CHARGER)
> > + return -EINVAL;
> > +
> > + dev_info(max14577->dev, "Requested current <%d, %d>\n", min_uA, max_uA);
>
> Don't spam the logs like this, there's problems like this throughout the
> rest of the code.
>
> > +static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev,
> > + struct max14577_platform_data *pdata)
>
> No DT binding document.
>
> > +static int max14577_regulator_probe(struct platform_device *pdev)
> > +{
> > + struct max14577_regulator *max14577_reg;
> > + struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
> > + struct max14577_platform_data *pdata = dev_get_platdata(max14577->dev);
> > + int i, ret, size;
> > + struct regulator_config config = {};
> > +
> > + if (!pdata) {
> > + dev_err(&pdev->dev, "No platform data found.\n");
> > + return -ENODEV;
> > + }
> > +
> > + if (max14577->dev->of_node) {
> > + ret = max14577_regulator_dt_parse_pdata(pdev, pdata);
> > + if (ret)
> > + return ret;
> > + }
>
> The driver insists on platform data but then requires DT...
>
> > + size = sizeof(struct regulator_dev *) * pdata->num_regulators;
> > + max14577_reg->regulators = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
>
> You shouldn't be getting the number of regulators from platform data,
> the number of regulators physically present in the silicon isn't going
> to vary.
>
> > + max14577_reg->regulators[i] =
> > + regulator_register(&supported_regulators[id], &config);
>
> devm_regulator_register().
Thanks for review. I'll start working on issues.
> > +subsys_initcall(max14577_regulator_init);
>
> Why is this subsys_initcall()?
Just like other regulators it should start before any other devices, for
example USB drivers.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/4] mfd: max14577: Add max14577 MFD driver core
[not found] ` <CAH9JG2U9VDnRW9JZ7j7OyRmVhSXWJ5Yp_gNqUPK=xCST7SY=xg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
@ 2013-11-14 10:24 ` Mark Brown
[not found] ` <20131114102419.GE26614-GFdadSzt00ze9xe1eoZjHA@public.gmane.org>
0 siblings, 1 reply; 15+ messages in thread
From: Mark Brown @ 2013-11-14 10:24 UTC (permalink / raw)
To: Kyungmin Park
Cc: Krzysztof Kozlowski, MyungJoo Ham, Chanwoo Choi, Samuel Ortiz,
Lee Jones, Anton Vorontsov, David Woodhouse, Liam Girdwood,
Grant Likely, Rob Herring, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA, Bartlomiej Zolnierkiewicz,
Marek Szyprowski
[-- Attachment #1: Type: text/plain, Size: 1467 bytes --]
On Thu, Nov 14, 2013 at 10:33:22AM +0900, Kyungmin Park wrote:
> On Wed, Nov 13, 2013 at 10:13 PM, Mark Brown <broonie-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> wrote:
> > Are you *positive* this is a falling triggered IRQ? All the code to do
> > with spinning reading the GPIO state during handling makes it look like
> > this is in fact an active low interrupt and a lot of the code in here is
> > working around trying to handle that as the wrong kind of IRQ.
> It's not work with level triggering. as wm8994, it requires edge
> triggering. previous time I send RFC patch to handle edge triggering
> at regmap.
No, wm8994 is level triggered only - the edge triggering stuff there is
to work around some SoCs that could only support edge triggering and not
level triggering. Is this a similar issue or is there something else
going on, what's the differeence with a level triggered scheme?
> >> +static int __init max14577_i2c_init(void)
> >> +{
> >> + return i2c_add_driver(&max14577_i2c_driver);
> >> +}
> >> +subsys_initcall(max14577_i2c_init);
> > Why not module_i2c_driver?
> there's ordering issue, it should provide regulator which is used
> others before USB probe. if not, it failed to use USB.
> Other PMICs use also subsys_initcall for this reason.
Modern systems should be able to use deferred probing to resolve these
dependencies, subsys_initcall() is mostly there for legacy reasons and
new systems ought to be able to move away from it.
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/4] mfd: max14577: Add max14577 MFD driver core
[not found] ` <20131114102419.GE26614-GFdadSzt00ze9xe1eoZjHA@public.gmane.org>
@ 2013-11-14 10:53 ` Marek Szyprowski
2013-11-14 11:22 ` Mark Brown
0 siblings, 1 reply; 15+ messages in thread
From: Marek Szyprowski @ 2013-11-14 10:53 UTC (permalink / raw)
To: Mark Brown, Kyungmin Park
Cc: Krzysztof Kozlowski, MyungJoo Ham, Chanwoo Choi, Samuel Ortiz,
Lee Jones, Anton Vorontsov, David Woodhouse, Liam Girdwood,
Grant Likely, Rob Herring, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
devicetree-u79uwXL29TY76Z2rM5mHXA, Bartlomiej Zolnierkiewicz
Hello,
On 2013-11-14 11:24, Mark Brown wrote:
> On Thu, Nov 14, 2013 at 10:33:22AM +0900, Kyungmin Park wrote:
> > On Wed, Nov 13, 2013 at 10:13 PM, Mark Brown <broonie-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org> wrote:
>
> > > Are you *positive* this is a falling triggered IRQ? All the code to do
> > > with spinning reading the GPIO state during handling makes it look like
> > > this is in fact an active low interrupt and a lot of the code in here is
> > > working around trying to handle that as the wrong kind of IRQ.
>
> > It's not work with level triggering. as wm8994, it requires edge
> > triggering. previous time I send RFC patch to handle edge triggering
> > at regmap.
>
> No, wm8994 is level triggered only - the edge triggering stuff there is
> to work around some SoCs that could only support edge triggering and not
> level triggering. Is this a similar issue or is there something else
> going on, what's the differeence with a level triggered scheme?
>
> > >> +static int __init max14577_i2c_init(void)
> > >> +{
> > >> + return i2c_add_driver(&max14577_i2c_driver);
> > >> +}
> > >> +subsys_initcall(max14577_i2c_init);
>
> > > Why not module_i2c_driver?
>
> > there's ordering issue, it should provide regulator which is used
> > others before USB probe. if not, it failed to use USB.
> > Other PMICs use also subsys_initcall for this reason.
>
> Modern systems should be able to use deferred probing to resolve these
> dependencies, subsys_initcall() is mostly there for legacy reasons and
> new systems ought to be able to move away from it.
The main problem here is usb gadget subsystem, which simply doesn't support
deferred probe yet. UDC driver (which uses those regulators) need to be
initialized before usb gadget driver (i.e. mass storage or cdc ethernet)
has been registered.
Best regards
--
Marek Szyprowski
Samsung R&D Institute Poland
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/4] mfd: max14577: Add max14577 MFD driver core
2013-11-14 10:53 ` Marek Szyprowski
@ 2013-11-14 11:22 ` Mark Brown
0 siblings, 0 replies; 15+ messages in thread
From: Mark Brown @ 2013-11-14 11:22 UTC (permalink / raw)
To: Marek Szyprowski
Cc: Kyungmin Park, Krzysztof Kozlowski, MyungJoo Ham, Chanwoo Choi,
Samuel Ortiz, Lee Jones, Anton Vorontsov, David Woodhouse,
Liam Girdwood, Grant Likely, Rob Herring, linux-kernel,
devicetree, Bartlomiej Zolnierkiewicz
[-- Attachment #1: Type: text/plain, Size: 608 bytes --]
On Thu, Nov 14, 2013 at 11:53:51AM +0100, Marek Szyprowski wrote:
> On 2013-11-14 11:24, Mark Brown wrote:
> >Modern systems should be able to use deferred probing to resolve these
> >dependencies, subsys_initcall() is mostly there for legacy reasons and
> >new systems ought to be able to move away from it.
> The main problem here is usb gadget subsystem, which simply doesn't support
> deferred probe yet. UDC driver (which uses those regulators) need to be
> initialized before usb gadget driver (i.e. mass storage or cdc ethernet)
> has been registered.
OK, that makes sense - OK for the time being.
[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 836 bytes --]
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2013-11-14 11:22 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-11-13 7:40 [PATCH 0/4] mfd: max14577: Add max14577 MFD drivers Krzysztof Kozlowski
2013-11-13 7:40 ` [PATCH 1/4] mfd: max14577: Add max14577 MFD driver core Krzysztof Kozlowski
[not found] ` <1384328457-5147-2-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
2013-11-13 11:50 ` Mark Rutland
[not found] ` <20131113115018.GE21713-NuALmloUBlrZROr8t4l/smS4ubULX0JqMm0uRHvK7Nw@public.gmane.org>
2013-11-14 8:32 ` Krzysztof Kozlowski
2013-11-13 13:13 ` Mark Brown
[not found] ` <20131113131328.GF878-GFdadSzt00ze9xe1eoZjHA@public.gmane.org>
2013-11-14 1:33 ` Kyungmin Park
[not found] ` <CAH9JG2U9VDnRW9JZ7j7OyRmVhSXWJ5Yp_gNqUPK=xCST7SY=xg-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>
2013-11-14 10:24 ` Mark Brown
[not found] ` <20131114102419.GE26614-GFdadSzt00ze9xe1eoZjHA@public.gmane.org>
2013-11-14 10:53 ` Marek Szyprowski
2013-11-14 11:22 ` Mark Brown
2013-11-14 8:15 ` Krzysztof Kozlowski
[not found] ` <1384328457-5147-1-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
2013-11-13 7:40 ` [PATCH 2/4] extcon: max77693: Add extcon-max14577 driver to support MUIC device Krzysztof Kozlowski
2013-11-13 7:40 ` [PATCH 3/4] charger: max14577: Add charger support for Maxim 14577 Krzysztof Kozlowski
2013-11-13 7:40 ` [PATCH 4/4] regulator: max14577: Add regulator driver " Krzysztof Kozlowski
[not found] ` <1384328457-5147-5-git-send-email-k.kozlowski-Sze3O3UU22JBDgjK7y7TUQ@public.gmane.org>
2013-11-13 13:23 ` Mark Brown
2013-11-14 9:17 ` Krzysztof Kozlowski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).