linux/drivers/mfd/hi655x-pmic.c
Daniel Lezcano ac03fec1d7 mfd: hi655x: Add the clock cell to provide WiFi and Bluetooth
The hi655x is a PMIC with regulator but also provides a clock for the WiFi
and the bluetooth which is missing in the current implementation.

Add the clock cell so it can be used in the next patch via the dts.

Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Signed-off-by: Lee Jones <lee.jones@linaro.org>
2017-04-27 11:54:43 +01:00

188 lines
4.8 KiB
C

/*
* Device driver for MFD hi655x PMIC
*
* Copyright (c) 2016 Hisilicon.
*
* Authors:
* Chen Feng <puck.chen@hisilicon.com>
* Fei Wang <w.f@huawei.com>
*
* 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.
*/
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/mfd/core.h>
#include <linux/mfd/hi655x-pmic.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
static const struct regmap_irq hi655x_irqs[] = {
{ .reg_offset = 0, .mask = OTMP_D1R_INT_MASK },
{ .reg_offset = 0, .mask = VSYS_2P5_R_INT_MASK },
{ .reg_offset = 0, .mask = VSYS_UV_D3R_INT_MASK },
{ .reg_offset = 0, .mask = VSYS_6P0_D200UR_INT_MASK },
{ .reg_offset = 0, .mask = PWRON_D4SR_INT_MASK },
{ .reg_offset = 0, .mask = PWRON_D20F_INT_MASK },
{ .reg_offset = 0, .mask = PWRON_D20R_INT_MASK },
{ .reg_offset = 0, .mask = RESERVE_INT_MASK },
};
static const struct regmap_irq_chip hi655x_irq_chip = {
.name = "hi655x-pmic",
.irqs = hi655x_irqs,
.num_regs = 1,
.num_irqs = ARRAY_SIZE(hi655x_irqs),
.status_base = HI655X_IRQ_STAT_BASE,
.ack_base = HI655X_IRQ_STAT_BASE,
.mask_base = HI655X_IRQ_MASK_BASE,
};
static struct regmap_config hi655x_regmap_config = {
.reg_bits = 32,
.reg_stride = HI655X_STRIDE,
.val_bits = 8,
.max_register = HI655X_BUS_ADDR(0xFFF),
};
static struct resource pwrkey_resources[] = {
{
.name = "down",
.start = PWRON_D20R_INT,
.end = PWRON_D20R_INT,
.flags = IORESOURCE_IRQ,
}, {
.name = "up",
.start = PWRON_D20F_INT,
.end = PWRON_D20F_INT,
.flags = IORESOURCE_IRQ,
}, {
.name = "hold 4s",
.start = PWRON_D4SR_INT,
.end = PWRON_D4SR_INT,
.flags = IORESOURCE_IRQ,
},
};
static const struct mfd_cell hi655x_pmic_devs[] = {
{
.name = "hi65xx-powerkey",
.num_resources = ARRAY_SIZE(pwrkey_resources),
.resources = &pwrkey_resources[0],
},
{ .name = "hi655x-regulator", },
{ .name = "hi655x-clk", },
};
static void hi655x_local_irq_clear(struct regmap *map)
{
int i;
regmap_write(map, HI655X_ANA_IRQM_BASE, HI655X_IRQ_CLR);
for (i = 0; i < HI655X_IRQ_ARRAY; i++) {
regmap_write(map, HI655X_IRQ_STAT_BASE + i * HI655X_STRIDE,
HI655X_IRQ_CLR);
}
}
static int hi655x_pmic_probe(struct platform_device *pdev)
{
int ret;
struct hi655x_pmic *pmic;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
void __iomem *base;
pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL);
if (!pmic)
return -ENOMEM;
pmic->dev = dev;
pmic->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(dev, pmic->res);
if (IS_ERR(base))
return PTR_ERR(base);
pmic->regmap = devm_regmap_init_mmio_clk(dev, NULL, base,
&hi655x_regmap_config);
regmap_read(pmic->regmap, HI655X_BUS_ADDR(HI655X_VER_REG), &pmic->ver);
if ((pmic->ver < PMU_VER_START) || (pmic->ver > PMU_VER_END)) {
dev_warn(dev, "PMU version %d unsupported\n", pmic->ver);
return -EINVAL;
}
hi655x_local_irq_clear(pmic->regmap);
pmic->gpio = of_get_named_gpio(np, "pmic-gpios", 0);
if (!gpio_is_valid(pmic->gpio)) {
dev_err(dev, "Failed to get the pmic-gpios\n");
return -ENODEV;
}
ret = devm_gpio_request_one(dev, pmic->gpio, GPIOF_IN,
"hi655x_pmic_irq");
if (ret < 0) {
dev_err(dev, "Failed to request gpio %d ret = %d\n",
pmic->gpio, ret);
return ret;
}
ret = regmap_add_irq_chip(pmic->regmap, gpio_to_irq(pmic->gpio),
IRQF_TRIGGER_LOW | IRQF_NO_SUSPEND, 0,
&hi655x_irq_chip, &pmic->irq_data);
if (ret) {
dev_err(dev, "Failed to obtain 'hi655x_pmic_irq' %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, pmic);
ret = mfd_add_devices(dev, PLATFORM_DEVID_AUTO, hi655x_pmic_devs,
ARRAY_SIZE(hi655x_pmic_devs), NULL, 0,
regmap_irq_get_domain(pmic->irq_data));
if (ret) {
dev_err(dev, "Failed to register device %d\n", ret);
regmap_del_irq_chip(gpio_to_irq(pmic->gpio), pmic->irq_data);
return ret;
}
return 0;
}
static int hi655x_pmic_remove(struct platform_device *pdev)
{
struct hi655x_pmic *pmic = platform_get_drvdata(pdev);
regmap_del_irq_chip(gpio_to_irq(pmic->gpio), pmic->irq_data);
mfd_remove_devices(&pdev->dev);
return 0;
}
static const struct of_device_id hi655x_pmic_match[] = {
{ .compatible = "hisilicon,hi655x-pmic", },
{},
};
MODULE_DEVICE_TABLE(of, hi655x_pmic_match);
static struct platform_driver hi655x_pmic_driver = {
.driver = {
.name = "hi655x-pmic",
.of_match_table = of_match_ptr(hi655x_pmic_match),
},
.probe = hi655x_pmic_probe,
.remove = hi655x_pmic_remove,
};
module_platform_driver(hi655x_pmic_driver);
MODULE_AUTHOR("Chen Feng <puck.chen@hisilicon.com>");
MODULE_DESCRIPTION("Hisilicon hi655x PMIC driver");
MODULE_LICENSE("GPL v2");