diff options
Diffstat (limited to 'drivers/mfd/hi6421-pmic-core.c')
-rw-r--r-- | drivers/mfd/hi6421-pmic-core.c | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/drivers/mfd/hi6421-pmic-core.c b/drivers/mfd/hi6421-pmic-core.c new file mode 100644 index 000000000000..1ef6d4e16172 --- /dev/null +++ b/drivers/mfd/hi6421-pmic-core.c @@ -0,0 +1,310 @@ +/* + * 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_find_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 i, 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; + + for (i = 0; i < HI6421_NR_IRQ; i++) { + ret = irq_create_mapping(pmic->domain, i); + if (ret == NO_IRQ) { + dev_err(dev, "failed mapping hwirq %d\n", i); + return -ENOMEM; + } + } + + 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"); |