blob: 27bde2c7720a71c9987e73f050f20ce5ac9c3f12 [file] [log] [blame]
/*
* 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");