diff options
author | kongzizaixian <xweikong@hotmail.com> | 2015-11-08 17:46:16 +0800 |
---|---|---|
committer | kongzizaixian <xweikong@hotmail.com> | 2015-11-08 17:46:16 +0800 |
commit | 4495c689c476d1d9ccb1fa1f33e30ebb30e93be8 (patch) | |
tree | 742aa1106bea5c7f6943cd7cc2be87b2a7b2f841 /drivers/mfd/hi655x-pmic.c | |
parent | bb3e34558f7e2ec280114c585ff5afde63787ad1 (diff) | |
parent | d880643119ede4cf58c7e577d3f292ad61a7e341 (diff) |
Merge pull request #10 from kongzizaixian/master
HiKey: boot ubunt system by SD card
Diffstat (limited to 'drivers/mfd/hi655x-pmic.c')
-rw-r--r-- | drivers/mfd/hi655x-pmic.c | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/drivers/mfd/hi655x-pmic.c b/drivers/mfd/hi655x-pmic.c new file mode 100644 index 000000000000..a0910345f6b6 --- /dev/null +++ b/drivers/mfd/hi655x-pmic.c @@ -0,0 +1,392 @@ +/* + * Hisilicon Hi655x series PMIC driver + * + * Copyright (c) 2015 Hisilicon Co. Ltd + * + * Author: + * Dongbin Yu <yudongbin@huawei.com> + * Bintian Wang <bintian.wang@huawei.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/io.h> +#include <linux/irq.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/gpio.h> +#include <linux/types.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/hardirq.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/irqdomain.h> +#include <linux/mfd/hi655x-pmic.h> + +static void __iomem *PMUSSI_BASE_ADDR; + +#define PMUSSI_REG(addr) ((char *)PMUSSI_BASE_ADDR + ((addr) << 2)) + +#define DEBUG_PMIC_GPIO + +struct hi655x_pmic { + struct resource *res; + struct device *dev; + spinlock_t ssi_hw_lock; + struct clk *clk; + struct irq_domain *domain; + int irq; + int gpio; + unsigned int irqs[HI655x_NR_IRQ]; + unsigned int ver; +}; + +static struct hi655x_pmic *pmic_dev; + +unsigned char hi655x_pmic_reg_read (unsigned int addr) +{ + unsigned char val; + val = *(volatile unsigned char*)PMUSSI_REG(addr); + return val; +} +EXPORT_SYMBOL(hi655x_pmic_reg_read); + +void hi655x_pmic_reg_write (unsigned int addr, unsigned char val) +{ + *(volatile unsigned char*)PMUSSI_REG(addr) = val; +} +EXPORT_SYMBOL(hi655x_pmic_reg_write); + +unsigned char hi655x_pmic_reg_read_ex (void *pmu_base, unsigned int addr) +{ + unsigned char val; + val = *(volatile unsigned char*)PMUSSI_REG_EX(pmu_base, addr); + return val; +} +EXPORT_SYMBOL(hi655x_pmic_reg_read_ex); + +void hi655x_pmic_reg_write_ex(void *pmu_base, unsigned int addr, unsigned char val) +{ + *(volatile unsigned char*)PMUSSI_REG_EX(pmu_base, addr) = val; +} +EXPORT_SYMBOL(hi655x_pmic_reg_write_ex); + +static struct of_device_id of_hi655x_pmic_child_match_tbl[] = { + { .compatible = "hisilicon,hi6552-regulator-pmic", }, + { .compatible = "hisilicon,hi6552-powerkey", }, + { .compatible = "hisilicon,hi6552-usbvbus", }, + { .compatible = "hisilicon,hi6552-coul", }, + { .compatible = "hisilicon,hi6552-pmu-rtc", }, + { .compatible = "hisilicon,hi6552-pmic-mntn", }, + { /* end */ } +}; + +static struct of_device_id of_hi655x_pmic_match_tbl[] = { + { .compatible = "hisilicon,hi6552-pmic-driver", }, + { /* end */ } +}; + +unsigned int hi655x_pmic_get_version(void) +{ + unsigned int uvalue = 0; + uvalue = (unsigned int)hi655x_pmic_reg_read(HI655x_VER_REG); + uvalue = uvalue & HI655x_REG_WIDTH; + + return uvalue; +} + +static int hi655x_pmic_version_check(void) +{ + int ret = SSI_DEVICE_ERR; + int ver = 0; + + ver = hi655x_pmic_get_version(); + if ((ver >= PMU_VER_START) && (ver <= PMU_VER_END)) + return SSI_DEVICE_OK; + + return ret; +} + +static irqreturn_t hi655x_pmic_irq_handler(int irq, void *data) +{ + struct hi655x_pmic *pmic = (struct hi655x_pmic *)data; + unsigned long pending; + unsigned int ret = IRQ_NONE; + int i, offset; + + for (i = 0; i < HI655x_IRQ_ARRAY; i++) { + pending = hi655x_pmic_reg_read((i + HI655x_IRQ_STAT_BASE)); + pending &= HI655x_REG_WIDTH; + if (pending != 0) + pr_debug("pending[%d]=0x%lx\n\r", i, pending); + + /* clear pmic-sub-interrupt */ + hi655x_pmic_reg_write((i + HI655x_IRQ_STAT_BASE), pending); + + if (pending) { + for_each_set_bit(offset, &pending, HI655x_BITS) + generic_handle_irq(pmic->irqs[offset + i * HI655x_BITS]); + ret = IRQ_HANDLED; + } + } + + return ret; +} + +static void hi655x_pmic_irq_mask(struct irq_data *d) +{ + u32 data, offset; + unsigned long pmic_spin_flag = 0; + offset = ((irqd_to_hwirq(d) >> 3) + HI655x_IRQ_MASK_BASE); + + spin_lock_irqsave(&pmic_dev->ssi_hw_lock, pmic_spin_flag); + data = hi655x_pmic_reg_read(offset); + data |= (1 << (irqd_to_hwirq(d) & 0x07)); + hi655x_pmic_reg_write(offset, data); + spin_unlock_irqrestore(&pmic_dev->ssi_hw_lock, pmic_spin_flag); +} + +static void hi655x_pmic_irq_unmask(struct irq_data *d) +{ + u32 data, offset; + unsigned long pmic_spin_flag = 0; + offset = ((irqd_to_hwirq(d) >> 3) + HI655x_IRQ_MASK_BASE); + + spin_lock_irqsave(&pmic_dev->ssi_hw_lock, pmic_spin_flag); + data = hi655x_pmic_reg_read(offset); + data &= ~(1 << (irqd_to_hwirq(d) & 0x07)); + hi655x_pmic_reg_write(offset, data); + spin_unlock_irqrestore(&pmic_dev->ssi_hw_lock, pmic_spin_flag); +} + +static struct irq_chip hi655x_pmic_irqchip = { + .name = "hisi-hi655x-pmic-irqchip", + .irq_mask = hi655x_pmic_irq_mask, + .irq_unmask = hi655x_pmic_irq_unmask, +}; + +static int hi655x_pmic_irq_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hw) +{ + struct hi655x_pmic *pmic = d->host_data; + + irq_set_chip_and_handler_name(virq, &hi655x_pmic_irqchip, + handle_simple_irq, "hisi-hi655x-pmic-irqchip"); + irq_set_chip_data(virq, pmic); + irq_set_irq_type(virq, IRQ_TYPE_NONE); + + return 0; +} + +static struct irq_domain_ops hi655x_domain_ops = { + .map = hi655x_pmic_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +static inline void hi655x_pmic_clear_int(void) +{ + int addr; + + for (addr = HI655x_IRQ_STAT_BASE; addr < (HI655x_IRQ_STAT_BASE + HI655x_IRQ_ARRAY); addr++) + hi655x_pmic_reg_write(addr, HI655x_IRQ_CLR); +} + +static inline void hi655x_pmic_mask_int(void) +{ + int addr; + + for (addr = HI655x_IRQ_MASK_BASE; addr < (HI655x_IRQ_MASK_BASE + HI655x_IRQ_ARRAY); addr++) + hi655x_pmic_reg_write(addr, HI655x_IRQ_MASK); +} + +static int hi655x_pmic_probe(struct platform_device *pdev) +{ + int i = 0; + int ret = 0 ; + int dev_stat = 0; + unsigned int virq = 0; + int pmu_on = 1; + enum of_gpio_flags gpio_flags; + struct device_node *gpio_np = NULL; + + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct hi655x_pmic *pmic = NULL; + + /* + * this is new feature in kernel 3.10 + */ + pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL); + if (!pmic) { + printk("cannot allocate hi655x_pmic device info\n"); + return -ENOMEM; + } + pmic_dev = pmic; + + /* init spin lock */ + spin_lock_init(&pmic->ssi_hw_lock); + + pmic->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!pmic->res) { + printk("platform_get_resource err\n"); + return -ENOENT; + } + if (!devm_request_mem_region(dev, pmic->res->start, + resource_size(pmic->res), + pdev->name)) { + printk("cannot claim register memory\n"); + return -ENOMEM; + } + PMUSSI_BASE_ADDR = ioremap(pmic->res->start, + resource_size(pmic->res)); + if (!PMUSSI_BASE_ADDR) { + printk("cannot map register memory\n"); + return -ENOMEM; + } + + /* confirm the pmu version */ + pmic->ver = hi655x_pmic_get_version(); + if ((pmic->ver < PMU_VER_START) || (pmic->ver > PMU_VER_END)) { + pr_err("it is wrong pmu version\n"); + pmu_on = 0; + } + + hi655x_pmic_reg_write(0x1b5, 0xff); + +#ifdef DEBUG_PMIC_GPIO + /* + * must finish the gpio&irq is function + */ + gpio_np = of_parse_phandle(np, "pmu_irq_gpio", 0); + if (!gpio_np) { + dev_err(dev, "can't parse property\n"); + return -ENOENT; + } + pmic->gpio = of_get_gpio_flags(gpio_np, 0, &gpio_flags); + if (pmic->gpio < 0) { + dev_err(dev, "failed to of_get_gpio_flags %d\n", pmic->gpio); + return pmic->gpio; + } + if (!gpio_is_valid(pmic->gpio)) { + dev_err(dev, "it is invalid gpio %d\n", pmic->gpio); + return -EINVAL; + } + + ret = gpio_request_one(pmic->gpio, GPIOF_IN, "hi655x_pmic_irq"); + if (ret < 0) { + pr_err("failed to request gpio %d, ret:%d\n", pmic->gpio, ret); + return ret; + } + pmic->irq = gpio_to_irq(pmic->gpio); +#endif + + /* clear PMIC sub-interrupt */ + hi655x_pmic_clear_int(); + + /* mask PMIC sub-interrupt */ + hi655x_pmic_mask_int(); + + /* register irq domain */ + pmic->domain = irq_domain_add_simple(np, HI655x_NR_IRQ, 0, + &hi655x_domain_ops, pmic); + if (!pmic->domain) { + pr_err("in %s failed irq domain add simple!\n", __func__); + ret = -ENODEV; + return ret; + } + + for (i = 0; i < HI655x_NR_IRQ; i++) { + virq = irq_create_mapping(pmic->domain, i); + if (0 == virq) { + printk("Failed mapping hwirq\n"); + ret = -ENOSPC; + return ret; + } + pmic->irqs[i] = virq; + } + + /* Check the GPIO status is high */ + if (pmu_on) { + ret = request_threaded_irq(pmic->irq, hi655x_pmic_irq_handler, NULL, + IRQF_TRIGGER_LOW | IRQF_SHARED | IRQF_NO_SUSPEND, + "hi655x-pmic-irq", pmic); + if (ret < 0) { + pr_err("*************could not claim pmic %d\n", ret); + ret = -ENODEV; + return ret; + } + } + + pmic->dev = dev; + + /* bind pmic to device */ + platform_set_drvdata(pdev, pmic); + + /* populate sub nodes */ + of_platform_populate(np, of_hi655x_pmic_child_match_tbl, NULL, dev); + + dev_stat = hi655x_pmic_version_check(); + return 0; +} + +#ifdef CONFIG_PM +static int hi655x_pmic_suspend(struct platform_device *pdev, pm_message_t pm) +{ + return 0; +} + +static int hi655x_pmic_resume(struct platform_device *pdev) +{ + return 0; +} +#endif + +static struct platform_driver pmic_driver = { + .driver = { + .name = "hisi,hi655x-pmic", + .owner = THIS_MODULE, + .of_match_table = of_hi655x_pmic_match_tbl, + }, + .probe = hi655x_pmic_probe, +#ifdef CONFIG_PM + .suspend = hi655x_pmic_suspend, + .resume = hi655x_pmic_resume, +#endif +}; + +static int __init hi655x_pmic_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&pmic_driver); + if (ret) { + printk("%s: platform_driver_register failed %d\n", + __func__, ret); + } + + return ret; +} + +static void __exit hi655x_pmic_exit(void) +{ + platform_driver_unregister(&pmic_driver); +} + +module_init(hi655x_pmic_init); +module_exit(hi655x_pmic_exit); + +MODULE_AUTHOR("Dongbin Yu <yudongbin@huawei.com>"); +MODULE_DESCRIPTION("Hisilicon HI655x PMU SSI interface driver"); +MODULE_LICENSE("GPL v2"); |