diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/mfd | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2 |
samsung update 1
Diffstat (limited to 'drivers/mfd')
-rw-r--r-- | drivers/mfd/Kconfig | 42 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 8 | ||||
-rw-r--r-- | drivers/mfd/max77686-irq.c | 344 | ||||
-rw-r--r-- | drivers/mfd/max77686.c | 407 | ||||
-rw-r--r-- | drivers/mfd/max77693-irq.c | 349 | ||||
-rw-r--r-- | drivers/mfd/max77693.c | 390 | ||||
-rw-r--r-- | drivers/mfd/max8698.c | 161 | ||||
-rw-r--r-- | drivers/mfd/max8997-irq.c | 558 | ||||
-rw-r--r-- | drivers/mfd/max8997.c | 298 | ||||
-rw-r--r-- | drivers/mfd/s5m-core.c | 249 | ||||
-rw-r--r-- | drivers/mfd/s5m-irq.c | 487 | ||||
-rw-r--r-- | drivers/mfd/wm8994-core.c | 242 | ||||
-rw-r--r-- | drivers/mfd/wm8994-irq.c | 12 |
13 files changed, 3047 insertions, 500 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 6ca938a..862aa03 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -349,6 +349,48 @@ config MFD_MAX8998 additional drivers must be enabled in order to use the functionality of the device. +config MFD_MAX8698 + bool "Maxim Semiconductor MAX8698 PMIC Support" + depends on I2C=y + select MFD_CORE + help + Say yes here to support for Maxim Semiconductor MAX8698. This is + a Power Management IC. This driver provies common support for + accessing the device, additional drivers must be enabled in order + to use the functionality of the device. + +config MFD_MAX77686 + bool "Maxim Semiconductor MAX77686 PMIC Support" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + help + Say yes here to support for Maxim Semiconductor MAX77686. + This is a Power Management IC with RTC 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_MAX77693 + bool "Maxim Semiconductor MAX77693 PMIC Support" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + help + Say yes here to support for Maxim Semiconductor MAX77963. + This is a Power Management IC with RTC, Flash, Fuel Gauge, Haptic, + MUIC controls on chip. + This driver provides common support for accessing the device; + additional drivers + +config MFD_S5M_CORE + bool "SAMSUNG S5M Series Support" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + help + Support for the Samsung Electronics S5M MFD series. + This driver provies common support for accessing the device, + additional drivers must be enabled in order to use the functionality + of the device + config MFD_WM8400 tristate "Support Wolfson Microelectronics WM8400" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index d7d47d2..16ffd4c 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -64,6 +64,9 @@ max8925-objs := max8925-core.o max8925-i2c.o obj-$(CONFIG_MFD_MAX8925) += max8925.o obj-$(CONFIG_MFD_MAX8997) += max8997.o max8997-irq.o obj-$(CONFIG_MFD_MAX8998) += max8998.o max8998-irq.o +obj-$(CONFIG_MFD_MAX8698) += max8698.o +obj-$(CONFIG_MFD_MAX77686) += max77686.o max77686-irq.o +obj-$(CONFIG_MFD_MAX77693) += max77693.o max77693-irq.o pcf50633-objs := pcf50633-core.o pcf50633-irq.o obj-$(CONFIG_MFD_PCF50633) += pcf50633.o @@ -91,7 +94,8 @@ obj-$(CONFIG_MFD_VX855) += vx855.o obj-$(CONFIG_MFD_WL1273_CORE) += wl1273-core.o obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o -obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o -obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o +obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o +obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o obj-$(CONFIG_MFD_TPS65910) += tps65910.o tps65910-irq.o obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o +obj-$(CONFIG_MFD_S5M_CORE) += s5m-core.o s5m-irq.o diff --git a/drivers/mfd/max77686-irq.c b/drivers/mfd/max77686-irq.c new file mode 100644 index 0000000..152a488 --- /dev/null +++ b/drivers/mfd/max77686-irq.c @@ -0,0 +1,344 @@ +/* + * max77686-irq.c - Interrupt controller support for MAX77686 + * + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Chiwoong Byun <woong.byun@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 + * + * This driver is based on max8997-irq.c + */ + +#include <linux/err.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/mfd/max77686.h> +#include <linux/mfd/max77686-private.h> + +#undef MAX77686_IRQ_TEST + +enum { + MAX77686_DEBUG_IRQ_INFO = 1 << 0, + MAX77686_DEBUG_IRQ_MASK = 1 << 1, + MAX77686_DEBUG_IRQ_INT = 1 << 2, +}; + +static int debug_mask = MAX77686_DEBUG_IRQ_INFO | MAX77686_DEBUG_IRQ_MASK | + MAX77686_DEBUG_IRQ_INT; + +static const u8 max77686_mask_reg[] = { + [PMIC_INT1] = MAX77686_REG_INT1MSK, + [PMIC_INT2] = MAX77686_REG_INT2MSK, + [RTC_INT] = MAX77686_RTC_INTM, +}; + +static struct i2c_client *max77686_get_i2c(struct max77686_dev *max77686, + enum max77686_irq_source src) +{ + switch (src) { + case PMIC_INT1 ... PMIC_INT2: + return max77686->i2c; + case RTC_INT: + return max77686->rtc; + default: + return ERR_PTR(-EINVAL); + } +} + +struct max77686_irq_data { + int mask; + enum max77686_irq_source group; +}; + +#define DECLARE_IRQ(idx, _group, _mask) \ + [(idx)] = { .group = (_group), .mask = (_mask) } +static const struct max77686_irq_data max77686_irqs[] = { + DECLARE_IRQ(MAX77686_PMICIRQ_PWRONF, PMIC_INT1, 1 << 0), + DECLARE_IRQ(MAX77686_PMICIRQ_PWRONR, PMIC_INT1, 1 << 1), + DECLARE_IRQ(MAX77686_PMICIRQ_JIGONBF, PMIC_INT1, 1 << 2), + DECLARE_IRQ(MAX77686_PMICIRQ_JIGONBR, PMIC_INT1, 1 << 3), + DECLARE_IRQ(MAX77686_PMICIRQ_ACOKBF, PMIC_INT1, 1 << 4), + DECLARE_IRQ(MAX77686_PMICIRQ_ACOKBR, PMIC_INT1, 1 << 5), + DECLARE_IRQ(MAX77686_PMICIRQ_ONKEY1S, PMIC_INT1, 1 << 6), + DECLARE_IRQ(MAX77686_PMICIRQ_MRSTB, PMIC_INT1, 1 << 7), + DECLARE_IRQ(MAX77686_PMICIRQ_140C, PMIC_INT2, 1 << 0), + DECLARE_IRQ(MAX77686_PMICIRQ_120C, PMIC_INT2, 1 << 1), + DECLARE_IRQ(MAX77686_RTCIRQ_RTC60S, RTC_INT, 1 << 0), + DECLARE_IRQ(MAX77686_RTCIRQ_RTCA1, RTC_INT, 1 << 1), + DECLARE_IRQ(MAX77686_RTCIRQ_RTCA2, RTC_INT, 1 << 2), + DECLARE_IRQ(MAX77686_RTCIRQ_SMPL, RTC_INT, 1 << 3), + DECLARE_IRQ(MAX77686_RTCIRQ_RTC1S, RTC_INT, 1 << 4), + DECLARE_IRQ(MAX77686_RTCIRQ_WTSR, RTC_INT, 1 << 5), +}; + +static void max77686_irq_lock(struct irq_data *data) +{ + struct max77686_dev *max77686 = irq_get_chip_data(data->irq); + + if (debug_mask & MAX77686_DEBUG_IRQ_MASK) + pr_debug("%s\n", __func__); + + mutex_lock(&max77686->irqlock); +} + +static void max77686_irq_sync_unlock(struct irq_data *data) +{ + struct max77686_dev *max77686 = irq_get_chip_data(data->irq); + int i; + + for (i = 0; i < MAX77686_IRQ_GROUP_NR; i++) { + u8 mask_reg = max77686_mask_reg[i]; + struct i2c_client *i2c = max77686_get_i2c(max77686, i); + + if (debug_mask & MAX77686_DEBUG_IRQ_MASK) + pr_debug("%s: mask_reg[%d]=0x%x, cur=0x%x\n", + __func__, i, mask_reg, max77686->irq_masks_cur[i]); + + if (mask_reg == MAX77686_REG_INVALID || + IS_ERR_OR_NULL(i2c)) + continue; + + max77686->irq_masks_cache[i] = max77686->irq_masks_cur[i]; + + max77686_write_reg(i2c, max77686_mask_reg[i], + max77686->irq_masks_cur[i]); + } + + mutex_unlock(&max77686->irqlock); +} + +static const inline struct max77686_irq_data * +irq_to_max77686_irq(struct max77686_dev *max77686, int irq) +{ + return &max77686_irqs[irq - max77686->irq_base]; +} + +static void max77686_irq_mask(struct irq_data *data) +{ + struct max77686_dev *max77686 = irq_get_chip_data(data->irq); + const struct max77686_irq_data *irq_data = irq_to_max77686_irq(max77686, + data->irq); + + max77686->irq_masks_cur[irq_data->group] |= irq_data->mask; + + if (debug_mask & MAX77686_DEBUG_IRQ_MASK) + pr_info("%s: group=%d, cur=0x%x\n", + __func__, irq_data->group, + max77686->irq_masks_cur[irq_data->group]); +} + +static void max77686_irq_unmask(struct irq_data *data) +{ + struct max77686_dev *max77686 = irq_get_chip_data(data->irq); + const struct max77686_irq_data *irq_data = irq_to_max77686_irq(max77686, + data->irq); + + max77686->irq_masks_cur[irq_data->group] &= ~irq_data->mask; + + if (debug_mask & MAX77686_DEBUG_IRQ_MASK) + pr_info("%s: group=%d, cur=0x%x\n", + __func__, irq_data->group, + max77686->irq_masks_cur[irq_data->group]); +} + +static struct irq_chip max77686_irq_chip = { + .name = "max77686", + .irq_bus_lock = max77686_irq_lock, + .irq_bus_sync_unlock = max77686_irq_sync_unlock, + .irq_mask = max77686_irq_mask, + .irq_unmask = max77686_irq_unmask, +}; + +static irqreturn_t max77686_irq_thread(int irq, void *data) +{ + struct max77686_dev *max77686 = data; + u8 irq_reg[MAX77686_IRQ_GROUP_NR] = {}; + u8 irq_src; + int ret; + int i; + + ret = max77686_read_reg(max77686->i2c, MAX77686_REG_INTSRC, &irq_src); + if (ret < 0) { + dev_err(max77686->dev, "Failed to read interrupt source: %d\n", + ret); + return IRQ_NONE; + } + + if (debug_mask & MAX77686_DEBUG_IRQ_INT) + pr_info("%s: irq_src=0x%x\n", __func__, irq_src); + + /* MAX77686_IRQSRC_RTC may be set even if there are pending at INT1/2 */ + ret = max77686_read_reg(max77686->i2c, MAX77686_REG_INT1, &irq_reg[0]); + ret = max77686_read_reg(max77686->i2c, MAX77686_REG_INT2, &irq_reg[1]); + if (ret < 0) { + dev_err(max77686->dev, "Failed to read pmic interrupt: %d\n", + ret); + return IRQ_NONE; + } + + if (debug_mask & MAX77686_DEBUG_IRQ_INT) + pr_info("%s: int1=0x%x, int2=0x%x\n", + __func__, irq_reg[PMIC_INT1], irq_reg[PMIC_INT2]); + + if (irq_src & MAX77686_IRQSRC_RTC) { +#ifdef CONFIG_RTC_DRV_MAX77686 + ret = max77686_read_reg(max77686->rtc, MAX77686_RTC_INT, &irq_reg[RTC_INT]); +#else + ret = -ENODEV; +#endif + if (ret < 0) { + dev_err(max77686->dev, "Failed to read rtc interrupt: %d\n", + ret); + return IRQ_NONE; + } + + if (debug_mask & MAX77686_DEBUG_IRQ_INT) + pr_info("%s: rtc int=0x%x\n", __func__, irq_reg[RTC_INT]); + + } + + for (i = 0; i < MAX77686_IRQ_NR; i++) { + if (irq_reg[max77686_irqs[i].group] & max77686_irqs[i].mask) + handle_nested_irq(max77686->irq_base + i); + } + + return IRQ_HANDLED; +} + +int max77686_irq_resume(struct max77686_dev *max77686) +{ + if (max77686->irq && max77686->irq_base) + max77686_irq_thread(max77686->irq_base, max77686); + return 0; +} + +int max77686_irq_init(struct max77686_dev *max77686) +{ + int i; + int cur_irq; + int ret; + int val; +#ifdef MAX77686_IRQ_TEST + u8 irq_reg[6] = { }; + u8 irq_src; +#endif + + if (debug_mask & MAX77686_DEBUG_IRQ_INFO) + pr_info("%s+\n", __func__); + + if (!max77686->irq_gpio) { + dev_warn(max77686->dev, "No interrupt gpio specified.\n"); + max77686->irq_base = 0; + return 0; + } + + if (!max77686->irq_base) { + dev_err(max77686->dev, "No interrupt base specified.\n"); + return 0; + } + + mutex_init(&max77686->irqlock); + + max77686->irq = gpio_to_irq(max77686->irq_gpio); + ret = gpio_request(max77686->irq_gpio, "pmic_irq"); + if (ret < 0 && ret != -EBUSY) { + dev_err(max77686->dev, + "Failed to request gpio %d with ret: %d\n", + max77686->irq_gpio, ret); + return IRQ_NONE; + } + if (ret == -EBUSY) + dev_warn(max77686->dev, "gpio pmic_irq is already requested\n"); + + gpio_direction_input(max77686->irq_gpio); + val = gpio_get_value(max77686->irq_gpio); + gpio_free(max77686->irq_gpio); + + if (debug_mask & MAX77686_DEBUG_IRQ_INT) + pr_info("%s: gpio_irq=%x\n", __func__, val); + +#ifdef MAX77686_IRQ_TEST + ret = max77686_read_reg(max77686->i2c, MAX77686_REG_INTSRC, &irq_src); + if (ret < 0) { + dev_err(max77686->dev, "Failed to read interrupt source: %d\n", + ret); + return IRQ_NONE; + } + + pr_info("%s: irq_src=0x%x\n", __func__, irq_src); + + ret = max77686_bulk_read(max77686->i2c, MAX77686_REG_INT1, 6, irq_reg); + if (ret < 0) { + dev_err(max77686->dev, "Failed to read interrupt source: %d\n", + ret); + return IRQ_NONE; + } + + for (i = 0; i < 6; i++) + pr_info("%s: i[%d]=0x%x\n", __func__, i, irq_reg[i]); +#endif /* MAX77686_IRQ_TEST */ + + /* Mask individual interrupt sources */ + for (i = 0; i < MAX77686_IRQ_GROUP_NR; i++) { + struct i2c_client *i2c; + + max77686->irq_masks_cur[i] = 0xff; + max77686->irq_masks_cache[i] = 0xff; + i2c = max77686_get_i2c(max77686, i); + + if (IS_ERR_OR_NULL(i2c)) + continue; + if (max77686_mask_reg[i] == MAX77686_REG_INVALID) + continue; + + max77686_write_reg(i2c, max77686_mask_reg[i], 0xff); + } + + /* Register with genirq */ + for (i = 0; i < MAX77686_IRQ_NR; i++) { + cur_irq = i + max77686->irq_base; + irq_set_chip_data(cur_irq, max77686); + irq_set_chip_and_handler(cur_irq, &max77686_irq_chip, + handle_edge_irq); + irq_set_nested_thread(cur_irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + irq_set_noprobe(cur_irq); +#endif + } + + ret = request_threaded_irq(max77686->irq, NULL, max77686_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "max77686-irq", max77686); + + if (ret) { + dev_err(max77686->dev, "Failed to request IRQ %d: %d\n", + max77686->irq, ret); + return ret; + } + + if (debug_mask & MAX77686_DEBUG_IRQ_INFO) + pr_info("%s-\n", __func__); + + return 0; +} + +void max77686_irq_exit(struct max77686_dev *max77686) +{ + if (max77686->irq) + free_irq(max77686->irq, max77686); +} diff --git a/drivers/mfd/max77686.c b/drivers/mfd/max77686.c new file mode 100644 index 0000000..9f5004d --- /dev/null +++ b/drivers/mfd/max77686.c @@ -0,0 +1,407 @@ +/* + * max77686.c - mfd core driver for the Maxim 77686 + * + * Copyright (C) 2011 Samsung Electronics + * Chiwoong Byun <woong.byun@smasung.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 + * + * This driver is based on max8997.c + */ + +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/pm_runtime.h> +#include <linux/mutex.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max77686.h> +#include <linux/mfd/max77686-private.h> +#include <linux/interrupt.h> + +#define I2C_ADDR_RTC (0x0C >> 1) + +static struct mfd_cell max77686_devs[] = { + { .name = "max77686-pmic", }, +#ifdef CONFIG_RTC_DRV_MAX77686 + { .name = "max77686-rtc", }, +#endif +}; + +int max77686_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) +{ + struct max77686_dev *max77686 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max77686->iolock); + ret = i2c_smbus_read_byte_data(i2c, reg); + mutex_unlock(&max77686->iolock); + if (ret < 0) + return ret; + + ret &= 0xff; + *dest = ret; + return 0; +} +EXPORT_SYMBOL_GPL(max77686_read_reg); + +int max77686_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max77686_dev *max77686 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max77686->iolock); + ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&max77686->iolock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(max77686_bulk_read); + +int max77686_write_reg(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct max77686_dev *max77686 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max77686->iolock); + ret = i2c_smbus_write_byte_data(i2c, reg, value); + mutex_unlock(&max77686->iolock); + return ret; +} +EXPORT_SYMBOL_GPL(max77686_write_reg); + +int max77686_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max77686_dev *max77686 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max77686->iolock); + ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&max77686->iolock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(max77686_bulk_write); + +int max77686_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) +{ + struct max77686_dev *max77686 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max77686->iolock); + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) { + u8 old_val = ret & 0xff; + u8 new_val = (val & mask) | (old_val & (~mask)); + ret = i2c_smbus_write_byte_data(i2c, reg, new_val); + } + mutex_unlock(&max77686->iolock); + return ret; +} +EXPORT_SYMBOL_GPL(max77686_update_reg); + +static int max77686_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max77686_dev *max77686; + struct max77686_platform_data *pdata = i2c->dev.platform_data; + u8 data; + int ret = 0; + + max77686 = kzalloc(sizeof(struct max77686_dev), GFP_KERNEL); + if (max77686 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, max77686); + max77686->dev = &i2c->dev; + max77686->i2c = i2c; + max77686->type = id->driver_data; + + if (!pdata) { + ret = -EIO; + goto err; + } + + max77686->wakeup = pdata->wakeup; + max77686->irq_gpio = pdata->irq_gpio; + max77686->irq_base = pdata->irq_base; + max77686->wtsr_smpl = pdata->wtsr_smpl; + + mutex_init(&max77686->iolock); + + if (max77686_read_reg(i2c, MAX77686_REG_DEVICE_ID, &data) < 0) { + dev_err(max77686->dev, + "device not found on this channel (this is not an error)\n"); + ret = -ENODEV; + goto err; + } else + dev_info(max77686->dev, "device found\n"); + +#ifdef CONFIG_RTC_DRV_MAX77686 + max77686->rtc = i2c_new_dummy(i2c->adapter, I2C_ADDR_RTC); + i2c_set_clientdata(max77686->rtc, max77686); +#endif + + max77686_irq_init(max77686); + + ret = mfd_add_devices(max77686->dev, -1, max77686_devs, + ARRAY_SIZE(max77686_devs), NULL, 0); + + if (ret < 0) + goto err_mfd; + + device_init_wakeup(max77686->dev, pdata->wakeup); + + return ret; + +err_mfd: + mfd_remove_devices(max77686->dev); +#ifdef CONFIG_RTC_DRV_MAX77686 + i2c_unregister_device(max77686->rtc); +#endif +err: + kfree(max77686); + return ret; +} + +static int max77686_i2c_remove(struct i2c_client *i2c) +{ + struct max77686_dev *max77686 = i2c_get_clientdata(i2c); + + mfd_remove_devices(max77686->dev); +#ifdef CONFIG_RTC_DRV_MAX77686 + i2c_unregister_device(max77686->rtc); +#endif + kfree(max77686); + + return 0; +} + +static const struct i2c_device_id max77686_i2c_id[] = { + { "max77686", TYPE_MAX77686 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max77686_i2c_id); + +#ifdef CONFIG_PM +static int max77686_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77686_dev *max77686 = i2c_get_clientdata(i2c); + + disable_irq(max77686->irq); + + if (device_may_wakeup(dev)) + enable_irq_wake(max77686->irq); + + return 0; +} + +static int max77686_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77686_dev *max77686 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + disable_irq_wake(max77686->irq); + + enable_irq(max77686->irq); + + return max77686_irq_resume(max77686); +} +#else +#define max77686_suspend NULL +#define max77686_resume NULL +#endif /* CONFIG_PM */ + +#ifdef CONFIG_HIBERNATION + +u8 max77686_dumpaddr_pmic[] = { + MAX77686_REG_INT1MSK, + MAX77686_REG_INT2MSK, + MAX77686_REG_ONOFF_DELAY, + MAX77686_REG_MRSTB , + /* Reserved: 0x0B-0x0F */ + MAX77686_REG_BUCK1CTRL, + MAX77686_REG_BUCK1OUT, + MAX77686_REG_BUCK2CTRL1, + MAX77686_REG_BUCK234FREQ, + MAX77686_REG_BUCK2DVS1, + MAX77686_REG_BUCK2DVS2, + MAX77686_REG_BUCK2DVS3, + MAX77686_REG_BUCK2DVS4, + MAX77686_REG_BUCK2DVS5, + MAX77686_REG_BUCK2DVS6, + MAX77686_REG_BUCK2DVS7, + MAX77686_REG_BUCK2DVS8, + MAX77686_REG_BUCK3CTRL1, + /* Reserved: 0x1D */ + MAX77686_REG_BUCK3DVS1, + MAX77686_REG_BUCK3DVS2, + MAX77686_REG_BUCK3DVS3, + MAX77686_REG_BUCK3DVS4, + MAX77686_REG_BUCK3DVS5, + MAX77686_REG_BUCK3DVS6, + MAX77686_REG_BUCK3DVS7, + MAX77686_REG_BUCK3DVS8, + MAX77686_REG_BUCK4CTRL1, + /* Reserved: 0x27 */ + MAX77686_REG_BUCK4DVS1, + MAX77686_REG_BUCK4DVS2, + MAX77686_REG_BUCK4DVS3, + MAX77686_REG_BUCK4DVS4, + MAX77686_REG_BUCK4DVS5, + MAX77686_REG_BUCK4DVS6, + MAX77686_REG_BUCK4DVS7, + MAX77686_REG_BUCK4DVS8, + MAX77686_REG_BUCK5CTRL, + MAX77686_REG_BUCK5OUT, + MAX77686_REG_BUCK6CTRL, + MAX77686_REG_BUCK6OUT, + MAX77686_REG_BUCK7CTRL, + MAX77686_REG_BUCK7OUT, + MAX77686_REG_BUCK8CTRL, + MAX77686_REG_BUCK8OUT, + MAX77686_REG_BUCK9CTRL, + MAX77686_REG_BUCK9OUT, + /* Reserved: 0x3A-0x3F */ + MAX77686_REG_LDO1CTRL1 , + MAX77686_REG_LDO2CTRL1 , + MAX77686_REG_LDO3CTRL1 , + MAX77686_REG_LDO4CTRL1 , + MAX77686_REG_LDO5CTRL1 , + MAX77686_REG_LDO6CTRL1, + MAX77686_REG_LDO7CTRL1 , + MAX77686_REG_LDO8CTRL1 , + MAX77686_REG_LDO9CTRL1 , + MAX77686_REG_LDO10CTRL1, + MAX77686_REG_LDO11CTRL1, + MAX77686_REG_LDO12CTRL1, + MAX77686_REG_LDO13CTRL1, + MAX77686_REG_LDO14CTRL1, + MAX77686_REG_LDO15CTRL1, + MAX77686_REG_LDO16CTRL1, + MAX77686_REG_LDO17CTRL1, + MAX77686_REG_LDO18CTRL1, + MAX77686_REG_LDO19CTRL1, + MAX77686_REG_LDO20CTRL1, + MAX77686_REG_LDO21CTRL1, + MAX77686_REG_LDO22CTRL1, + MAX77686_REG_LDO23CTRL1, + MAX77686_REG_LDO24CTRL1, + MAX77686_REG_LDO25CTRL1, + MAX77686_REG_LDO26CTRL1, + /* Reserved: 0x5A-0x5F */ + MAX77686_REG_LDO1CTRL2 , + MAX77686_REG_LDO2CTRL2 , + MAX77686_REG_LDO3CTRL2 , + MAX77686_REG_LDO4CTRL2 , + MAX77686_REG_LDO5CTRL2 , + MAX77686_REG_LDO6CTRL2, + MAX77686_REG_LDO7CTRL2 , + MAX77686_REG_LDO8CTRL2 , + MAX77686_REG_LDO9CTRL2 , + MAX77686_REG_LDO10CTRL2, + MAX77686_REG_LDO11CTRL2, + MAX77686_REG_LDO12CTRL2, + MAX77686_REG_LDO13CTRL2, + MAX77686_REG_LDO14CTRL2, + MAX77686_REG_LDO15CTRL2, + MAX77686_REG_LDO16CTRL2, + MAX77686_REG_LDO17CTRL2, + MAX77686_REG_LDO18CTRL2, + MAX77686_REG_LDO19CTRL2, + MAX77686_REG_LDO20CTRL2, + MAX77686_REG_LDO21CTRL2, + MAX77686_REG_LDO22CTRL2, + MAX77686_REG_LDO23CTRL2, + MAX77686_REG_LDO24CTRL2, + MAX77686_REG_LDO25CTRL2, + MAX77686_REG_LDO26CTRL2, + MAX77686_REG_BBAT_CHG, + MAX77686_REG_32KHZ, +}; + +static int max77686_freeze(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77686_dev *max77686 = i2c_get_clientdata(i2c); + int i; + + for (i = 0; i < ARRAY_SIZE(max77686_dumpaddr_pmic); i++) + max77686_read_reg(i2c, max77686_dumpaddr_pmic[i], + &max77686->reg_dump[i]); + + disable_irq(max77686->irq); + + return 0; +} + +static int max77686_restore(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77686_dev *max77686 = i2c_get_clientdata(i2c); + int i; + + enable_irq(max77686->irq); + + for (i = 0; i < ARRAY_SIZE(max77686_dumpaddr_pmic); i++) + max77686_write_reg(i2c, max77686_dumpaddr_pmic[i], + max77686->reg_dump[i]); + + return 0; +} +#endif + +const struct dev_pm_ops max77686_pm = { + .suspend = max77686_suspend, + .resume = max77686_resume, +#ifdef CONFIG_HIBERNATION + .freeze = max77686_freeze, + .thaw = max77686_restore, + .restore = max77686_restore, +#endif +}; + +static struct i2c_driver max77686_i2c_driver = { + .driver = { + .name = "max77686", + .owner = THIS_MODULE, + .pm = &max77686_pm, + }, + .probe = max77686_i2c_probe, + .remove = max77686_i2c_remove, + .id_table = max77686_i2c_id, +}; + +static int __init max77686_i2c_init(void) +{ + return i2c_add_driver(&max77686_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(max77686_i2c_init); + +static void __exit max77686_i2c_exit(void) +{ + i2c_del_driver(&max77686_i2c_driver); +} +module_exit(max77686_i2c_exit); + +MODULE_DESCRIPTION("MAXIM 77686 multi-function core driver"); +MODULE_AUTHOR("Chiwoong Byun <woong.byun@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/max77693-irq.c b/drivers/mfd/max77693-irq.c new file mode 100644 index 0000000..a52fc02 --- /dev/null +++ b/drivers/mfd/max77693-irq.c @@ -0,0 +1,349 @@ +/* + * max77693-irq.c - Interrupt controller support for MAX77693 + * + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * SangYoung Son <hello.son@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 + * + * This driver is based on max77693-irq.c + */ + +#include <linux/err.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/mfd/max77693.h> +#include <linux/mfd/max77693-private.h> + +static const u8 max77693_mask_reg[] = { + [LED_INT] = MAX77693_LED_REG_FLASH_INT_MASK, + [TOPSYS_INT] = MAX77693_PMIC_REG_TOPSYS_INT_MASK, + [CHG_INT] = MAX77693_CHG_REG_CHG_INT_MASK, + [MUIC_INT1] = MAX77693_MUIC_REG_INTMASK1, + [MUIC_INT2] = MAX77693_MUIC_REG_INTMASK2, + [MUIC_INT3] = MAX77693_MUIC_REG_INTMASK3, +}; + +static struct i2c_client *get_i2c(struct max77693_dev *max77693, + enum max77693_irq_source src) +{ + switch (src) { + case LED_INT ... CHG_INT: + return max77693->i2c; + case MUIC_INT1 ... MUIC_INT3: + return max77693->muic; + default: + return ERR_PTR(-EINVAL); + } +} + +struct max77693_irq_data { + int mask; + enum max77693_irq_source group; +}; + +#define DECLARE_IRQ(idx, _group, _mask) \ + [(idx)] = { .group = (_group), .mask = (_mask) } +static const struct max77693_irq_data max77693_irqs[] = { + DECLARE_IRQ(MAX77693_LED_IRQ_FLED2_OPEN, LED_INT, 1 << 0), + DECLARE_IRQ(MAX77693_LED_IRQ_FLED2_SHORT, LED_INT, 1 << 1), + DECLARE_IRQ(MAX77693_LED_IRQ_FLED1_OPEN, LED_INT, 1 << 2), + DECLARE_IRQ(MAX77693_LED_IRQ_FLED1_SHORT, LED_INT, 1 << 3), + DECLARE_IRQ(MAX77693_LED_IRQ_MAX_FLASH, LED_INT, 1 << 4), + + DECLARE_IRQ(MAX77693_TOPSYS_IRQ_T120C_INT, TOPSYS_INT, 1 << 0), + DECLARE_IRQ(MAX77693_TOPSYS_IRQ_T140C_INT, TOPSYS_INT, 1 << 1), + DECLARE_IRQ(MAX77693_TOPSYS_IRQLOWSYS_INT, TOPSYS_INT, 1 << 3), + + DECLARE_IRQ(MAX77693_CHG_IRQ_BYP_I, CHG_INT, 1 << 0), + DECLARE_IRQ(MAX77693_CHG_IRQ_THM_I, CHG_INT, 1 << 2), + DECLARE_IRQ(MAX77693_CHG_IRQ_BAT_I, CHG_INT, 1 << 3), + DECLARE_IRQ(MAX77693_CHG_IRQ_CHG_I, CHG_INT, 1 << 4), + DECLARE_IRQ(MAX77693_CHG_IRQ_CHGIN_I, CHG_INT, 1 << 6), + + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT1_ADC, MUIC_INT1, 1 << 0), + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT1_ADCLOW, MUIC_INT1, 1 << 1), + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT1_ADCERR, MUIC_INT1, 1 << 2), + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT1_ADC1K, MUIC_INT1, 1 << 3), + + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT2_CHGTYP, MUIC_INT2, 1 << 0), + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT2_CHGDETREUN, MUIC_INT2, 1 << 1), + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT2_DCDTMR, MUIC_INT2, 1 << 2), + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT2_DXOVP, MUIC_INT2, 1 << 3), + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT2_VBVOLT, MUIC_INT2, 1 << 4), + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT2_VIDRM, MUIC_INT2, 1 << 5), + + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT3_EOC, MUIC_INT3, 1 << 0), + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT3_CGMBC, MUIC_INT3, 1 << 1), + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT3_OVP, MUIC_INT3, 1 << 2), + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT3_MBCCHGERR, MUIC_INT3, 1 << 3), + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT3_CHGENABLED, MUIC_INT3, 1 << 4), + DECLARE_IRQ(MAX77693_MUIC_IRQ_INT3_BATDET, MUIC_INT3, 1 << 5), +}; + +static void max77693_irq_lock(struct irq_data *data) +{ + struct max77693_dev *max77693 = irq_get_chip_data(data->irq); + + mutex_lock(&max77693->irqlock); +} + +static void max77693_irq_sync_unlock(struct irq_data *data) +{ + struct max77693_dev *max77693 = irq_get_chip_data(data->irq); + int i; + + for (i = 0; i < MAX77693_IRQ_GROUP_NR; i++) { + u8 mask_reg = max77693_mask_reg[i]; + struct i2c_client *i2c = get_i2c(max77693, i); + + if (mask_reg == MAX77693_REG_INVALID || + IS_ERR_OR_NULL(i2c)) + continue; + max77693->irq_masks_cache[i] = max77693->irq_masks_cur[i]; + + max77693_write_reg(i2c, max77693_mask_reg[i], + max77693->irq_masks_cur[i]); + } + + mutex_unlock(&max77693->irqlock); +} + +static const inline struct max77693_irq_data * +irq_to_max77693_irq(struct max77693_dev *max77693, int irq) +{ + return &max77693_irqs[irq - max77693->irq_base]; +} + +static void max77693_irq_mask(struct irq_data *data) +{ + struct max77693_dev *max77693 = irq_get_chip_data(data->irq); + const struct max77693_irq_data *irq_data = + irq_to_max77693_irq(max77693, data->irq); + + if (irq_data->group >= MAX77693_IRQ_GROUP_NR) + return; + + if (irq_data->group >= MUIC_INT1 && irq_data->group <= MUIC_INT3) + max77693->irq_masks_cur[irq_data->group] &= ~irq_data->mask; + else + max77693->irq_masks_cur[irq_data->group] |= irq_data->mask; +} + +static void max77693_irq_unmask(struct irq_data *data) +{ + struct max77693_dev *max77693 = irq_get_chip_data(data->irq); + const struct max77693_irq_data *irq_data = + irq_to_max77693_irq(max77693, data->irq); + + if (irq_data->group >= MAX77693_IRQ_GROUP_NR) + return; + + if (irq_data->group >= MUIC_INT1 && irq_data->group <= MUIC_INT3) + max77693->irq_masks_cur[irq_data->group] |= irq_data->mask; + else + max77693->irq_masks_cur[irq_data->group] &= ~irq_data->mask; +} + +static struct irq_chip max77693_irq_chip = { + .name = "max77693", + .irq_bus_lock = max77693_irq_lock, + .irq_bus_sync_unlock = max77693_irq_sync_unlock, + .irq_mask = max77693_irq_mask, + .irq_unmask = max77693_irq_unmask, +}; + +static irqreturn_t max77693_irq_thread(int irq, void *data) +{ + struct max77693_dev *max77693 = data; + u8 irq_reg[MAX77693_IRQ_GROUP_NR] = {}; + u8 irq_src; + int ret; + int i; + pr_debug("%s: irq gpio pre-state(0x%02x)\n", __func__, + gpio_get_value(max77693->irq_gpio)); + +clear_retry: + ret = max77693_read_reg(max77693->i2c, MAX77693_PMIC_REG_INTSRC, &irq_src); + if (ret < 0) { + dev_err(max77693->dev, "Failed to read interrupt source: %d\n", + ret); + return IRQ_NONE; + } + pr_info("%s: interrupt source(0x%02x)\n", __func__, irq_src); + + if (irq_src & MAX77693_IRQSRC_CHG) { + /* CHG_INT */ + ret = max77693_read_reg(max77693->i2c, MAX77693_CHG_REG_CHG_INT, + &irq_reg[CHG_INT]); + pr_info("%s: charger interrupt(0x%02x)\n", __func__, irq_reg[CHG_INT]); + } + + if (irq_src & MAX77693_IRQSRC_TOP) { + /* TOPSYS_INT */ + ret = max77693_read_reg(max77693->i2c, MAX77693_PMIC_REG_TOPSYS_INT, + &irq_reg[TOPSYS_INT]); + pr_info("%s: topsys interrupt(0x%02x)\n", __func__, irq_reg[TOPSYS_INT]); + } + + if (irq_src & MAX77693_IRQSRC_FLASH) { + /* LED_INT */ + ret = max77693_read_reg(max77693->i2c, MAX77693_LED_REG_FLASH_INT, + &irq_reg[LED_INT]); + pr_info("%s: led interrupt(0x%02x)\n", __func__, irq_reg[LED_INT]); + } + + if (irq_src & MAX77693_IRQSRC_MUIC) { + /* MUIC INT1 ~ INT3 */ + max77693_bulk_read(max77693->muic, MAX77693_MUIC_REG_INT1, MAX77693_NUM_IRQ_MUIC_REGS, + &irq_reg[MUIC_INT1]); + pr_info("%s: muic interrupt(0x%02x, 0x%02x, 0x%02x)\n", __func__, + irq_reg[MUIC_INT1], irq_reg[MUIC_INT2], irq_reg[MUIC_INT3]); + } + + pr_debug("%s: irq gpio post-state(0x%02x)\n", __func__, + gpio_get_value(max77693->irq_gpio)); + + if (gpio_get_value(max77693->irq_gpio) == 0) + goto clear_retry; + + /* Apply masking */ + for (i = 0; i < MAX77693_IRQ_GROUP_NR; i++) { + if (i >= MUIC_INT1 && i <= MUIC_INT3) + irq_reg[i] &= max77693->irq_masks_cur[i]; + else + irq_reg[i] &= ~max77693->irq_masks_cur[i]; + } + + /* Report */ + for (i = 0; i < MAX77693_IRQ_NR; i++) { + if (irq_reg[max77693_irqs[i].group] & max77693_irqs[i].mask) + handle_nested_irq(max77693->irq_base + i); + } + + return IRQ_HANDLED; +} + +int max77693_irq_resume(struct max77693_dev *max77693) +{ + int ret = 0; + if (max77693->irq && max77693->irq_base) + ret = max77693_irq_thread(max77693->irq_base, max77693); + + dev_info(max77693->dev, "%s: irq_resume ret=%d", __func__, ret); + + return ret >= 0 ? 0 : ret; +} + +int max77693_irq_init(struct max77693_dev *max77693) +{ + int i; + int cur_irq; + int ret; + u8 i2c_data; + + if (!max77693->irq_gpio) { + dev_warn(max77693->dev, "No interrupt specified.\n"); + max77693->irq_base = 0; + return 0; + } + + if (!max77693->irq_base) { + dev_err(max77693->dev, "No interrupt base specified.\n"); + return 0; + } + + mutex_init(&max77693->irqlock); + + max77693->irq = gpio_to_irq(max77693->irq_gpio); + ret = gpio_request(max77693->irq_gpio, "if_pmic_irq"); + if (ret) { + dev_err(max77693->dev, "%s: failed requesting gpio %d\n", + __func__, max77693->irq_gpio); + return ret; + } + gpio_direction_input(max77693->irq_gpio); + gpio_free(max77693->irq_gpio); + + /* Mask individual interrupt sources */ + for (i = 0; i < MAX77693_IRQ_GROUP_NR; i++) { + struct i2c_client *i2c; + /* MUIC IRQ 0:MASK 1:NOT MASK */ + /* Other IRQ 1:MASK 0:NOT MASK */ + if (i >= MUIC_INT1 && i <= MUIC_INT3) { + max77693->irq_masks_cur[i] = 0x00; + max77693->irq_masks_cache[i] = 0x00; + } else { + max77693->irq_masks_cur[i] = 0xff; + max77693->irq_masks_cache[i] = 0xff; + } + i2c = get_i2c(max77693, i); + + if (IS_ERR_OR_NULL(i2c)) + continue; + if (max77693_mask_reg[i] == MAX77693_REG_INVALID) + continue; + if (i >= MUIC_INT1 && i <= MUIC_INT3) + max77693_write_reg(i2c, max77693_mask_reg[i], 0x00); + else + max77693_write_reg(i2c, max77693_mask_reg[i], 0xff); + } + + /* Register with genirq */ + for (i = 0; i < MAX77693_IRQ_NR; i++) { + cur_irq = i + max77693->irq_base; + irq_set_chip_data(cur_irq, max77693); + irq_set_chip_and_handler(cur_irq, &max77693_irq_chip, + handle_edge_irq); + irq_set_nested_thread(cur_irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + irq_set_noprobe(cur_irq); +#endif + } + + /* Unmask max77693 interrupt */ + ret = max77693_read_reg(max77693->i2c, MAX77693_PMIC_REG_INTSRC_MASK, + &i2c_data); + if (ret) { + dev_err(max77693->dev, "%s: fail to read muic reg\n", __func__); + return ret; + } + + i2c_data &= ~(MAX77693_IRQSRC_CHG); /* Unmask charger interrupt */ + i2c_data &= ~(MAX77693_IRQSRC_MUIC); /* Unmask muic interrupt */ + max77693_write_reg(max77693->i2c, MAX77693_PMIC_REG_INTSRC_MASK, + i2c_data); + + ret = request_threaded_irq(max77693->irq, NULL, max77693_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "max77693-irq", max77693); + + if (ret) { + dev_err(max77693->dev, "Failed to request IRQ %d: %d\n", + max77693->irq, ret); + return ret; + } + + return 0; +} + +void max77693_irq_exit(struct max77693_dev *max77693) +{ + if (max77693->irq) + free_irq(max77693->irq, max77693); +} diff --git a/drivers/mfd/max77693.c b/drivers/mfd/max77693.c new file mode 100644 index 0000000..ee13340 --- /dev/null +++ b/drivers/mfd/max77693.c @@ -0,0 +1,390 @@ +/* + * max77693.c - mfd core driver for the Maxim 77693 + * + * Copyright (C) 2011 Samsung Electronics + * SangYoung Son <hello.son@smasung.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 + * + * This driver is based on max8997.c + */ + +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max77693.h> +#include <linux/mfd/max77693-private.h> +#include <linux/regulator/machine.h> + +#define I2C_ADDR_PMIC (0xCC >> 1) /* Charger, Flash LED */ +#define I2C_ADDR_MUIC (0x4A >> 1) +#define I2C_ADDR_HAPTIC (0x90 >> 1) + +static struct mfd_cell max77693_devs[] = { + { .name = "max77693-charger", }, + { .name = "max77693-led", }, + { .name = "max77693-muic", }, + { .name = "max77693-safeout", }, + { .name = "max77693-haptic", }, +}; + +int max77693_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) +{ + struct max77693_dev *max77693 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max77693->iolock); + ret = i2c_smbus_read_byte_data(i2c, reg); + mutex_unlock(&max77693->iolock); + if (ret < 0) + return ret; + + ret &= 0xff; + *dest = ret; + return 0; +} +EXPORT_SYMBOL_GPL(max77693_read_reg); + +int max77693_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max77693_dev *max77693 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max77693->iolock); + ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&max77693->iolock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(max77693_bulk_read); + +int max77693_write_reg(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct max77693_dev *max77693 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max77693->iolock); + ret = i2c_smbus_write_byte_data(i2c, reg, value); + mutex_unlock(&max77693->iolock); + return ret; +} +EXPORT_SYMBOL_GPL(max77693_write_reg); + +int max77693_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct max77693_dev *max77693 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max77693->iolock); + ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&max77693->iolock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(max77693_bulk_write); + +int max77693_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) +{ + struct max77693_dev *max77693 = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&max77693->iolock); + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) { + u8 old_val = ret & 0xff; + u8 new_val = (val & mask) | (old_val & (~mask)); + ret = i2c_smbus_write_byte_data(i2c, reg, new_val); + } + mutex_unlock(&max77693->iolock); + return ret; +} +EXPORT_SYMBOL_GPL(max77693_update_reg); + +static int max77693_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max77693_dev *max77693; + struct max77693_platform_data *pdata = i2c->dev.platform_data; + u8 reg_data; + int ret = 0; + + max77693 = kzalloc(sizeof(struct max77693_dev), GFP_KERNEL); + if (max77693 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, max77693); + max77693->dev = &i2c->dev; + max77693->i2c = i2c; + max77693->irq = i2c->irq; + max77693->type = id->driver_data; + if (pdata) { + max77693->irq_base = pdata->irq_base; + max77693->irq_gpio = pdata->irq_gpio; + max77693->wakeup = pdata->wakeup; + } else + goto err; + + mutex_init(&max77693->iolock); + + if (max77693_read_reg(i2c, MAX77693_PMIC_REG_PMIC_ID2, ®_data) < 0) { + dev_err(max77693->dev, + "device not found on this channel (this is not an error)\n"); + ret = -ENODEV; + goto err; + } else { + /* print rev */ + max77693->pmic_rev = (reg_data & 0x7); + max77693->pmic_ver = ((reg_data & 0xF8) >> 0x3); + pr_info("%s: device found: rev.0x%x, ver.0x%x\n", __func__, + max77693->pmic_rev, max77693->pmic_ver); + } + + max77693->muic = i2c_new_dummy(i2c->adapter, I2C_ADDR_MUIC); + i2c_set_clientdata(max77693->muic, max77693); + + max77693->haptic = i2c_new_dummy(i2c->adapter, I2C_ADDR_HAPTIC); + i2c_set_clientdata(max77693->haptic, max77693); + + ret = max77693_irq_init(max77693); + if (ret < 0) + goto err_mfd; + + ret = mfd_add_devices(max77693->dev, -1, max77693_devs, + ARRAY_SIZE(max77693_devs), NULL, 0); + if (ret < 0) + goto err_mfd; + + device_init_wakeup(max77693->dev, pdata->wakeup); + + return ret; + +err_mfd: + mfd_remove_devices(max77693->dev); + i2c_unregister_device(max77693->muic); + i2c_unregister_device(max77693->haptic); +err: + kfree(max77693); + return ret; +} + +static int max77693_i2c_remove(struct i2c_client *i2c) +{ + struct max77693_dev *max77693 = i2c_get_clientdata(i2c); + + mfd_remove_devices(max77693->dev); + i2c_unregister_device(max77693->muic); + i2c_unregister_device(max77693->haptic); + kfree(max77693); + + return 0; +} + +static const struct i2c_device_id max77693_i2c_id[] = { + { "max77693", TYPE_MAX77693 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max77693_i2c_id); + +#ifdef CONFIG_PM +static int max77693_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77693_dev *max77693 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + enable_irq_wake(max77693->irq); + + disable_irq(max77693->irq); + + return 0; +} + +static int max77693_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77693_dev *max77693 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + disable_irq_wake(max77693->irq); + + enable_irq(max77693->irq); + + return max77693_irq_resume(max77693); +} +#else +#define max77693_suspend NULL +#define max77693_resume NULL +#endif /* CONFIG_PM */ + +#ifdef CONFIG_HIBERNATION + +u8 max77693_dumpaddr_pmic[] = { + MAX77693_LED_REG_IFLASH1, + MAX77693_LED_REG_IFLASH2, + MAX77693_LED_REG_ITORCH, + MAX77693_LED_REG_ITORCHTORCHTIMER, + MAX77693_LED_REG_FLASH_TIMER, + MAX77693_LED_REG_FLASH_EN, + MAX77693_LED_REG_MAX_FLASH1, + MAX77693_LED_REG_MAX_FLASH2, + MAX77693_LED_REG_VOUT_CNTL, + MAX77693_LED_REG_VOUT_FLASH1, + MAX77693_LED_REG_FLASH_INT_STATUS, + + MAX77693_PMIC_REG_TOPSYS_INT_MASK, + MAX77693_PMIC_REG_MAINCTRL1, + MAX77693_PMIC_REG_LSCNFG, + MAX77693_CHG_REG_CHG_INT_MASK, + MAX77693_CHG_REG_CHG_CNFG_00, + MAX77693_CHG_REG_CHG_CNFG_01, + MAX77693_CHG_REG_CHG_CNFG_02, + MAX77693_CHG_REG_CHG_CNFG_03, + MAX77693_CHG_REG_CHG_CNFG_04, + MAX77693_CHG_REG_CHG_CNFG_05, + MAX77693_CHG_REG_CHG_CNFG_06, + MAX77693_CHG_REG_CHG_CNFG_07, + MAX77693_CHG_REG_CHG_CNFG_08, + MAX77693_CHG_REG_CHG_CNFG_09, + MAX77693_CHG_REG_CHG_CNFG_10, + MAX77693_CHG_REG_CHG_CNFG_11, + MAX77693_CHG_REG_CHG_CNFG_12, + MAX77693_CHG_REG_CHG_CNFG_13, + MAX77693_CHG_REG_CHG_CNFG_14, + MAX77693_CHG_REG_SAFEOUT_CTRL, +}; + +u8 max77693_dumpaddr_muic[] = { + MAX77693_MUIC_REG_INTMASK1, + MAX77693_MUIC_REG_INTMASK2, + MAX77693_MUIC_REG_INTMASK3, + MAX77693_MUIC_REG_CDETCTRL1, + MAX77693_MUIC_REG_CDETCTRL2, + MAX77693_MUIC_REG_CTRL1, + MAX77693_MUIC_REG_CTRL2, + MAX77693_MUIC_REG_CTRL3, +}; + + +u8 max77693_dumpaddr_haptic[] = { + MAX77693_HAPTIC_REG_CONFIG1, + MAX77693_HAPTIC_REG_CONFIG2, + MAX77693_HAPTIC_REG_CONFIG_CHNL, + MAX77693_HAPTIC_REG_CONFG_CYC1, + MAX77693_HAPTIC_REG_CONFG_CYC2, + MAX77693_HAPTIC_REG_CONFIG_PER1, + MAX77693_HAPTIC_REG_CONFIG_PER2, + MAX77693_HAPTIC_REG_CONFIG_PER3, + MAX77693_HAPTIC_REG_CONFIG_PER4, + MAX77693_HAPTIC_REG_CONFIG_DUTY1, + MAX77693_HAPTIC_REG_CONFIG_DUTY2, + MAX77693_HAPTIC_REG_CONFIG_PWM1, + MAX77693_HAPTIC_REG_CONFIG_PWM2, + MAX77693_HAPTIC_REG_CONFIG_PWM3, + MAX77693_HAPTIC_REG_CONFIG_PWM4, +}; + + +static int max77693_freeze(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77693_dev *max77693 = i2c_get_clientdata(i2c); + int i; + + for (i = 0; i < ARRAY_SIZE(max77693_dumpaddr_pmic); i++) + max77693_read_reg(i2c, max77693_dumpaddr_pmic[i], + &max77693->reg_pmic_dump[i]); + + for (i = 0; i < ARRAY_SIZE(max77693_dumpaddr_muic); i++) + max77693_read_reg(i2c, max77693_dumpaddr_muic[i], + &max77693->reg_muic_dump[i]); + + for (i = 0; i < ARRAY_SIZE(max77693_dumpaddr_haptic); i++) + max77693_read_reg(i2c, max77693_dumpaddr_haptic[i], + &max77693->reg_haptic_dump[i]); + + disable_irq(max77693->irq); + + return 0; +} + +static int max77693_restore(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct max77693_dev *max77693 = i2c_get_clientdata(i2c); + int i; + + enable_irq(max77693->irq); + + for (i = 0; i < ARRAY_SIZE(max77693_dumpaddr_pmic); i++) + max77693_write_reg(i2c, max77693_dumpaddr_pmic[i], + max77693->reg_pmic_dump[i]); + + for (i = 0; i < ARRAY_SIZE(max77693_dumpaddr_muic); i++) + max77693_write_reg(i2c, max77693_dumpaddr_muic[i], + max77693->reg_muic_dump[i]); + + for (i = 0; i < ARRAY_SIZE(max77693_dumpaddr_haptic); i++) + max77693_write_reg(i2c, max77693_dumpaddr_haptic[i], + max77693->reg_haptic_dump[i]); + + + return 0; +} +#endif + + +const struct dev_pm_ops max77693_pm = { + .suspend = max77693_suspend, + .resume = max77693_resume, +#ifdef CONFIG_HIBERNATION + .freeze = max77693_freeze, + .thaw = max77693_restore, + .restore = max77693_restore, +#endif +}; + +static struct i2c_driver max77693_i2c_driver = { + .driver = { + .name = "max77693", + .owner = THIS_MODULE, + .pm = &max77693_pm, + }, + .probe = max77693_i2c_probe, + .remove = max77693_i2c_remove, + .id_table = max77693_i2c_id, +}; + +static int __init max77693_i2c_init(void) +{ + return i2c_add_driver(&max77693_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(max77693_i2c_init); + +static void __exit max77693_i2c_exit(void) +{ + i2c_del_driver(&max77693_i2c_driver); +} +module_exit(max77693_i2c_exit); + +MODULE_DESCRIPTION("MAXIM 77693 multi-function core driver"); +MODULE_AUTHOR("SangYoung, Son <hello.son@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/max8698.c b/drivers/mfd/max8698.c new file mode 100644 index 0000000..9dd6004 --- /dev/null +++ b/drivers/mfd/max8698.c @@ -0,0 +1,161 @@ +/* + * max8698.c - mfd core driver for the Maxim 8698 + * + * Copyright (C) 2009-2010 Samsung Electronics + * Kyungmin Park <kyungmin.park@samsung.com> + * Marek Szyprowski <m.szyprowski@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 + * + * 2010.10.26 + * Modified by Taekki Kim <taekki.kim@samsung.com> + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/mfd/core.h> +#include <linux/mfd/max8698.h> +#include <linux/mfd/max8698-private.h> + +static struct mfd_cell max8698_devs[] = { + { + .name = "max8698-pmic", + } +}; + +static int max8698_i2c_device_read(struct max8698_dev *max8698, u8 reg, u8 *dest) +{ + struct i2c_client *client = max8698->i2c_client; + int ret; + + mutex_lock(&max8698->iolock); + ret = i2c_smbus_read_byte_data(client, reg); + mutex_unlock(&max8698->iolock); + if (ret < 0) + return ret; + + ret &= 0xff; + *dest = ret; + return 0; +} + +static int max8698_i2c_device_write(struct max8698_dev *max8698, u8 reg, u8 value) +{ + struct i2c_client *client = max8698->i2c_client; + int ret; + + mutex_lock(&max8698->iolock); + ret = i2c_smbus_write_byte_data(client, reg, value); + mutex_unlock(&max8698->iolock); + return ret; +} + +static int max8698_i2c_device_update(struct max8698_dev *max8698, u8 reg, + u8 val, u8 mask) +{ + struct i2c_client *client = max8698->i2c_client; + int ret; + + mutex_lock(&max8698->iolock); + ret = i2c_smbus_read_byte_data(client, reg); + if (ret >= 0) { + u8 old_val = ret & 0xff; + u8 new_val = (val & mask) | (old_val & (~mask)); + ret = i2c_smbus_write_byte_data(client, reg, new_val); + if (ret >= 0) + ret = 0; + } + mutex_unlock(&max8698->iolock); + return ret; +} + +static int max8698_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct max8698_dev *max8698; + int ret = 0; + + max8698 = kzalloc(sizeof(struct max8698_dev), GFP_KERNEL); + if (max8698 == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, max8698); + max8698->dev = &i2c->dev; + max8698->i2c_client = i2c; + max8698->dev_read = max8698_i2c_device_read; + max8698->dev_write = max8698_i2c_device_write; + max8698->dev_update = max8698_i2c_device_update; + mutex_init(&max8698->iolock); + + ret = mfd_add_devices(max8698->dev, -1, + max8698_devs, ARRAY_SIZE(max8698_devs), + NULL, 0); + if (ret < 0) + goto err; + + return ret; + +err: + mfd_remove_devices(max8698->dev); + kfree(max8698); + return ret; +} + +static int max8698_i2c_remove(struct i2c_client *i2c) +{ + struct max8698_dev *max8698 = i2c_get_clientdata(i2c); + + mfd_remove_devices(max8698->dev); + kfree(max8698); + + return 0; +} + +static const struct i2c_device_id max8698_i2c_id[] = { + { "max8698", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max8698_i2c_id); + +static struct i2c_driver max8698_i2c_driver = { + .driver = { + .name = "max8698", + .owner = THIS_MODULE, + }, + .probe = max8698_i2c_probe, + .remove = max8698_i2c_remove, + .id_table = max8698_i2c_id, +}; + +static int __init max8698_i2c_init(void) +{ + return i2c_add_driver(&max8698_i2c_driver); +} +/* init early so consumer devices can complete system boot */ +subsys_initcall(max8698_i2c_init); + +static void __exit max8698_i2c_exit(void) +{ + i2c_del_driver(&max8698_i2c_driver); +} +module_exit(max8698_i2c_exit); + +MODULE_DESCRIPTION("MAXIM 8698 multi-function core driver"); +MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/max8997-irq.c b/drivers/mfd/max8997-irq.c index 638bf7e..399ba92 100644 --- a/drivers/mfd/max8997-irq.c +++ b/drivers/mfd/max8997-irq.c @@ -1,118 +1,187 @@ /* - * max8997-irq.c - Interrupt controller support for MAX8997 + * Interrupt controller support for MAX8997 * - * Copyright (C) 2011 Samsung Electronics Co.Ltd - * MyungJoo Ham <myungjoo.ham@samsung.com> + * Copyright (C) 2010 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. + * based on max8998-irq.c * - * 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. + * <ms925.kim@samsung.com> * - * 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 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 driver is based on max8998-irq.c */ -#include <linux/err.h> -#include <linux/irq.h> +#include <linux/device.h> #include <linux/interrupt.h> -#include <linux/mfd/max8997.h> +#include <linux/irq.h> #include <linux/mfd/max8997-private.h> +#include <linux/delay.h> -static const u8 max8997_mask_reg[] = { - [PMIC_INT1] = MAX8997_REG_INT1MSK, - [PMIC_INT2] = MAX8997_REG_INT2MSK, - [PMIC_INT3] = MAX8997_REG_INT3MSK, - [PMIC_INT4] = MAX8997_REG_INT4MSK, - [FUEL_GAUGE] = MAX8997_REG_INVALID, - [MUIC_INT1] = MAX8997_MUIC_REG_INTMASK1, - [MUIC_INT2] = MAX8997_MUIC_REG_INTMASK2, - [MUIC_INT3] = MAX8997_MUIC_REG_INTMASK3, - [GPIO_LOW] = MAX8997_REG_INVALID, - [GPIO_HI] = MAX8997_REG_INVALID, - [FLASH_STATUS] = MAX8997_REG_INVALID, -}; +extern struct class *sec_class; -static struct i2c_client *get_i2c(struct max8997_dev *max8997, - enum max8997_irq_source src) -{ - switch (src) { - case PMIC_INT1 ... PMIC_INT4: - return max8997->i2c; - case FUEL_GAUGE: - return NULL; - case MUIC_INT1 ... MUIC_INT3: - return max8997->muic; - case GPIO_LOW ... GPIO_HI: - return max8997->i2c; - case FLASH_STATUS: - return max8997->i2c; - default: - return ERR_PTR(-EINVAL); - } +#define FLED1_FLT_BIT (1<<0) +#define FLED2_FLT_BIT (1<<1) +#define FLED_FLT_MASK (FLED1_FLT_BIT | FLED2_FLT_BIT) - return ERR_PTR(-EINVAL); -} +static u8 fled_status; struct max8997_irq_data { + int reg; int mask; - enum max8997_irq_source group; }; -#define DECLARE_IRQ(idx, _group, _mask) \ - [(idx)] = { .group = (_group), .mask = (_mask) } -static const struct max8997_irq_data max8997_irqs[] = { - DECLARE_IRQ(MAX8997_PMICIRQ_PWRONR, PMIC_INT1, 1 << 0), - DECLARE_IRQ(MAX8997_PMICIRQ_PWRONF, PMIC_INT1, 1 << 1), - DECLARE_IRQ(MAX8997_PMICIRQ_PWRON1SEC, PMIC_INT1, 1 << 3), - DECLARE_IRQ(MAX8997_PMICIRQ_JIGONR, PMIC_INT1, 1 << 4), - DECLARE_IRQ(MAX8997_PMICIRQ_JIGONF, PMIC_INT1, 1 << 5), - DECLARE_IRQ(MAX8997_PMICIRQ_LOWBAT2, PMIC_INT1, 1 << 6), - DECLARE_IRQ(MAX8997_PMICIRQ_LOWBAT1, PMIC_INT1, 1 << 7), - - DECLARE_IRQ(MAX8997_PMICIRQ_JIGR, PMIC_INT2, 1 << 0), - DECLARE_IRQ(MAX8997_PMICIRQ_JIGF, PMIC_INT2, 1 << 1), - DECLARE_IRQ(MAX8997_PMICIRQ_MR, PMIC_INT2, 1 << 2), - DECLARE_IRQ(MAX8997_PMICIRQ_DVS1OK, PMIC_INT2, 1 << 3), - DECLARE_IRQ(MAX8997_PMICIRQ_DVS2OK, PMIC_INT2, 1 << 4), - DECLARE_IRQ(MAX8997_PMICIRQ_DVS3OK, PMIC_INT2, 1 << 5), - DECLARE_IRQ(MAX8997_PMICIRQ_DVS4OK, PMIC_INT2, 1 << 6), - - DECLARE_IRQ(MAX8997_PMICIRQ_CHGINS, PMIC_INT3, 1 << 0), - DECLARE_IRQ(MAX8997_PMICIRQ_CHGRM, PMIC_INT3, 1 << 1), - DECLARE_IRQ(MAX8997_PMICIRQ_DCINOVP, PMIC_INT3, 1 << 2), - DECLARE_IRQ(MAX8997_PMICIRQ_TOPOFFR, PMIC_INT3, 1 << 3), - DECLARE_IRQ(MAX8997_PMICIRQ_CHGRSTF, PMIC_INT3, 1 << 5), - DECLARE_IRQ(MAX8997_PMICIRQ_MBCHGTMEXPD, PMIC_INT3, 1 << 7), - - DECLARE_IRQ(MAX8997_PMICIRQ_RTC60S, PMIC_INT4, 1 << 0), - DECLARE_IRQ(MAX8997_PMICIRQ_RTCA1, PMIC_INT4, 1 << 1), - DECLARE_IRQ(MAX8997_PMICIRQ_RTCA2, PMIC_INT4, 1 << 2), - DECLARE_IRQ(MAX8997_PMICIRQ_SMPL_INT, PMIC_INT4, 1 << 3), - DECLARE_IRQ(MAX8997_PMICIRQ_RTC1S, PMIC_INT4, 1 << 4), - DECLARE_IRQ(MAX8997_PMICIRQ_WTSR, PMIC_INT4, 1 << 5), - - DECLARE_IRQ(MAX8997_MUICIRQ_ADCError, MUIC_INT1, 1 << 2), - DECLARE_IRQ(MAX8997_MUICIRQ_ADCLow, MUIC_INT1, 1 << 1), - DECLARE_IRQ(MAX8997_MUICIRQ_ADC, MUIC_INT1, 1 << 0), - - DECLARE_IRQ(MAX8997_MUICIRQ_VBVolt, MUIC_INT2, 1 << 4), - DECLARE_IRQ(MAX8997_MUICIRQ_DBChg, MUIC_INT2, 1 << 3), - DECLARE_IRQ(MAX8997_MUICIRQ_DCDTmr, MUIC_INT2, 1 << 2), - DECLARE_IRQ(MAX8997_MUICIRQ_ChgDetRun, MUIC_INT2, 1 << 1), - DECLARE_IRQ(MAX8997_MUICIRQ_ChgTyp, MUIC_INT2, 1 << 0), - - DECLARE_IRQ(MAX8997_MUICIRQ_OVP, MUIC_INT3, 1 << 2), +static struct max8997_irq_data max8997_irqs[] = { + [MAX8997_IRQ_PWRONR] = { + .reg = 1, + .mask = MAX8997_IRQ_PWRONR_MASK, + }, + [MAX8997_IRQ_PWRONF] = { + .reg = 1, + .mask = MAX8997_IRQ_PWRONF_MASK, + }, + [MAX8997_IRQ_PWRON1SEC] = { + .reg = 1, + .mask = MAX8997_IRQ_PWRON1SEC_MASK, + }, + [MAX8997_IRQ_JIGONR] = { + .reg = 1, + .mask = MAX8997_IRQ_JIGONR_MASK, + }, + [MAX8997_IRQ_JIGONF] = { + .reg = 1, + .mask = MAX8997_IRQ_JIGONF_MASK, + }, + [MAX8997_IRQ_LOWBAT2] = { + .reg = 1, + .mask = MAX8997_IRQ_LOWBAT2_MASK, + }, + [MAX8997_IRQ_LOWBAT1] = { + .reg = 1, + .mask = MAX8997_IRQ_LOWBAT1_MASK, + }, + [MAX8997_IRQ_JIGR] = { + .reg = 2, + .mask = MAX8997_IRQ_JIGR_MASK, + }, + [MAX8997_IRQ_JIGF] = { + .reg = 2, + .mask = MAX8997_IRQ_JIGF_MASK, + }, + [MAX8997_IRQ_MR] = { + .reg = 2, + .mask = MAX8997_IRQ_MR_MASK, + }, + [MAX8997_IRQ_DVS1OK] = { + .reg = 2, + .mask = MAX8997_IRQ_DVS1OK_MASK, + }, + [MAX8997_IRQ_DVS2OK] = { + .reg = 2, + .mask = MAX8997_IRQ_DVS2OK_MASK, + }, + [MAX8997_IRQ_DVS4OK] = { + .reg = 2, + .mask = MAX8997_IRQ_DVS4OK_MASK, + }, + [MAX8997_IRQ_DVS5OK] = { + .reg = 2, + .mask = MAX8997_IRQ_DVS5OK_MASK, + }, + [MAX8997_IRQ_CHGINS] = { + .reg = 3, + .mask = MAX8997_IRQ_CHGINS_MASK, + }, + [MAX8997_IRQ_CHGRM] = { + .reg = 3, + .mask = MAX8997_IRQ_CHGRM_MASK, + }, + [MAX8997_IRQ_DCINOVP] = { + .reg = 3, + .mask = MAX8997_IRQ_DCINOVP_MASK, + }, + [MAX8997_IRQ_TOPOFF] = { + .reg = 3, + .mask = MAX8997_IRQ_TOPOFF_MASK, + }, + [MAX8997_IRQ_CHGRSTF] = { + .reg = 3, + .mask = MAX8997_IRQ_CHGRSTF_MASK, + }, + [MAX8997_IRQ_MBCHGTMEXPD] = { + .reg = 3, + .mask = MAX8997_IRQ_MBCHGTMEXPD_MASK, + }, + [MAX8997_IRQ_RTC60S] = { + .reg = 4, + .mask = MAX8997_IRQ_RTC60S_MASK, + }, + [MAX8997_IRQ_RTCA1] = { + .reg = 4, + .mask = MAX8997_IRQ_RTCA1_MASK, + }, + [MAX8997_IRQ_RTCA2] = { + .reg = 4, + .mask = MAX8997_IRQ_RTCA2_MASK, + }, + [MAX8997_IRQ_SMPL_INT] = { + .reg = 4, + .mask = MAX8997_IRQ_SMPL_INT_MASK, + }, + [MAX8997_IRQ_RTC1S] = { + .reg = 4, + .mask = MAX8997_IRQ_RTC1S_MASK, + }, + [MAX8997_IRQ_WTSR] = { + .reg = 4, + .mask = MAX8997_IRQ_WTSR_MASK, + }, + [MAX8997_IRQ_ADC] = { + .reg = 5, + .mask = MAX8997_IRQ_ADC_MASK, + }, + [MAX8997_IRQ_ADCLOW] = { + .reg = 5, + .mask = MAX8997_IRQ_ADCLOW_MASK, + }, + [MAX8997_IRQ_ADCERR] = { + .reg = 5, + .mask = MAX8997_IRQ_ADCERR_MASK, + }, + [MAX8997_IRQ_CHGTYP] = { + .reg = 6, + .mask = MAX8997_IRQ_CHGTYP_MASK, + }, + [MAX8997_IRQ_CHGDETRUN] = { + .reg = 6, + .mask = MAX8997_IRQ_CHGDETRUN_MASK, + }, + [MAX8997_IRQ_DCDTMR] = { + .reg = 6, + .mask = MAX8997_IRQ_DCDTMR_MASK, + }, + [MAX8997_IRQ_DBCHG] = { + .reg = 6, + .mask = MAX8997_IRQ_DCDTMR_MASK, + }, + [MAX8997_IRQ_VBVOLT] = { + .reg = 6, + .mask = MAX8997_IRQ_VBVOLT_MASK, + }, + [MAX8997_IRQ_OVP] = { + .reg = 7, + .mask = MAX8997_IRQ_OVP_MASK, + }, }; +static inline struct max8997_irq_data * +irq_to_max8997_irq(struct max8997_dev *max8997, int irq) +{ + return &max8997_irqs[irq - max8997->irq_base]; +} + static void max8997_irq_lock(struct irq_data *data) { struct max8997_dev *max8997 = irq_get_chip_data(data->irq); @@ -125,216 +194,221 @@ static void max8997_irq_sync_unlock(struct irq_data *data) struct max8997_dev *max8997 = irq_get_chip_data(data->irq); int i; - for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++) { - u8 mask_reg = max8997_mask_reg[i]; - struct i2c_client *i2c = get_i2c(max8997, i); - - if (mask_reg == MAX8997_REG_INVALID || - IS_ERR_OR_NULL(i2c)) - continue; - max8997->irq_masks_cache[i] = max8997->irq_masks_cur[i]; - - max8997_write_reg(i2c, max8997_mask_reg[i], - max8997->irq_masks_cur[i]); + for (i = 0; i < ARRAY_SIZE(max8997->irq_masks_cur); i++) { + /* + * If there's been a change in the mask write it back + * to the hardware. + */ + if (max8997->irq_masks_cur[i] != max8997->irq_masks_cache[i]) { + max8997->irq_masks_cache[i] = max8997->irq_masks_cur[i]; + if (i < MAX8997_NUM_IRQ_PMIC_REGS) + max8997_write_reg(max8997->i2c, + MAX8997_REG_IRQM1 + i, + max8997->irq_masks_cur[i]); + else + max8997_write_reg(max8997->muic, + MAX8997_MUIC_REG_INTMASK1 + i + - MAX8997_NUM_IRQ_PMIC_REGS, + ~max8997->irq_masks_cur[i]); + } } mutex_unlock(&max8997->irqlock); } -static const inline struct max8997_irq_data * -irq_to_max8997_irq(struct max8997_dev *max8997, int irq) -{ - return &max8997_irqs[irq - max8997->irq_base]; -} - -static void max8997_irq_mask(struct irq_data *data) +static void max8997_irq_unmask(struct irq_data *data) { struct max8997_dev *max8997 = irq_get_chip_data(data->irq); - const struct max8997_irq_data *irq_data = irq_to_max8997_irq(max8997, - data->irq); + struct max8997_irq_data *irq_data + = irq_to_max8997_irq(max8997, data->irq); - max8997->irq_masks_cur[irq_data->group] |= irq_data->mask; + max8997->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask; } -static void max8997_irq_unmask(struct irq_data *data) +static void max8997_irq_mask(struct irq_data *data) { struct max8997_dev *max8997 = irq_get_chip_data(data->irq); - const struct max8997_irq_data *irq_data = irq_to_max8997_irq(max8997, - data->irq); + struct max8997_irq_data *irq_data + = irq_to_max8997_irq(max8997, data->irq); - max8997->irq_masks_cur[irq_data->group] &= ~irq_data->mask; + max8997->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask; } static struct irq_chip max8997_irq_chip = { - .name = "max8997", - .irq_bus_lock = max8997_irq_lock, - .irq_bus_sync_unlock = max8997_irq_sync_unlock, - .irq_mask = max8997_irq_mask, - .irq_unmask = max8997_irq_unmask, + .name = "max8997", + .irq_bus_lock = max8997_irq_lock, + .irq_bus_sync_unlock = max8997_irq_sync_unlock, + .irq_mask = max8997_irq_mask, + .irq_unmask = max8997_irq_unmask, }; -#define MAX8997_IRQSRC_PMIC (1 << 1) -#define MAX8997_IRQSRC_FUELGAUGE (1 << 2) -#define MAX8997_IRQSRC_MUIC (1 << 3) -#define MAX8997_IRQSRC_GPIO (1 << 4) -#define MAX8997_IRQSRC_FLASH (1 << 5) static irqreturn_t max8997_irq_thread(int irq, void *data) { struct max8997_dev *max8997 = data; - u8 irq_reg[MAX8997_IRQ_GROUP_NR] = {}; u8 irq_src; + u8 irq_flash; + u8 irq_reg[MAX8997_NUM_IRQ_REGS]; int ret; int i; - ret = max8997_read_reg(max8997->i2c, MAX8997_REG_INTSRC, &irq_src); + ret = max8997_read_reg(max8997->i2c, MAX8997_REG_IRQ_SOURCE, &irq_src); + if (ret < 0) { - dev_err(max8997->dev, "Failed to read interrupt source: %d\n", - ret); + dev_err(max8997->dev, "Failed to read interrupt src" + " register: %d\n", ret); return IRQ_NONE; } - if (irq_src & MAX8997_IRQSRC_PMIC) { - /* PMIC INT1 ~ INT4 */ - max8997_bulk_read(max8997->i2c, MAX8997_REG_INT1, 4, - &irq_reg[PMIC_INT1]); - } - if (irq_src & MAX8997_IRQSRC_FUELGAUGE) { - /* - * TODO: FUEL GAUGE - * - * This is to be supported by Max17042 driver. When - * an interrupt incurs here, it should be relayed to a - * Max17042 device that is connected (probably by - * platform-data). However, we do not have interrupt - * handling in Max17042 driver currently. The Max17042 IRQ - * driver should be ready to be used as a stand-alone device and - * a Max8997-dependent device. Because it is not ready in - * Max17042-side and it is not too critical in operating - * Max8997, we do not implement this in initial releases. - */ - irq_reg[FUEL_GAUGE] = 0; - } - if (irq_src & MAX8997_IRQSRC_MUIC) { - /* MUIC INT1 ~ INT3 */ - max8997_bulk_read(max8997->muic, MAX8997_MUIC_REG_INT1, 3, - &irq_reg[MUIC_INT1]); - } - if (irq_src & MAX8997_IRQSRC_GPIO) { - /* GPIO Interrupt */ - u8 gpio_info[MAX8997_NUM_GPIO]; - - irq_reg[GPIO_LOW] = 0; - irq_reg[GPIO_HI] = 0; - - max8997_bulk_read(max8997->i2c, MAX8997_REG_GPIOCNTL1, - MAX8997_NUM_GPIO, gpio_info); - for (i = 0; i < MAX8997_NUM_GPIO; i++) { - bool interrupt = false; - - switch (gpio_info[i] & MAX8997_GPIO_INT_MASK) { - case MAX8997_GPIO_INT_BOTH: - if (max8997->gpio_status[i] != gpio_info[i]) - interrupt = true; - break; - case MAX8997_GPIO_INT_RISE: - if ((max8997->gpio_status[i] != gpio_info[i]) && - (gpio_info[i] & MAX8997_GPIO_DATA_MASK)) - interrupt = true; - break; - case MAX8997_GPIO_INT_FALL: - if ((max8997->gpio_status[i] != gpio_info[i]) && - !(gpio_info[i] & MAX8997_GPIO_DATA_MASK)) - interrupt = true; - break; - default: - break; - } - - if (interrupt) { - if (i < 8) - irq_reg[GPIO_LOW] |= (1 << i); - else - irq_reg[GPIO_HI] |= (1 << (i - 8)); - } - + dev_info(max8997->dev, "%s: irq:%d, irq_src:0x%x\n", __func__, + irq, irq_src); + + + for (i = 0; i < MAX8997_NUM_IRQ_REGS; i++) + irq_reg[i] = 0x0; + + if (irq_src & MAX8997_INTR_PMIC_MASK) + ret = max8997_bulk_read(max8997->i2c, MAX8997_REG_IRQ1, + MAX8997_NUM_IRQ_PMIC_REGS, irq_reg); + else if (irq_src & MAX8997_INTR_MUIC_MASK) + ret = max8997_bulk_read(max8997->muic, MAX8997_MUIC_REG_INT1, + MAX8997_NUM_IRQ_MUIC_REGS, + &irq_reg[MAX8997_NUM_IRQ_PMIC_REGS]); + else if (irq_src & MAX8997_INTR_FLASH_MASK) { + ret = max8997_read_reg(max8997->i2c, MAX8997_REG_FLASH_STATUS, + &irq_flash); + if (ret < 0) + dev_err(max8997->dev, "%s: fail to read flash" + " status: %d\n", __func__, ret); + else { + dev_info(max8997->dev, "%s: FLASH Interrupt: 0x%02x\n", + __func__, irq_flash); + fled_status = irq_flash & FLED_FLT_MASK; } + } else if (irq_src & MAX8997_INTR_FUELGAUGE_MASK) { + dev_warn(max8997->dev, "%s: Fuel Gauge interrupt\n", __func__); + msleep(20); + } else { + dev_warn(max8997->dev, "Unused interrupt source: 0x%x\n", + irq_src); + return IRQ_NONE; } - if (irq_src & MAX8997_IRQSRC_FLASH) { - /* Flash Status Interrupt */ - ret = max8997_read_reg(max8997->i2c, MAX8997_REG_FLASHSTATUS, - &irq_reg[FLASH_STATUS]); + + if (ret < 0) { + dev_err(max8997->dev, "Failed to read interrupt register: %d\n", + ret); + return IRQ_NONE; } /* Apply masking */ - for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++) + for (i = 0; i < MAX8997_NUM_IRQ_REGS; i++) irq_reg[i] &= ~max8997->irq_masks_cur[i]; /* Report */ for (i = 0; i < MAX8997_IRQ_NR; i++) { - if (irq_reg[max8997_irqs[i].group] & max8997_irqs[i].mask) + if (irq_reg[max8997_irqs[i].reg - 1] & max8997_irqs[i].mask) handle_nested_irq(max8997->irq_base + i); } return IRQ_HANDLED; } -int max8997_irq_resume(struct max8997_dev *max8997) +static ssize_t max8997_show_fled_state(struct device *dev, + struct device_attribute *attr, + char *buf) { - if (max8997->irq && max8997->irq_base) - max8997_irq_thread(max8997->irq_base, max8997); - return 0; + if (fled_status & FLED1_FLT_BIT) + sprintf(buf, "LED1 FAULT\n"); + else if (fled_status & FLED2_FLT_BIT) + sprintf(buf, "LED2 FAULT\n"); + else + sprintf(buf, "LED OK\n"); + + pr_info("%s: %s\n", __func__, buf); + + return strlen(buf); } +static DEVICE_ATTR(fled_state, 0444, max8997_show_fled_state, NULL); + int max8997_irq_init(struct max8997_dev *max8997) { int i; int cur_irq; int ret; - u8 val; + u8 pmic_intm[MAX8997_NUM_IRQ_PMIC_REGS]; + u8 muic_intm[MAX8997_NUM_IRQ_MUIC_REGS]; + u8 pmic_id; + struct device *fled_dev; if (!max8997->irq) { - dev_warn(max8997->dev, "No interrupt specified.\n"); + dev_warn(max8997->dev, + "No interrupt specified, no interrupts\n"); max8997->irq_base = 0; return 0; } if (!max8997->irq_base) { - dev_err(max8997->dev, "No interrupt base specified.\n"); + dev_err(max8997->dev, + "No interrupt base specified, no interrupts\n"); return 0; } - mutex_init(&max8997->irqlock); + for (i = 0; i < MAX8997_NUM_IRQ_PMIC_REGS; i++) + pmic_intm[i] = 0xff; + + for (i = 0; i < MAX8997_NUM_IRQ_MUIC_REGS; i++) + muic_intm[i] = 0x00; - /* Mask individual interrupt sources */ - for (i = 0; i < MAX8997_IRQ_GROUP_NR; i++) { - struct i2c_client *i2c; + mutex_init(&max8997->irqlock); + ret = max8997_read_reg(max8997->i2c, MAX8997_REG_ID, &pmic_id); + if (ret) + dev_err(max8997->dev, "%s: fail to read PMIC ID(0x%X)\n", + __func__, ret); + else + dev_info(max8997->dev, "%s: PMIC ID(0x%X)\n", + __func__, pmic_id); + + /* Mask the individual interrupt sources */ + for (i = 0; i < MAX8997_NUM_IRQ_REGS; i++) { max8997->irq_masks_cur[i] = 0xff; max8997->irq_masks_cache[i] = 0xff; - i2c = get_i2c(max8997, i); + } - if (IS_ERR_OR_NULL(i2c)) - continue; - if (max8997_mask_reg[i] == MAX8997_REG_INVALID) - continue; + ret = max8997_bulk_write(max8997->i2c, MAX8997_REG_IRQM1, + MAX8997_NUM_IRQ_PMIC_REGS, pmic_intm); + if (ret) + dev_err(max8997->dev, "%s: fail to write PMIC IRQM(%d)\n", + __func__, ret); - max8997_write_reg(i2c, max8997_mask_reg[i], 0xff); - } + ret = max8997_bulk_write(max8997->muic, MAX8997_MUIC_REG_INTMASK1, + MAX8997_NUM_IRQ_MUIC_REGS, muic_intm); + if (ret) + dev_err(max8997->dev, "%s: fail to write MUIC IRQM(%d)\n", + __func__, ret); + + /* Enable flash interrupts to debug flash over current issues + * refer MAX8997 I2C Registers: FLASH STATUS control register(0x6D) + * 0x01: FLED1_FLT : FLED1 status read back + * 0x02: FLED2_FLT : FLED2 status read back + */ + ret = max8997_write_reg(max8997->i2c, MAX8997_REG_FLASH_STATUS_MASK, + 0xfc); + if (ret) + dev_err(max8997->dev, "%s: fail to write FLASH IRQM(%d)\n", + __func__, ret); - for (i = 0; i < MAX8997_NUM_GPIO; i++) { - max8997->gpio_status[i] = (max8997_read_reg(max8997->i2c, - MAX8997_REG_GPIOCNTL1 + i, - &val) - & MAX8997_GPIO_DATA_MASK) ? - true : false; - } + fled_dev = device_create(sec_class, NULL, 0, NULL, "sec_fled"); + ret = device_create_file(fled_dev, &dev_attr_fled_state); + if (ret) + dev_err(fled_dev, "failed to create device file(fled_state)\n"); - /* Register with genirq */ + /* register with genirq */ for (i = 0; i < MAX8997_IRQ_NR; i++) { cur_irq = i + max8997->irq_base; irq_set_chip_data(cur_irq, max8997); irq_set_chip_and_handler(cur_irq, &max8997_irq_chip, - handle_edge_irq); + handle_level_irq); irq_set_nested_thread(cur_irq, 1); #ifdef CONFIG_ARM set_irq_flags(cur_irq, IRQF_VALID); @@ -344,25 +418,27 @@ int max8997_irq_init(struct max8997_dev *max8997) } ret = request_threaded_irq(max8997->irq, NULL, max8997_irq_thread, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - "max8997-irq", max8997); - + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "max8997-irq", max8997); if (ret) { dev_err(max8997->dev, "Failed to request IRQ %d: %d\n", - max8997->irq, ret); + max8997->irq, ret); return ret; } if (!max8997->ono) return 0; + dev_dbg(max8997->dev, "%s: ono irq request\n", __func__); ret = request_threaded_irq(max8997->ono, NULL, max8997_irq_thread, - IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | - IRQF_ONESHOT, "max8997-ono", max8997); - - if (ret) - dev_err(max8997->dev, "Failed to request ono-IRQ %d: %d\n", - max8997->ono, ret); + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | + IRQF_ONESHOT, "max8997-ono", max8997); + if (ret) { + dev_err(max8997->dev, "Failed to request IRQ %d: %d\n", + max8997->ono, ret); + return ret; + } + enable_irq_wake(max8997->ono); return 0; } diff --git a/drivers/mfd/max8997.c b/drivers/mfd/max8997.c index 5d1fca0..55d6e8d 100644 --- a/drivers/mfd/max8997.c +++ b/drivers/mfd/max8997.c @@ -1,8 +1,9 @@ /* - * max8997.c - mfd core driver for the Maxim 8966 and 8997 + * max8997.c - mfd core driver for the Maxim 8997 * - * Copyright (C) 2011 Samsung Electronics - * MyungJoo Ham <myungjoo.ham@smasung.com> + * Copyright (C) 2009-2010 Samsung Electronics + * + * based on max8998.c * * 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 @@ -17,31 +18,35 @@ * 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 max8998.c */ +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> #include <linux/slab.h> #include <linux/i2c.h> -#include <linux/pm_runtime.h> +#include <linux/interrupt.h> #include <linux/mutex.h> #include <linux/mfd/core.h> #include <linux/mfd/max8997.h> #include <linux/mfd/max8997-private.h> -#define I2C_ADDR_PMIC (0xCC >> 1) -#define I2C_ADDR_MUIC (0x4A >> 1) -#define I2C_ADDR_BATTERY (0x6C >> 1) -#define I2C_ADDR_RTC (0x0C >> 1) -#define I2C_ADDR_HAPTIC (0x90 >> 1) +#define RTC_I2C_ADDR (0x0c >> 1) +#define MUIC_I2C_ADDR (0x4a >> 1) +#define HMOTOR_I2C_ADDR (0x90 >> 1) static struct mfd_cell max8997_devs[] = { - { .name = "max8997-pmic", }, - { .name = "max8997-rtc", }, - { .name = "max8997-battery", }, - { .name = "max8997-haptic", }, - { .name = "max8997-muic", }, - { .name = "max8997-flash", }, + { + .name = "max8997-pmic", + }, { + .name = "max8997-charger", + }, { + .name = "max8997-rtc", + }, { + .name = "max8997-muic", + }, { + .name = "max8997-hapticmotor", + }, }; int max8997_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) @@ -59,7 +64,7 @@ int max8997_read_reg(struct i2c_client *i2c, u8 reg, u8 *dest) *dest = ret; return 0; } -EXPORT_SYMBOL_GPL(max8997_read_reg); +EXPORT_SYMBOL(max8997_read_reg); int max8997_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) { @@ -74,7 +79,7 @@ int max8997_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) return 0; } -EXPORT_SYMBOL_GPL(max8997_bulk_read); +EXPORT_SYMBOL(max8997_bulk_read); int max8997_write_reg(struct i2c_client *i2c, u8 reg, u8 value) { @@ -86,7 +91,7 @@ int max8997_write_reg(struct i2c_client *i2c, u8 reg, u8 value) mutex_unlock(&max8997->iolock); return ret; } -EXPORT_SYMBOL_GPL(max8997_write_reg); +EXPORT_SYMBOL(max8997_write_reg); int max8997_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) { @@ -101,7 +106,7 @@ int max8997_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) return 0; } -EXPORT_SYMBOL_GPL(max8997_bulk_write); +EXPORT_SYMBOL(max8997_bulk_write); int max8997_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) { @@ -118,13 +123,13 @@ int max8997_update_reg(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) mutex_unlock(&max8997->iolock); return ret; } -EXPORT_SYMBOL_GPL(max8997_update_reg); +EXPORT_SYMBOL(max8997_update_reg); static int max8997_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { - struct max8997_dev *max8997; struct max8997_platform_data *pdata = i2c->dev.platform_data; + struct max8997_dev *max8997; int ret = 0; max8997 = kzalloc(sizeof(struct max8997_dev), GFP_KERNEL); @@ -134,44 +139,39 @@ static int max8997_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, max8997); max8997->dev = &i2c->dev; max8997->i2c = i2c; + max8997->irq = i2c->irq; max8997->type = id->driver_data; - - if (!pdata) - goto err; - - max8997->wakeup = pdata->wakeup; - + if (pdata) { + max8997->ono = pdata->ono; + max8997->irq_base = pdata->irq_base; + max8997->wakeup = pdata->wakeup; + } mutex_init(&max8997->iolock); - max8997->rtc = i2c_new_dummy(i2c->adapter, I2C_ADDR_RTC); + max8997->rtc = i2c_new_dummy(i2c->adapter, RTC_I2C_ADDR); i2c_set_clientdata(max8997->rtc, max8997); - max8997->haptic = i2c_new_dummy(i2c->adapter, I2C_ADDR_HAPTIC); - i2c_set_clientdata(max8997->haptic, max8997); - max8997->muic = i2c_new_dummy(i2c->adapter, I2C_ADDR_MUIC); - i2c_set_clientdata(max8997->muic, max8997); - pm_runtime_set_active(max8997->dev); + max8997->muic = i2c_new_dummy(i2c->adapter, MUIC_I2C_ADDR); + i2c_set_clientdata(max8997->muic, max8997); - mfd_add_devices(max8997->dev, -1, max8997_devs, - ARRAY_SIZE(max8997_devs), - NULL, 0); + max8997->hmotor = i2c_new_dummy(i2c->adapter, HMOTOR_I2C_ADDR); + i2c_set_clientdata(max8997->hmotor, max8997); - /* - * TODO: enable others (flash, muic, rtc, battery, ...) and - * check the return value - */ + max8997_irq_init(max8997); + ret = mfd_add_devices(max8997->dev, -1, + max8997_devs, ARRAY_SIZE(max8997_devs), + NULL, 0); if (ret < 0) - goto err_mfd; + goto err; + return ret; -err_mfd: +err: mfd_remove_devices(max8997->dev); - i2c_unregister_device(max8997->muic); - i2c_unregister_device(max8997->haptic); + max8997_irq_exit(max8997); i2c_unregister_device(max8997->rtc); -err: kfree(max8997); return ret; } @@ -181,8 +181,7 @@ static int max8997_i2c_remove(struct i2c_client *i2c) struct max8997_dev *max8997 = i2c_get_clientdata(i2c); mfd_remove_devices(max8997->dev); - i2c_unregister_device(max8997->muic); - i2c_unregister_device(max8997->haptic); + max8997_irq_exit(max8997); i2c_unregister_device(max8997->rtc); kfree(max8997); @@ -191,211 +190,44 @@ static int max8997_i2c_remove(struct i2c_client *i2c) static const struct i2c_device_id max8997_i2c_id[] = { { "max8997", TYPE_MAX8997 }, - { "max8966", TYPE_MAX8966 }, { } }; -MODULE_DEVICE_TABLE(i2c, max8998_i2c_id); - -u8 max8997_dumpaddr_pmic[] = { - MAX8997_REG_INT1MSK, - MAX8997_REG_INT2MSK, - MAX8997_REG_INT3MSK, - MAX8997_REG_INT4MSK, - MAX8997_REG_MAINCON1, - MAX8997_REG_MAINCON2, - MAX8997_REG_BUCKRAMP, - MAX8997_REG_BUCK1CTRL, - MAX8997_REG_BUCK1DVS1, - MAX8997_REG_BUCK1DVS2, - MAX8997_REG_BUCK1DVS3, - MAX8997_REG_BUCK1DVS4, - MAX8997_REG_BUCK1DVS5, - MAX8997_REG_BUCK1DVS6, - MAX8997_REG_BUCK1DVS7, - MAX8997_REG_BUCK1DVS8, - MAX8997_REG_BUCK2CTRL, - MAX8997_REG_BUCK2DVS1, - MAX8997_REG_BUCK2DVS2, - MAX8997_REG_BUCK2DVS3, - MAX8997_REG_BUCK2DVS4, - MAX8997_REG_BUCK2DVS5, - MAX8997_REG_BUCK2DVS6, - MAX8997_REG_BUCK2DVS7, - MAX8997_REG_BUCK2DVS8, - MAX8997_REG_BUCK3CTRL, - MAX8997_REG_BUCK3DVS, - MAX8997_REG_BUCK4CTRL, - MAX8997_REG_BUCK4DVS, - MAX8997_REG_BUCK5CTRL, - MAX8997_REG_BUCK5DVS1, - MAX8997_REG_BUCK5DVS2, - MAX8997_REG_BUCK5DVS3, - MAX8997_REG_BUCK5DVS4, - MAX8997_REG_BUCK5DVS5, - MAX8997_REG_BUCK5DVS6, - MAX8997_REG_BUCK5DVS7, - MAX8997_REG_BUCK5DVS8, - MAX8997_REG_BUCK6CTRL, - MAX8997_REG_BUCK6BPSKIPCTRL, - MAX8997_REG_BUCK7CTRL, - MAX8997_REG_BUCK7DVS, - MAX8997_REG_LDO1CTRL, - MAX8997_REG_LDO2CTRL, - MAX8997_REG_LDO3CTRL, - MAX8997_REG_LDO4CTRL, - MAX8997_REG_LDO5CTRL, - MAX8997_REG_LDO6CTRL, - MAX8997_REG_LDO7CTRL, - MAX8997_REG_LDO8CTRL, - MAX8997_REG_LDO9CTRL, - MAX8997_REG_LDO10CTRL, - MAX8997_REG_LDO11CTRL, - MAX8997_REG_LDO12CTRL, - MAX8997_REG_LDO13CTRL, - MAX8997_REG_LDO14CTRL, - MAX8997_REG_LDO15CTRL, - MAX8997_REG_LDO16CTRL, - MAX8997_REG_LDO17CTRL, - MAX8997_REG_LDO18CTRL, - MAX8997_REG_LDO21CTRL, - MAX8997_REG_MBCCTRL1, - MAX8997_REG_MBCCTRL2, - MAX8997_REG_MBCCTRL3, - MAX8997_REG_MBCCTRL4, - MAX8997_REG_MBCCTRL5, - MAX8997_REG_MBCCTRL6, - MAX8997_REG_OTPCGHCVS, - MAX8997_REG_SAFEOUTCTRL, - MAX8997_REG_LBCNFG1, - MAX8997_REG_LBCNFG2, - MAX8997_REG_BBCCTRL, - - MAX8997_REG_FLASH1_CUR, - MAX8997_REG_FLASH2_CUR, - MAX8997_REG_MOVIE_CUR, - MAX8997_REG_GSMB_CUR, - MAX8997_REG_BOOST_CNTL, - MAX8997_REG_LEN_CNTL, - MAX8997_REG_FLASH_CNTL, - MAX8997_REG_WDT_CNTL, - MAX8997_REG_MAXFLASH1, - MAX8997_REG_MAXFLASH2, - MAX8997_REG_FLASHSTATUSMASK, - - MAX8997_REG_GPIOCNTL1, - MAX8997_REG_GPIOCNTL2, - MAX8997_REG_GPIOCNTL3, - MAX8997_REG_GPIOCNTL4, - MAX8997_REG_GPIOCNTL5, - MAX8997_REG_GPIOCNTL6, - MAX8997_REG_GPIOCNTL7, - MAX8997_REG_GPIOCNTL8, - MAX8997_REG_GPIOCNTL9, - MAX8997_REG_GPIOCNTL10, - MAX8997_REG_GPIOCNTL11, - MAX8997_REG_GPIOCNTL12, - - MAX8997_REG_LDO1CONFIG, - MAX8997_REG_LDO2CONFIG, - MAX8997_REG_LDO3CONFIG, - MAX8997_REG_LDO4CONFIG, - MAX8997_REG_LDO5CONFIG, - MAX8997_REG_LDO6CONFIG, - MAX8997_REG_LDO7CONFIG, - MAX8997_REG_LDO8CONFIG, - MAX8997_REG_LDO9CONFIG, - MAX8997_REG_LDO10CONFIG, - MAX8997_REG_LDO11CONFIG, - MAX8997_REG_LDO12CONFIG, - MAX8997_REG_LDO13CONFIG, - MAX8997_REG_LDO14CONFIG, - MAX8997_REG_LDO15CONFIG, - MAX8997_REG_LDO16CONFIG, - MAX8997_REG_LDO17CONFIG, - MAX8997_REG_LDO18CONFIG, - MAX8997_REG_LDO21CONFIG, - - MAX8997_REG_DVSOKTIMER1, - MAX8997_REG_DVSOKTIMER2, - MAX8997_REG_DVSOKTIMER4, - MAX8997_REG_DVSOKTIMER5, -}; +MODULE_DEVICE_TABLE(i2c, max8997_i2c_id); -u8 max8997_dumpaddr_muic[] = { - MAX8997_MUIC_REG_INTMASK1, - MAX8997_MUIC_REG_INTMASK2, - MAX8997_MUIC_REG_INTMASK3, - MAX8997_MUIC_REG_CDETCTRL, - MAX8997_MUIC_REG_CONTROL1, - MAX8997_MUIC_REG_CONTROL2, - MAX8997_MUIC_REG_CONTROL3, -}; - -u8 max8997_dumpaddr_haptic[] = { - MAX8997_HAPTIC_REG_CONF1, - MAX8997_HAPTIC_REG_CONF2, - MAX8997_HAPTIC_REG_DRVCONF, - MAX8997_HAPTIC_REG_CYCLECONF1, - MAX8997_HAPTIC_REG_CYCLECONF2, - MAX8997_HAPTIC_REG_SIGCONF1, - MAX8997_HAPTIC_REG_SIGCONF2, - MAX8997_HAPTIC_REG_SIGCONF3, - MAX8997_HAPTIC_REG_SIGCONF4, - MAX8997_HAPTIC_REG_SIGDC1, - MAX8997_HAPTIC_REG_SIGDC2, - MAX8997_HAPTIC_REG_SIGPWMDC1, - MAX8997_HAPTIC_REG_SIGPWMDC2, - MAX8997_HAPTIC_REG_SIGPWMDC3, - MAX8997_HAPTIC_REG_SIGPWMDC4, -}; - -static int max8997_freeze(struct device *dev) +#ifdef CONFIG_PM +static int max8997_suspend(struct device *dev) { struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); struct max8997_dev *max8997 = i2c_get_clientdata(i2c); - int i; - for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_pmic); i++) - max8997_read_reg(i2c, max8997_dumpaddr_pmic[i], - &max8997->reg_dump[i]); + if (max8997->wakeup) + enable_irq_wake(max8997->irq); - for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_muic); i++) - max8997_read_reg(i2c, max8997_dumpaddr_muic[i], - &max8997->reg_dump[i + MAX8997_REG_PMIC_END]); - - for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_haptic); i++) - max8997_read_reg(i2c, max8997_dumpaddr_haptic[i], - &max8997->reg_dump[i + MAX8997_REG_PMIC_END + - MAX8997_MUIC_REG_END]); + disable_irq(max8997->irq); return 0; } -static int max8997_restore(struct device *dev) +static int max8997_resume(struct device *dev) { struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); struct max8997_dev *max8997 = i2c_get_clientdata(i2c); - int i; - - for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_pmic); i++) - max8997_write_reg(i2c, max8997_dumpaddr_pmic[i], - max8997->reg_dump[i]); - for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_muic); i++) - max8997_write_reg(i2c, max8997_dumpaddr_muic[i], - max8997->reg_dump[i + MAX8997_REG_PMIC_END]); + if (max8997->wakeup) + disable_irq_wake(max8997->irq); - for (i = 0; i < ARRAY_SIZE(max8997_dumpaddr_haptic); i++) - max8997_write_reg(i2c, max8997_dumpaddr_haptic[i], - max8997->reg_dump[i + MAX8997_REG_PMIC_END + - MAX8997_MUIC_REG_END]); + enable_irq(max8997->irq); return 0; } +#else +#define max8997_suspend NULL +#define max8997_resume NULL +#endif /* CONFIG_PM */ const struct dev_pm_ops max8997_pm = { - .freeze = max8997_freeze, - .restore = max8997_restore, + .suspend = max8997_suspend, + .resume = max8997_resume, }; static struct i2c_driver max8997_i2c_driver = { @@ -423,5 +255,5 @@ static void __exit max8997_i2c_exit(void) module_exit(max8997_i2c_exit); MODULE_DESCRIPTION("MAXIM 8997 multi-function core driver"); -MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); +MODULE_AUTHOR("<ms925.kim@samsung.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/s5m-core.c b/drivers/mfd/s5m-core.c new file mode 100644 index 0000000..fa947ab --- /dev/null +++ b/drivers/mfd/s5m-core.c @@ -0,0 +1,249 @@ +/* + * s5m87xx.c + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd + * http://www.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. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/pm_runtime.h> +#include <linux/mutex.h> +#include <linux/gpio.h> +#include <linux/mfd/core.h> +#include <linux/mfd/s5m87xx/s5m-core.h> +#include <linux/mfd/s5m87xx/s5m-pmic.h> +#include <linux/mfd/s5m87xx/s5m-rtc.h> + +static struct mfd_cell s5m87xx_devs[] = { + { + .name = "s5m8767-pmic", + }, { + .name = "s5m8763-pmic", + }, { + .name = "s5m-rtc", + }, +}; + +int s5m_reg_read(struct i2c_client *i2c, u8 reg, u8 *dest) +{ + struct s5m87xx_dev *s5m87xx = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s5m87xx->iolock); + ret = i2c_smbus_read_byte_data(i2c, reg); + mutex_unlock(&s5m87xx->iolock); + if (ret < 0) + return ret; + + ret &= 0xff; + *dest = ret; + return 0; +} +EXPORT_SYMBOL(s5m_reg_read); + +int s5m_bulk_read(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct s5m87xx_dev *s5m87xx = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s5m87xx->iolock); + ret = i2c_smbus_read_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&s5m87xx->iolock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(s5m_bulk_read); + +int s5m_reg_write(struct i2c_client *i2c, u8 reg, u8 value) +{ + struct s5m87xx_dev *s5m87xx = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s5m87xx->iolock); + ret = i2c_smbus_write_byte_data(i2c, reg, value); + mutex_unlock(&s5m87xx->iolock); + return ret; +} +EXPORT_SYMBOL(s5m_reg_write); + +int s5m_bulk_write(struct i2c_client *i2c, u8 reg, int count, u8 *buf) +{ + struct s5m87xx_dev *s5m87xx = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s5m87xx->iolock); + ret = i2c_smbus_write_i2c_block_data(i2c, reg, count, buf); + mutex_unlock(&s5m87xx->iolock); + if (ret < 0) + return ret; + + return 0; +} +EXPORT_SYMBOL(s5m_bulk_write); + +int s5m_reg_update(struct i2c_client *i2c, u8 reg, u8 val, u8 mask) +{ + struct s5m87xx_dev *s5m87xx = i2c_get_clientdata(i2c); + int ret; + + mutex_lock(&s5m87xx->iolock); + ret = i2c_smbus_read_byte_data(i2c, reg); + if (ret >= 0) { + u8 old_val = ret & 0xff; + u8 new_val = (val & mask) | (old_val & (~mask)); + ret = i2c_smbus_write_byte_data(i2c, reg, new_val); + } + mutex_unlock(&s5m87xx->iolock); + return ret; +} +EXPORT_SYMBOL(s5m_reg_update); + +static int s5m87xx_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct s5m_platform_data *pdata = i2c->dev.platform_data; + struct s5m87xx_dev *s5m87xx; + int ret = 0; + + s5m87xx = kzalloc(sizeof(struct s5m87xx_dev), GFP_KERNEL); + if (s5m87xx == NULL) + return -ENOMEM; + + i2c_set_clientdata(i2c, s5m87xx); + s5m87xx->dev = &i2c->dev; + s5m87xx->i2c = i2c; + s5m87xx->irq = gpio_to_irq(pdata->irq_gpio); + s5m87xx->type = id->driver_data; + + if (pdata) { + s5m87xx->device_type = pdata->device_type; + s5m87xx->ono = pdata->ono; + s5m87xx->irq_base = pdata->irq_base; + s5m87xx->wakeup = pdata->wakeup; + s5m87xx->wtsr_smpl = pdata->wtsr_smpl; + } + + mutex_init(&s5m87xx->iolock); + + s5m87xx->rtc = i2c_new_dummy(i2c->adapter, RTC_I2C_ADDR); + i2c_set_clientdata(s5m87xx->rtc, s5m87xx); + + if (pdata && pdata->cfg_pmic_irq) + pdata->cfg_pmic_irq(); + + s5m_irq_init(s5m87xx); + + pm_runtime_set_active(s5m87xx->dev); + + ret = mfd_add_devices(s5m87xx->dev, -1, + s5m87xx_devs, ARRAY_SIZE(s5m87xx_devs), + NULL, 0); + + if (ret < 0) + goto err; + + dev_info(s5m87xx->dev ,"S5M87xx MFD probe done!!! \n"); + return ret; + +err: + mfd_remove_devices(s5m87xx->dev); + s5m_irq_exit(s5m87xx); + i2c_unregister_device(s5m87xx->rtc); + kfree(s5m87xx); + return ret; +} + +static int s5m87xx_i2c_remove(struct i2c_client *i2c) +{ + struct s5m87xx_dev *s5m87xx = i2c_get_clientdata(i2c); + + mfd_remove_devices(s5m87xx->dev); + s5m_irq_exit(s5m87xx); + i2c_unregister_device(s5m87xx->rtc); + kfree(s5m87xx); + + return 0; +} + +static const struct i2c_device_id s5m87xx_i2c_id[] = { + { "s5m87xx", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, s5m87xx_i2c_id); + +#ifdef CONFIG_PM +static int s5m_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct s5m87xx_dev *s5m87xx = i2c_get_clientdata(i2c); + + if (s5m87xx->wakeup) + enable_irq_wake(s5m87xx->irq); + + disable_irq(s5m87xx->irq); + + return 0; +} + +static int s5m_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct s5m87xx_dev *s5m87xx = i2c_get_clientdata(i2c); + + if (s5m87xx->wakeup) + disable_irq_wake(s5m87xx->irq); + + enable_irq(s5m87xx->irq); + + return 0; +} +#else +#define s5m_suspend NULL +#define s5m_resume NULL +#endif /* CONFIG_PM */ + +const struct dev_pm_ops s5m87xx_apm = { + .suspend = s5m_suspend, + .resume = s5m_resume, +}; + +static struct i2c_driver s5m87xx_i2c_driver = { + .driver = { + .name = "s5m87xx", + .owner = THIS_MODULE, + .pm = &s5m87xx_apm, + }, + .probe = s5m87xx_i2c_probe, + .remove = s5m87xx_i2c_remove, + .id_table = s5m87xx_i2c_id, +}; + +static int __init s5m87xx_i2c_init(void) +{ + return i2c_add_driver(&s5m87xx_i2c_driver); +} + +subsys_initcall(s5m87xx_i2c_init); + +static void __exit s5m87xx_i2c_exit(void) +{ + i2c_del_driver(&s5m87xx_i2c_driver); +} +module_exit(s5m87xx_i2c_exit); + +MODULE_AUTHOR("Sangbeom Kim <sbkim73@samsung.com>"); +MODULE_DESCRIPTION("Core support for the S5M87XX MFD"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/s5m-irq.c b/drivers/mfd/s5m-irq.c new file mode 100644 index 0000000..dbf1b7a --- /dev/null +++ b/drivers/mfd/s5m-irq.c @@ -0,0 +1,487 @@ +/* + * s5m87xx-irq.c + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd + * http://www.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. + * + */ + +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/mfd/s5m87xx/s5m-core.h> + +struct s5m_irq_data { + int reg; + int mask; +}; + +static struct s5m_irq_data s5m8767_irqs[] = { + [S5M8767_IRQ_PWRR] = { + .reg = 1, + .mask = S5M8767_IRQ_PWRR_MASK, + }, + [S5M8767_IRQ_PWRF] = { + .reg = 1, + .mask = S5M8767_IRQ_PWRF_MASK, + }, + [S5M8767_IRQ_PWR1S] = { + .reg = 1, + .mask = S5M8767_IRQ_PWR1S_MASK, + }, + [S5M8767_IRQ_JIGR] = { + .reg = 1, + .mask = S5M8767_IRQ_JIGR_MASK, + }, + [S5M8767_IRQ_JIGF] = { + .reg = 1, + .mask = S5M8767_IRQ_JIGF_MASK, + }, + [S5M8767_IRQ_LOWBAT2] = { + .reg = 1, + .mask = S5M8767_IRQ_LOWBAT2_MASK, + }, + [S5M8767_IRQ_LOWBAT1] = { + .reg = 1, + .mask = S5M8767_IRQ_LOWBAT1_MASK, + }, + [S5M8767_IRQ_MRB] = { + .reg = 2, + .mask = S5M8767_IRQ_MRB_MASK, + }, + [S5M8767_IRQ_DVSOK2] = { + .reg = 2, + .mask = S5M8767_IRQ_DVSOK2_MASK, + }, + [S5M8767_IRQ_DVSOK3] = { + .reg = 2, + .mask = S5M8767_IRQ_DVSOK3_MASK, + }, + [S5M8767_IRQ_DVSOK4] = { + .reg = 2, + .mask = S5M8767_IRQ_DVSOK4_MASK, + }, + [S5M8767_IRQ_RTC60S] = { + .reg = 3, + .mask = S5M8767_IRQ_RTC60S_MASK, + }, + [S5M8767_IRQ_RTCA1] = { + .reg = 3, + .mask = S5M8767_IRQ_RTCA1_MASK, + }, + [S5M8767_IRQ_RTCA2] = { + .reg = 3, + .mask = S5M8767_IRQ_RTCA2_MASK, + }, + [S5M8767_IRQ_SMPL] = { + .reg = 3, + .mask = S5M8767_IRQ_SMPL_MASK, + }, + [S5M8767_IRQ_RTC1S] = { + .reg = 3, + .mask = S5M8767_IRQ_RTC1S_MASK, + }, + [S5M8767_IRQ_WTSR] = { + .reg = 3, + .mask = S5M8767_IRQ_WTSR_MASK, + }, +}; + +static struct s5m_irq_data s5m8763_irqs[] = { + [S5M8763_IRQ_DCINF] = { + .reg = 1, + .mask = S5M8763_IRQ_DCINF_MASK, + }, + [S5M8763_IRQ_DCINR] = { + .reg = 1, + .mask = S5M8763_IRQ_DCINR_MASK, + }, + [S5M8763_IRQ_JIGF] = { + .reg = 1, + .mask = S5M8763_IRQ_JIGF_MASK, + }, + [S5M8763_IRQ_JIGR] = { + .reg = 1, + .mask = S5M8763_IRQ_JIGR_MASK, + }, + [S5M8763_IRQ_PWRONF] = { + .reg = 1, + .mask = S5M8763_IRQ_PWRONF_MASK, + }, + [S5M8763_IRQ_PWRONR] = { + .reg = 1, + .mask = S5M8763_IRQ_PWRONR_MASK, + }, + [S5M8763_IRQ_WTSREVNT] = { + .reg = 2, + .mask = S5M8763_IRQ_WTSREVNT_MASK, + }, + [S5M8763_IRQ_SMPLEVNT] = { + .reg = 2, + .mask = S5M8763_IRQ_SMPLEVNT_MASK, + }, + [S5M8763_IRQ_ALARM1] = { + .reg = 2, + .mask = S5M8763_IRQ_ALARM1_MASK, + }, + [S5M8763_IRQ_ALARM0] = { + .reg = 2, + .mask = S5M8763_IRQ_ALARM0_MASK, + }, + [S5M8763_IRQ_ONKEY1S] = { + .reg = 3, + .mask = S5M8763_IRQ_ONKEY1S_MASK, + }, + [S5M8763_IRQ_TOPOFFR] = { + .reg = 3, + .mask = S5M8763_IRQ_TOPOFFR_MASK, + }, + [S5M8763_IRQ_DCINOVPR] = { + .reg = 3, + .mask = S5M8763_IRQ_DCINOVPR_MASK, + }, + [S5M8763_IRQ_CHGRSTF] = { + .reg = 3, + .mask = S5M8763_IRQ_CHGRSTF_MASK, + }, + [S5M8763_IRQ_DONER] = { + .reg = 3, + .mask = S5M8763_IRQ_DONER_MASK, + }, + [S5M8763_IRQ_CHGFAULT] = { + .reg = 3, + .mask = S5M8763_IRQ_CHGFAULT_MASK, + }, + [S5M8763_IRQ_LOBAT1] = { + .reg = 4, + .mask = S5M8763_IRQ_LOBAT1_MASK, + }, + [S5M8763_IRQ_LOBAT2] = { + .reg = 4, + .mask = S5M8763_IRQ_LOBAT2_MASK, + }, +}; + +static inline struct s5m_irq_data * +irq_to_s5m8767_irq(struct s5m87xx_dev *s5m87xx, int irq) +{ + return &s5m8767_irqs[irq - s5m87xx->irq_base]; +} + +static void s5m8767_irq_lock(struct irq_data *data) +{ + struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data); + + mutex_lock(&s5m87xx->irqlock); +} + +static void s5m8767_irq_sync_unlock(struct irq_data *data) +{ + struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data); + int i; + + for (i = 0; i < ARRAY_SIZE(s5m87xx->irq_masks_cur); i++) { + if (s5m87xx->irq_masks_cur[i] != s5m87xx->irq_masks_cache[i]) { + s5m87xx->irq_masks_cache[i] = s5m87xx->irq_masks_cur[i]; + s5m_reg_write(s5m87xx->i2c, S5M8767_REG_INT1M + i, + s5m87xx->irq_masks_cur[i]); + } + } + + mutex_unlock(&s5m87xx->irqlock); +} + +static void s5m8767_irq_unmask(struct irq_data *data) +{ + struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data); + struct s5m_irq_data *irq_data = irq_to_s5m8767_irq(s5m87xx, + data->irq); + + s5m87xx->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask; +} + +static void s5m8767_irq_mask(struct irq_data *data) +{ + struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data); + struct s5m_irq_data *irq_data = irq_to_s5m8767_irq(s5m87xx, + data->irq); + + s5m87xx->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask; +} + +static struct irq_chip s5m8767_irq_chip = { + .name = "s5m8767", + .irq_bus_lock = s5m8767_irq_lock, + .irq_bus_sync_unlock = s5m8767_irq_sync_unlock, + .irq_mask = s5m8767_irq_mask, + .irq_unmask = s5m8767_irq_unmask, +}; + +static inline struct s5m_irq_data * +irq_to_s5m8763_irq(struct s5m87xx_dev *s5m87xx, int irq) +{ + return &s5m8763_irqs[irq - s5m87xx->irq_base]; +} + +static void s5m8763_irq_lock(struct irq_data *data) +{ + struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data); + + mutex_lock(&s5m87xx->irqlock); +} + +static void s5m8763_irq_sync_unlock(struct irq_data *data) +{ + struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data); + int i; + + for (i = 0; i < ARRAY_SIZE(s5m87xx->irq_masks_cur); i++) { + if (s5m87xx->irq_masks_cur[i] != s5m87xx->irq_masks_cache[i]) { + s5m87xx->irq_masks_cache[i] = s5m87xx->irq_masks_cur[i]; + s5m_reg_write(s5m87xx->i2c, S5M8763_REG_IRQM1 + i, + s5m87xx->irq_masks_cur[i]); + } + } + + mutex_unlock(&s5m87xx->irqlock); +} + +static void s5m8763_irq_unmask(struct irq_data *data) +{ + struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data); + struct s5m_irq_data *irq_data = irq_to_s5m8763_irq(s5m87xx, + data->irq); + + s5m87xx->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask; +} + +static void s5m8763_irq_mask(struct irq_data *data) +{ + struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data); + struct s5m_irq_data *irq_data = irq_to_s5m8763_irq(s5m87xx, + data->irq); + + s5m87xx->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask; +} + +static struct irq_chip s5m8763_irq_chip = { + .name = "s5m8763", + .irq_bus_lock = s5m8763_irq_lock, + .irq_bus_sync_unlock = s5m8763_irq_sync_unlock, + .irq_mask = s5m8763_irq_mask, + .irq_unmask = s5m8763_irq_unmask, +}; + + +static irqreturn_t s5m8767_irq_thread(int irq, void *data) +{ + struct s5m87xx_dev *s5m87xx = data; + u8 irq_reg[NUM_IRQ_REGS-1]; + int ret; + int i; + + + ret = s5m_bulk_read(s5m87xx->i2c, S5M8767_REG_INT1, + NUM_IRQ_REGS - 1, irq_reg); + if (ret < 0) { + dev_err(s5m87xx->dev, "Failed to read interrupt register: %d\n", + ret); + return IRQ_NONE; + } + + for (i = 0; i < NUM_IRQ_REGS - 1; i++) + irq_reg[i] &= ~s5m87xx->irq_masks_cur[i]; + + for (i = 0; i < S5M8767_IRQ_NR; i++) { + if (irq_reg[s5m8767_irqs[i].reg - 1] & s5m8767_irqs[i].mask) + handle_nested_irq(s5m87xx->irq_base + i); + } + + return IRQ_HANDLED; +} + +static irqreturn_t s5m8763_irq_thread(int irq, void *data) +{ + struct s5m87xx_dev *s5m87xx = data; + u8 irq_reg[NUM_IRQ_REGS]; + int ret; + int i; + + ret = s5m_bulk_read(s5m87xx->i2c, S5M8763_REG_IRQ1, + NUM_IRQ_REGS, irq_reg); + if (ret < 0) { + dev_err(s5m87xx->dev, "Failed to read interrupt register: %d\n", + ret); + return IRQ_NONE; + } + + for (i = 0; i < NUM_IRQ_REGS; i++) + irq_reg[i] &= ~s5m87xx->irq_masks_cur[i]; + + for (i = 0; i < S5M8763_IRQ_NR; i++) { + if (irq_reg[s5m8763_irqs[i].reg - 1] & s5m8763_irqs[i].mask) + handle_nested_irq(s5m87xx->irq_base + i); + } + + return IRQ_HANDLED; +} + +int s5m_irq_resume(struct s5m87xx_dev *s5m87xx) +{ + if (s5m87xx->irq && s5m87xx->irq_base){ + switch (s5m87xx->device_type) { + case S5M8763X: + s5m8763_irq_thread(s5m87xx->irq_base, s5m87xx); + break; + case S5M8767X: + s5m8767_irq_thread(s5m87xx->irq_base, s5m87xx); + break; + default: + break; + + } + } + return 0; +} + +int s5m_irq_init(struct s5m87xx_dev *s5m87xx) +{ + int i; + int cur_irq; + int ret = 0; + int type = s5m87xx->device_type; + + if (!s5m87xx->irq) { + dev_warn(s5m87xx->dev, + "No interrupt specified, no interrupts\n"); + s5m87xx->irq_base = 0; + return 0; + } + + if (!s5m87xx->irq_base) { + dev_err(s5m87xx->dev, + "No interrupt base specified, no interrupts\n"); + return 0; + } + + mutex_init(&s5m87xx->irqlock); + + switch (type) { + case S5M8763X: + for (i = 0; i < NUM_IRQ_REGS; i++) { + s5m87xx->irq_masks_cur[i] = 0xff; + s5m87xx->irq_masks_cache[i] = 0xff; + s5m_reg_write(s5m87xx->i2c, S5M8763_REG_IRQM1 + i, + 0xff); + } + + s5m_reg_write(s5m87xx->i2c, S5M8763_REG_STATUSM1, 0xff); + s5m_reg_write(s5m87xx->i2c, S5M8763_REG_STATUSM2, 0xff); + + for (i = 0; i < S5M8763_IRQ_NR; i++) { + cur_irq = i + s5m87xx->irq_base; + irq_set_chip_data(cur_irq, s5m87xx); + irq_set_chip_and_handler(cur_irq, &s5m8763_irq_chip, + handle_edge_irq); + irq_set_nested_thread(cur_irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + irq_set_noprobe(cur_irq); +#endif + } + + ret = request_threaded_irq(s5m87xx->irq, NULL, + s5m8763_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "s5m87xx-irq", s5m87xx); + if (ret) { + dev_err(s5m87xx->dev, "Failed to request IRQ %d: %d\n", + s5m87xx->irq, ret); + return ret; + } + break; + case S5M8767X: + for (i = 0; i < NUM_IRQ_REGS - 1; i++) { + s5m87xx->irq_masks_cur[i] = 0xff; + s5m87xx->irq_masks_cache[i] = 0xff; + s5m_reg_write(s5m87xx->i2c, S5M8767_REG_INT1M + i, + 0xff); + } + for (i = 0; i < S5M8767_IRQ_NR; i++) { + cur_irq = i + s5m87xx->irq_base; + ret = irq_set_chip_data(cur_irq, s5m87xx); + if (ret) { + dev_err(s5m87xx->dev, + "Failed to irq_set_chip_data %d: %d\n", + s5m87xx->irq, ret); + return ret; + } + + irq_set_chip_and_handler(cur_irq, &s5m8767_irq_chip, + handle_edge_irq); + irq_set_nested_thread(cur_irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + irq_set_noprobe(cur_irq); +#endif + } + + ret = request_threaded_irq(s5m87xx->irq, NULL, + s5m8767_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "s5m87xx-irq", s5m87xx); + if (ret) { + dev_err(s5m87xx->dev, "Failed to request IRQ %d: %d\n", + s5m87xx->irq, ret); + return ret; + } + break; + default: + break; + } + + if (!s5m87xx->ono) + return 0; + + switch (type) { + case S5M8763X: + ret = request_threaded_irq(s5m87xx->ono, NULL, + s5m8763_irq_thread, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | + IRQF_ONESHOT, "s5m87xx-ono", + s5m87xx); + break; + case S5M8767X: + ret = request_threaded_irq(s5m87xx->ono, NULL, + s5m8767_irq_thread, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | + IRQF_ONESHOT, "s5m87xx-ono", s5m87xx); + break; + default: + break; + } + + if (ret) + dev_err(s5m87xx->dev, "Failed to request IRQ %d: %d\n", + s5m87xx->ono, ret); + + return 0; +} + +void s5m_irq_exit(struct s5m87xx_dev *s5m87xx) +{ + if (s5m87xx->ono) + free_irq(s5m87xx->ono, s5m87xx); + + if (s5m87xx->irq) + free_irq(s5m87xx->irq, s5m87xx); +} diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c index e198d40..95f9c0e 100644 --- a/drivers/mfd/wm8994-core.c +++ b/drivers/mfd/wm8994-core.c @@ -243,6 +243,18 @@ static struct mfd_cell wm8994_devs[] = { * and should be handled via the standard regulator API supply * management. */ +static const char *wm1811_main_supplies[] = { + "DBVDD1", + "DBVDD2", + "DBVDD3", + "DCVDD", + "AVDD1", + "AVDD2", + "CPVDD", + "SPKVDD1", + "SPKVDD2", +}; + static const char *wm8994_main_supplies[] = { "DBVDD", "DCVDD", @@ -281,6 +293,62 @@ static int wm8994_suspend(struct device *dev) return 0; } + ret = wm8994_reg_read(wm8994, WM8994_POWER_MANAGEMENT_4); + if (ret < 0) { + dev_err(dev, "Failed to read power status: %d\n", ret); + } else if (ret & (WM8994_AIF2ADCL_ENA | WM8994_AIF2ADCR_ENA | + WM8994_AIF1ADC2L_ENA | WM8994_AIF1ADC2R_ENA | + WM8994_AIF1ADC1L_ENA | WM8994_AIF1ADC1R_ENA)) { + dev_dbg(dev, "CODEC still active, ignoring suspend\n"); + return 0; + } + + ret = wm8994_reg_read(wm8994, WM8994_POWER_MANAGEMENT_5); + if (ret < 0) { + dev_err(dev, "Failed to read power status: %d\n", ret); + } else if (ret & (WM8994_AIF2DACL_ENA | WM8994_AIF2DACR_ENA | + WM8994_AIF1DAC2L_ENA | WM8994_AIF1DAC2R_ENA | + WM8994_AIF1DAC1L_ENA | WM8994_AIF1DAC1R_ENA)) { + dev_dbg(dev, "CODEC still active, ignoring suspend\n"); + return 0; + } + + switch (wm8994->type) { + case WM8958: + case WM1811: + ret = wm8994_reg_read(wm8994, WM8958_MIC_DETECT_1); + if (ret < 0) { + dev_err(dev, "Failed to read power status: %d\n", ret); + } else if (ret & WM8958_MICD_ENA) { + dev_dbg(dev, "CODEC still active, ignoring suspend\n"); + return 0; + } + break; + default: + break; + } + + switch (wm8994->type) { + case WM1811: + ret = wm8994_reg_read(wm8994, WM8994_ANTIPOP_2); + if (ret < 0) { + dev_err(dev, "Failed to read jackdet: %d\n", ret); + } else if (ret & WM1811_JACKDET_MODE_MASK) { + dev_dbg(dev, "CODEC still active, ignoring suspend\n"); + return 0; + } + break; + default: + break; + } + + /* Disable LDO pulldowns while the device is suspended if we + * don't know that something will be driving them. */ + if (!wm8994->ldo_ena_always_driven) + wm8994_set_bits(wm8994, WM8994_PULL_CONTROL_2, + WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD, + WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD); + /* GPIO configuration state is saved here since we may be configuring * the GPIO alternate functions even if we're not using the gpiolib * driver for them. @@ -316,7 +384,7 @@ static int wm8994_suspend(struct device *dev) static int wm8994_resume(struct device *dev) { struct wm8994 *wm8994 = dev_get_drvdata(dev); - int ret; + int ret, i; /* We may have lied to the PM core about suspending */ if (!wm8994->suspended) @@ -329,10 +397,16 @@ static int wm8994_resume(struct device *dev) return ret; } - ret = wm8994_write(wm8994, WM8994_INTERRUPT_STATUS_1_MASK, - WM8994_NUM_IRQ_REGS * 2, &wm8994->irq_masks_cur); - if (ret < 0) - dev_err(dev, "Failed to restore interrupt masks: %d\n", ret); + /* Write register at a time as we use the cache on the CPU so store + * it in native endian. + */ + for (i = 0; i < ARRAY_SIZE(wm8994->irq_masks_cur); i++) { + ret = wm8994_reg_write(wm8994, WM8994_INTERRUPT_STATUS_1_MASK + + i, wm8994->irq_masks_cur[i]); + if (ret < 0) + dev_err(dev, "Failed to restore interrupt masks: %d\n", + ret); + } ret = wm8994_write(wm8994, WM8994_LDO_1, WM8994_NUM_LDO_REGS * 2, &wm8994->ldo_regs); @@ -344,6 +418,11 @@ static int wm8994_resume(struct device *dev) if (ret < 0) dev_err(dev, "Failed to restore GPIO registers: %d\n", ret); + /* Disable LDO pulldowns while the device is active */ + wm8994_set_bits(wm8994, WM8994_PULL_CONTROL_2, + WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD, + 0); + wm8994->suspended = false; return 0; @@ -380,6 +459,7 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) struct wm8994_pdata *pdata = wm8994->dev->platform_data; const char *devname; int ret, i; + int pulls = 0; mutex_init(&wm8994->io_lock); dev_set_drvdata(wm8994->dev, wm8994); @@ -395,6 +475,9 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) } switch (wm8994->type) { + case WM1811: + wm8994->num_supplies = ARRAY_SIZE(wm1811_main_supplies); + break; case WM8994: wm8994->num_supplies = ARRAY_SIZE(wm8994_main_supplies); break; @@ -403,7 +486,7 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) break; default: BUG(); - return -EINVAL; + goto err; } wm8994->supplies = kzalloc(sizeof(struct regulator_bulk_data) * @@ -415,6 +498,10 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) } switch (wm8994->type) { + case WM1811: + for (i = 0; i < ARRAY_SIZE(wm1811_main_supplies); i++) + wm8994->supplies[i].supply = wm1811_main_supplies[i]; + break; case WM8994: for (i = 0; i < ARRAY_SIZE(wm8994_main_supplies); i++) wm8994->supplies[i].supply = wm8994_main_supplies[i]; @@ -425,9 +512,9 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) break; default: BUG(); - return -EINVAL; + goto err; } - + ret = regulator_bulk_get(wm8994->dev, wm8994->num_supplies, wm8994->supplies); if (ret != 0) { @@ -446,8 +533,29 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) if (ret < 0) { dev_err(wm8994->dev, "Failed to read ID register\n"); goto err_enable; - } + } else + dev_info(wm8994->dev, "Succeeded to read ID register\n"); + switch (ret) { + case 0x1811: + devname = "WM1811"; + if (wm8994->type != WM1811) + dev_warn(wm8994->dev, "Device registered as type %d\n", + wm8994->type); + wm8994->type = WM1811; + + /* Samsung-specific customization of MICBIAS levels */ + wm8994_reg_write(wm8994, 0x102, 0x3); + wm8994_reg_write(wm8994, 0xcb, 0x5151); + wm8994_reg_write(wm8994, 0xd3, 0x3f3f); + wm8994_reg_write(wm8994, 0xd4, 0x3f3f); + wm8994_reg_write(wm8994, 0xd5, 0x3f3f); + wm8994_reg_write(wm8994, 0xd6, 0x3226); + wm8994_reg_write(wm8994, 0x102, 0x0); + wm8994_reg_write(wm8994, 0xd1, 0x87); + wm8994_reg_write(wm8994, 0x3b, 0x9); + wm8994_reg_write(wm8994, 0x3c, 0x2); + break; case 0x8994: devname = "WM8994"; if (wm8994->type != WM8994) @@ -476,19 +584,28 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) goto err_enable; } - switch (ret) { - case 0: - case 1: - if (wm8994->type == WM8994) + wm8994->revision = ret & WM8994_CHIP_REV_MASK; + wm8994->cust_id = (ret & WM8994_CUST_ID_MASK) >> WM8994_CUST_ID_SHIFT; + + switch (wm8994->type) { + case WM8994: + switch (wm8994->revision) { + case 0: + case 1: dev_warn(wm8994->dev, "revision %c not fully supported\n", - 'A' + ret); + 'A' + wm8994->revision); + break; + default: + break; + } break; default: break; } - dev_info(wm8994->dev, "%s revision %c\n", devname, 'A' + ret); + dev_info(wm8994->dev, "%s revision %c CUST_ID %02x\n", devname, + 'A' + wm8994->revision, wm8994->cust_id); if (pdata) { wm8994->irq_base = pdata->irq_base; @@ -502,8 +619,19 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) pdata->gpio_defaults[i]); } } + + wm8994->ldo_ena_always_driven = pdata->ldo_ena_always_driven; + + if (pdata->spkmode_pu) + pulls |= WM8994_SPKMODE_PU; } + /* Disable unneeded pulls */ + wm8994_set_bits(wm8994, WM8994_PULL_CONTROL_2, + WM8994_LDO1ENA_PD | WM8994_LDO2ENA_PD | + WM8994_SPKMODE_PU | WM8994_CSNADDR_PD, + pulls); + /* In some system designs where the regulators are not in use, * we can achieve a small reduction in leakage currents by * floating LDO outputs. This bit makes no difference if the @@ -529,8 +657,10 @@ static int wm8994_device_init(struct wm8994 *wm8994, int irq) goto err_irq; } +#if 0 /* To do */ pm_runtime_enable(wm8994->dev); pm_runtime_resume(wm8994->dev); +#endif return 0; @@ -545,7 +675,6 @@ err_supplies: kfree(wm8994->supplies); err: mfd_remove_devices(wm8994->dev); - kfree(wm8994); return ret; } @@ -582,13 +711,24 @@ static int wm8994_i2c_read_device(struct wm8994 *wm8994, unsigned short reg, return 0; } -static int wm8994_i2c_write_device(struct wm8994 *wm8994, unsigned short reg, +static int wm8994_i2c_gather_write_device(struct wm8994 *wm8994, unsigned short reg, int bytes, const void *src) { struct i2c_client *i2c = wm8994->control_data; struct i2c_msg xfer[2]; int ret; + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_PROTOCOL_MANGLING)) { + dev_vdbg(wm8994->dev, + "%s: I2C Controller does _NOT_ support block/gather\n", + __func__); + return -ENOTSUPP; + } + + dev_vdbg(wm8994->dev, + "%s: gather write - Reg = 0x%04x, bytes = 0x%x\n", + __func__, reg, bytes); + reg = cpu_to_be16(reg); xfer[0].addr = i2c->addr; @@ -610,6 +750,66 @@ static int wm8994_i2c_write_device(struct wm8994 *wm8994, unsigned short reg, return 0; } +static int wm8994_i2c_write_device(struct wm8994 *wm8994, unsigned short reg, + int bytes, const void *src) +{ + struct i2c_client *i2c = wm8994->control_data; + int ret; + + unsigned char msg[2 + 2]; + void *buf; + + /* If we're doing a single register write we can probably just + * send the work_buf directly, otherwise try to do a gather + * write. + */ + if (bytes == 2) { + dev_vdbg(wm8994->dev, + "%s: Single register write - Reg = 0x%04x, bytes = 0x%x\n", + __func__, reg, bytes); + + reg = cpu_to_be16(reg); + memcpy(&msg[0], ®, 2); + memcpy(&msg[2], src, bytes); + + ret = i2c_master_send(i2c, msg, bytes + 2); + if (ret < 0) + return ret; + if (ret < bytes + 2) + return -EIO; + + return 0; + } else { + ret = wm8994_i2c_gather_write_device(wm8994, reg, bytes, src); + } + + if (ret == -ENOTSUPP) { + dev_vdbg(wm8994->dev, + "%s: Manual group write - Reg = 0x%04x, bytes = 0x%x\n", + __func__, reg, bytes); + + buf = kmalloc(2 + bytes, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + reg = cpu_to_be16(reg); + memcpy(buf, ®, 2); + memcpy(buf + 2, src, bytes); + + ret = i2c_master_send(i2c, buf, bytes + 2); + + kfree(buf); + + if (ret < 0) + return ret; + if (ret < bytes + 2) + return -EIO; + + return 0; + } + return ret; +} + static int wm8994_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -627,7 +827,12 @@ static int wm8994_i2c_probe(struct i2c_client *i2c, wm8994->irq = i2c->irq; wm8994->type = id->driver_data; - return wm8994_device_init(wm8994, i2c->irq); + if (wm8994_device_init(wm8994, i2c->irq) < 0) { + kfree(wm8994); + return -ENODEV; + } + + return 0; } static int wm8994_i2c_remove(struct i2c_client *i2c) @@ -640,6 +845,7 @@ static int wm8994_i2c_remove(struct i2c_client *i2c) } static const struct i2c_device_id wm8994_i2c_id[] = { + { "wm1811", WM1811 }, { "wm8994", WM8994 }, { "wm8958", WM8958 }, { } diff --git a/drivers/mfd/wm8994-irq.c b/drivers/mfd/wm8994-irq.c index 71c6e8f..d682f7b 100644 --- a/drivers/mfd/wm8994-irq.c +++ b/drivers/mfd/wm8994-irq.c @@ -231,12 +231,6 @@ static irqreturn_t wm8994_irq_thread(int irq, void *data) status[i] &= ~wm8994->irq_masks_cur[i]; } - /* Report */ - for (i = 0; i < ARRAY_SIZE(wm8994_irqs); i++) { - if (status[wm8994_irqs[i].reg - 1] & wm8994_irqs[i].mask) - handle_nested_irq(wm8994->irq_base + i); - } - /* Ack any unmasked IRQs */ for (i = 0; i < ARRAY_SIZE(status); i++) { if (status[i]) @@ -244,6 +238,12 @@ static irqreturn_t wm8994_irq_thread(int irq, void *data) status[i]); } + /* Report */ + for (i = 0; i < ARRAY_SIZE(wm8994_irqs); i++) { + if (status[wm8994_irqs[i].reg - 1] & wm8994_irqs[i].mask) + handle_nested_irq(wm8994->irq_base + i); + } + return IRQ_HANDLED; } |