diff options
author | Guodong Xu <guodong.xu@linaro.org> | 2013-08-15 16:50:36 +0800 |
---|---|---|
committer | Andrey Konovalov <andrey.konovalov@linaro.org> | 2013-12-05 21:25:34 +0400 |
commit | 56f556c092c503c017dfd1edbe2ba4cd81bb5032 (patch) | |
tree | 2548b87683fddbf3c9d4bca7ae1b89851b1dd077 | |
parent | fcdb3a3a7276b820fa87cea4ad8678b418138624 (diff) |
mfd: Support HiSilicon Hi6421 PMIC
Add support of HiSilicon Hi6421 in mfd. Hi6421 is a power management and
codec IC from HiSilicon Inc., which includes regulators, codec, ADCs,
Coulomb counter, etc. The way to communitcate with Hi6421 is through
memory mapped I/O ports.
Signed-off-by: Guodong Xu <guodong.xu@linaro.org>
Signed-off-by: Haojian Zhuang <haojian.zhuang@linaro.org>
-rw-r--r-- | drivers/mfd/Kconfig | 8 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/hi6421-pmic-core.c | 302 | ||||
-rw-r--r-- | include/linux/mfd/hi6421-pmic.h | 84 |
4 files changed, 395 insertions, 0 deletions
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 62a60caa5d1f..5019bfb26ce8 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1173,6 +1173,14 @@ config MFD_STW481X in various ST Microelectronics and ST-Ericsson embedded Nomadik series. +config MFD_HI6421_PMIC + tristate "HiSilicon Hi6421 PMU/Codec IC" + depends on OF + help + This driver supports HiSilicon Hi6421 power management and codec IC, + including regulators, codec, ADCs, Coulomb counter, etc. Memory + mapped I/O ports are the way of communication with it. + endmenu endif diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 8a28dc90fe78..0d5c827fe174 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -164,3 +164,4 @@ obj-$(CONFIG_MFD_RETU) += retu-mfd.o obj-$(CONFIG_MFD_AS3711) += as3711.o obj-$(CONFIG_MFD_AS3722) += as3722.o obj-$(CONFIG_MFD_STW481X) += stw481x.o +obj-$(CONFIG_MFD_HI6421_PMIC) += hi6421-pmic-core.o diff --git a/drivers/mfd/hi6421-pmic-core.c b/drivers/mfd/hi6421-pmic-core.c new file mode 100644 index 000000000000..b2d7c4e82bd2 --- /dev/null +++ b/drivers/mfd/hi6421-pmic-core.c @@ -0,0 +1,302 @@ +/* + * Device driver for regulators in Hi6421 IC + * + * Copyright (c) 2013 Linaro Ltd. + * Copyright (c) 2011 Hisilicon. + * + * Guodong Xu <guodong.xu@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/of_irq.h> +#include <linux/mfd/hi6421-pmic.h> + +#include <asm/mach/irq.h> + +/* 8-bit register offset in PMIC */ +#define HI6421_REG_IRQ1 1 +#define HI6421_REG_IRQ2 2 +#define HI6421_REG_IRQ3 3 +#define HI6421_REG_IRQM1 4 +#define HI6421_REG_IRQM2 5 +#define HI6421_REG_IRQM3 6 + +static struct of_device_id of_hi6421_pmic_child_match_tbl[] = { + /* regulators */ + { + .compatible = "hisilicon,hi6421-ldo", + }, + { + .compatible = "hisilicon,hi6421-buck012", + }, + { + .compatible = "hisilicon,hi6421-buck345", + }, + { /* end */ } +}; + +static struct of_device_id of_hi6421_pmic_match_tbl[] = { + { + .compatible = "hisilicon,hi6421-pmic", + }, + { /* end */ } +}; + +/* + * The PMIC register is only 8-bit. + * Hisilicon SoC use hardware to map PMIC register into SoC mapping. + * At here, we are accessing SoC register with 32-bit. + */ +u32 hi6421_pmic_read(struct hi6421_pmic *pmic, int reg) +{ + unsigned long flags; + u32 ret; + spin_lock_irqsave(&pmic->lock, flags); + ret = readl_relaxed(pmic->regs + (reg << 2)); + spin_unlock_irqrestore(&pmic->lock, flags); + return ret; +} +EXPORT_SYMBOL(hi6421_pmic_read); + +void hi6421_pmic_write(struct hi6421_pmic *pmic, int reg, u32 val) +{ + unsigned long flags; + spin_lock_irqsave(&pmic->lock, flags); + writel_relaxed(val, pmic->regs + (reg << 2)); + spin_unlock_irqrestore(&pmic->lock, flags); +} +EXPORT_SYMBOL(hi6421_pmic_write); + +void hi6421_pmic_rmw(struct hi6421_pmic *pmic, int reg, + u32 mask, u32 bits) +{ + u32 data; + + spin_lock(&pmic->lock); + data = readl_relaxed(pmic->regs + (reg << 2)) & ~mask; + data |= mask & bits; + writel_relaxed(data, pmic->regs + (reg << 2)); + spin_unlock(&pmic->lock); +} +EXPORT_SYMBOL(hi6421_pmic_rmw); + +static int hi6421_to_irq(struct hi6421_pmic *pmic, unsigned offset) +{ + return irq_create_mapping(pmic->domain, offset); +} + +static irqreturn_t hi6421_irq_handler(int irq, void *data) +{ + struct hi6421_pmic *pmic = (struct hi6421_pmic *)data; + unsigned long pending; + int i, offset, index; + + + for (i = HI6421_REG_IRQ1; i <= HI6421_REG_IRQ3; i++) { + spin_lock(&pmic->lock); + pending = readl_relaxed(pmic->regs + (i << 2)); + pending &= HI6421_MASK_FIELD; + writel_relaxed(pending, pmic->regs + ((i + 3) << 2)); + spin_unlock(&pmic->lock); + + if (pending) { + for_each_set_bit(offset, &pending, HI6421_BITS) { + index = offset + (i - HI6421_REG_IRQ1) * HI6421_BITS; + generic_handle_irq(hi6421_to_irq(pmic, index)); + } + } + + spin_lock(&pmic->lock); + writel_relaxed(0, pmic->regs + ((i + 3) << 2)); + writel_relaxed(pending, pmic->regs + (i << 2)); + spin_unlock(&pmic->lock); + } + + return IRQ_HANDLED; +} + +static void hi6421_irq_mask(struct irq_data *d) +{ + struct hi6421_pmic *pmic = irq_data_get_irq_chip_data(d); + u32 data, offset; + + offset = ((irqd_to_hwirq(d) >> 3) + HI6421_REG_IRQM1) << 2; + spin_lock(&pmic->lock); + data = readl_relaxed(pmic->regs + offset); + data |= irqd_to_hwirq(d) % 8; + writel_relaxed(data, pmic->regs + offset); + spin_unlock(&pmic->lock); +} + +static void hi6421_irq_unmask(struct irq_data *d) +{ + struct hi6421_pmic *pmic = irq_data_get_irq_chip_data(d); + u32 data, offset; + + offset = ((irqd_to_hwirq(d) >> 3) + HI6421_REG_IRQM1) << 2; + spin_lock(&pmic->lock); + data = readl_relaxed(pmic->regs + offset); + data &= ~(irqd_to_hwirq(d) % 8); + writel_relaxed(data, pmic->regs + offset); + spin_unlock(&pmic->lock); +} + +static struct irq_chip hi6421_irqchip = { + .name = "pmic", + .irq_mask = hi6421_irq_mask, + .irq_unmask = hi6421_irq_unmask, +}; + +static int hi6421_irq_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hw) +{ + struct hi6421_pmic *pmic = d->host_data; + + irq_set_chip_and_handler_name(virq, &hi6421_irqchip, + handle_simple_irq, "hi6421"); + irq_set_chip_data(virq, pmic); + irq_set_irq_type(virq, IRQ_TYPE_NONE); + + return 0; +} + +static struct irq_domain_ops hi6421_domain_ops = { + .map = hi6421_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +static int hi6421_pmic_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct hi6421_pmic *pmic = NULL; + enum of_gpio_flags flags; + int ret; + + pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL); + if (!pmic) { + dev_err(dev, "cannot allocate hi6421_pmic device info\n"); + return -ENOMEM; + } + + mutex_init(&pmic->enable_mutex); + /* get resources */ + pmic->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!pmic->res) { + dev_err(dev, "platform_get_resource err\n"); + return -ENOENT; + } + + if (!devm_request_mem_region(dev, pmic->res->start, + resource_size(pmic->res), + pdev->name)) { + dev_err(dev, "cannot claim register memory\n"); + return -ENOMEM; + } + + pmic->regs = devm_ioremap(dev, pmic->res->start, + resource_size(pmic->res)); + if (!pmic->regs) { + dev_err(dev, "cannot map register memory\n"); + return -ENOMEM; + } + + /* TODO: get and enable clk request */ + + spin_lock_init(&pmic->lock); + + pmic->gpio = of_get_gpio_flags(np, 0, &flags); + if (pmic->gpio < 0) + return pmic->gpio; + if (!gpio_is_valid(pmic->gpio)) + return -EINVAL; + ret = gpio_request_one(pmic->gpio, GPIOF_IN, "pmic"); + if (ret < 0) { + dev_err(dev, "failed to request gpio%d\n", pmic->gpio); + return ret; + } + pmic->irq = gpio_to_irq(pmic->gpio); + /* clear IRQ status */ + spin_lock(&pmic->lock); + writel_relaxed(0xff, pmic->regs + (HI6421_REG_IRQ1 << 2)); + writel_relaxed(0xff, pmic->regs + (HI6421_REG_IRQ2 << 2)); + writel_relaxed(0xff, pmic->regs + (HI6421_REG_IRQ3 << 2)); + spin_unlock(&pmic->lock); + + pmic->domain = irq_domain_add_simple(np, HI6421_NR_IRQ, 0, + &hi6421_domain_ops, pmic); + if (!pmic->domain) + return -ENODEV; + + ret = request_threaded_irq(pmic->irq, hi6421_irq_handler, NULL, + IRQF_TRIGGER_LOW | IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND, + "pmic", pmic); + + platform_set_drvdata(pdev, pmic); + + /* set over-current protection debounce 8ms*/ + hi6421_pmic_rmw(pmic, OCP_DEB_CTRL_REG, \ + OCP_DEB_SEL_MASK | OCP_EN_DEBOUNCE_MASK | OCP_AUTO_STOP_MASK, \ + OCP_DEB_SEL_8MS | OCP_EN_DEBOUNCE_ENABLE); + + /* populate sub nodes */ + of_platform_populate(np, of_hi6421_pmic_child_match_tbl, NULL, dev); + + return 0; +} + +static int hi6421_pmic_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct hi6421_pmic *pmic = platform_get_drvdata(pdev); + + free_irq(pmic->irq, pmic); + gpio_free(pmic->gpio); + devm_iounmap(dev, pmic->regs); + devm_release_mem_region(dev, pmic->res->start, + resource_size(pmic->res)); + devm_kfree(dev, pmic); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver hi6421_pmic_driver = { + .driver = { + .name = "hi6421_pmic", + .owner = THIS_MODULE, + .of_match_table = of_hi6421_pmic_match_tbl, + }, + .probe = hi6421_pmic_probe, + .remove = hi6421_pmic_remove, +}; +module_platform_driver(hi6421_pmic_driver); + +MODULE_AUTHOR("Guodong Xu <guodong.xu@linaro.org>"); +MODULE_DESCRIPTION("Hi6421 PMIC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mfd/hi6421-pmic.h b/include/linux/mfd/hi6421-pmic.h new file mode 100644 index 000000000000..2f875cf52ae9 --- /dev/null +++ b/include/linux/mfd/hi6421-pmic.h @@ -0,0 +1,84 @@ +/* + * Header file for device driver Hi6421 PMIC + * + * Copyright (c) 2013 Linaro Ltd. + * Copyright (C) 2011 Hisilicon. + * + * Guodong Xu <guodong.xu@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __HI6421_PMIC_H +#define __HI6421_PMIC_H + +#include <linux/irqdomain.h> + +#define OCP_DEB_CTRL_REG (0x51) +#define OCP_DEB_SEL_MASK (0x0C) +#define OCP_DEB_SEL_8MS (0x00) +#define OCP_DEB_SEL_16MS (0x04) +#define OCP_DEB_SEL_32MS (0x08) +#define OCP_DEB_SEL_64MS (0x0C) +#define OCP_EN_DEBOUNCE_MASK (0x02) +#define OCP_EN_DEBOUNCE_ENABLE (0x02) +#define OCP_AUTO_STOP_MASK (0x01) +#define OCP_AUTO_STOP_ENABLE (0x01) +#define HI6421_REGS_ENA_PROTECT_TIME (100) /* in microseconds */ +#define HI6421_ECO_MODE_ENABLE (1) +#define HI6421_ECO_MODE_DISABLE (0) + +#define HI6421_NR_IRQ 24 +#define HI6421_MASK_FIELD 0xFF +#define HI6421_BITS 8 + +#define HI6421_IRQ_ALARM 0 /* RTC Alarm */ +#define HI6421_IRQ_OTMP 1 /* Temperature too high */ +#define HI6421_IRQ_OCP 2 /* BUCK/LDO overload */ +#define HI6421_IRQ_RESET_IN 3 /* RESETIN_N signal */ +#define HI6421_IRQ_ONKEY_10S 4 /* PWRON key hold for 10s */ +#define HI6421_IRQ_ONKEY_1S 5 /* PWRON key hold for 1s */ +#define HI6421_IRQ_ONKEY_UP 6 /* PWRON key released */ +#define HI6421_IRQ_ONKEY_DOWN 7 /* PWRON key pressed */ +#define HI6421_IRQ_HEADSET_OUT 8 /* Headset plugged out */ +#define HI6421_IRQ_HEADSET_IN 9 /* Headset plugged in */ +#define HI6421_IRQ_COULOMB 10 /* Coulomb Counter */ +#define HI6421_IRQ_VBUS_UP 11 /* VBUS rising */ +#define HI6421_IRQ_VBUS_DOWN 12 /* VBUS falling */ +#define HI6421_IRQ_VBAT_LOW 13 /* VBattery too low */ +#define HI6421_IRQ_VBAT_HIGH 14 /* VBattery overvoltage */ +#define HI6421_IRQ_CHARGE_IN1 15 /* Charger plugged in */ +#define HI6421_IRQ_CHARGE_IN3 16 /* Charger plugged in */ +#define HI6421_IRQ_CHARGE_IN2 17 /* Charger plugged in */ +#define HI6421_IRQ_HS_BTN_DOWN 18 /* Headset button pressed */ +#define HI6421_IRQ_HS_BTN_UP 19 /* Headset button released */ +#define HI6421_IRQ_BATTERY_ON 20 /* Battery inserted */ +#define HI6421_IRQ_RESET 21 /* Reset */ + +struct hi6421_pmic { + struct resource *res; + struct device *dev; + void __iomem *regs; + spinlock_t lock; + struct irq_domain *domain; + int irq; + int gpio; +}; + +/* Register Access Helpers */ +u32 hi6421_pmic_read(struct hi6421_pmic *pmic, int reg); +void hi6421_pmic_write(struct hi6421_pmic *pmic, int reg, u32 val); +void hi6421_pmic_rmw(struct hi6421_pmic *pmic, int reg, u32 mask, u32 bits); + +#endif /* __HI6421_PMIC_H */ |