blob: c1fc50d1d2ce6d7e6e64e1fd65967ce18a02e1d1 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/mfd/core.h>
#include <linux/of.h>
#include <linux/regulator/of_regulator.h>
#include <linux/gpio/consumer.h>
#include <linux/amlogic/pmic/meson_pmic6.h>
#define PMIC6_IRQ_MASK0_OFFSET 0
#define PMIC6_IRQ_MASK1_OFFSET 1
#define PMIC6_IRQ_MASK2_OFFSET 2
#define PMIC6_IRQ_MASK3_OFFSET 3
static struct resource meson_pmic_regulators_resources[] = {
{
.name = "LDO1_OV",
.start = PMIC6_IRQ_LDO1_OV,
.end = PMIC6_IRQ_LDO1_OV,
.flags = IORESOURCE_IRQ,
},
{
.name = "LDO1_UV",
.start = PMIC6_IRQ_LDO1_UV,
.end = PMIC6_IRQ_LDO1_UV,
.flags = IORESOURCE_IRQ,
}
};
static struct resource meson_pmic_onkey_resources[] = {
{
.name = "PWRKEY",
.start = PMIC6_IRQ_PWR_KEY,
.end = PMIC6_IRQ_PWR_KEY,
.flags = IORESOURCE_IRQ,
},
};
static const struct mfd_cell meson_pmic_common_devs[] = {
{
.name = PMIC6_DRVNAME_REGULATORS,
.num_resources = ARRAY_SIZE(meson_pmic_regulators_resources),
.resources = meson_pmic_regulators_resources,
},
{
.name = PMIC6_DRVNAME_LEDS,
},
{
.name = PMIC6_DRVNAME_WATCHDOG,
.of_compatible = "amlogic,pmic6-watchdog",
},
{
.name = PMIC6_DRVNAME_GPIO,
.of_compatible = "amlogic,pmic6-gpio",
},
{
.name = PMIC6_DRVNAME_ONKEY,
.num_resources = ARRAY_SIZE(meson_pmic_onkey_resources),
.resources = meson_pmic_onkey_resources,
.of_compatible = "amlogic,pmic6-pwrkey",
},
{
.name = PMIC6_DRVNAME_BATTERY,
.of_compatible = "amlogic,pmic6-battery",
},
};
static const struct regmap_range meson_pmic_readable_ranges[] = {
regmap_reg_range(PMIC6_POWER_UP_SEL0, PMIC6_REG_MAX),
};
static const struct regmap_range meson_pmic_writeable_ranges[] = {
regmap_reg_range(PMIC6_POWER_UP_SEL0, PMIC6_REG_MAX),
};
static const struct regmap_range meson_pmic_volatile_ranges[] = {
regmap_reg_range(PMIC6_POWER_UP_SEL0, PMIC6_REG_MAX),
};
static const struct regmap_access_table meson_pmic_readable_table = {
.yes_ranges = meson_pmic_readable_ranges,
.n_yes_ranges = ARRAY_SIZE(meson_pmic_readable_ranges),
};
static const struct regmap_access_table meson_pmic_writeable_table = {
.yes_ranges = meson_pmic_writeable_ranges,
.n_yes_ranges = ARRAY_SIZE(meson_pmic_writeable_ranges),
};
static const struct regmap_access_table meson_pmic_volatile_table = {
.yes_ranges = meson_pmic_volatile_ranges,
.n_yes_ranges = ARRAY_SIZE(meson_pmic_volatile_ranges),
};
static bool meson_pmic_volatile_reg(struct device *dev, unsigned int reg)
{
return true;
}
static struct regmap_config meson_pmic_regmap_config = {
.reg_bits = 16,
.val_bits = 8,
.volatile_reg = meson_pmic_volatile_reg,
.max_register = PMIC6_REG_MAX,
.reg_format_endian = REGMAP_ENDIAN_LITTLE,
.cache_type = REGCACHE_RBTREE,
};
static const struct regmap_irq meson_pmic_irqs[] = {
/* PMIC6 IRQ status 0 register */
REGMAP_IRQ_REG(PMIC6_IRQ_NTC_UNT,
PMIC6_IRQ_MASK0_OFFSET, BIT(0)),
REGMAP_IRQ_REG(PMIC6_IRQ_NTC_OVT,
PMIC6_IRQ_MASK0_OFFSET, BIT(1)),
REGMAP_IRQ_REG(PMIC6_IRQ_CHG_DCIN_UV_LV,
PMIC6_IRQ_MASK0_OFFSET, BIT(2)),
REGMAP_IRQ_REG(PMIC6_IRQ_CHG_DCIN_OV_LV,
PMIC6_IRQ_MASK0_OFFSET, BIT(3)),
REGMAP_IRQ_REG(PMIC6_IRQ_CHG_OTP,
PMIC6_IRQ_MASK0_OFFSET, BIT(4)),
REGMAP_IRQ_REG(PMIC6_IRQ_CHG_OCP,
PMIC6_IRQ_MASK0_OFFSET, BIT(5)),
REGMAP_IRQ_REG(PMIC6_IRQ_CHG_OVP,
PMIC6_IRQ_MASK0_OFFSET, BIT(6)),
REGMAP_IRQ_REG(PMIC6_IRQ_CHG_DCIN_OK,
PMIC6_IRQ_MASK0_OFFSET, BIT(7)),
/* PMIC6 IRQ status 1 register */
REGMAP_IRQ_REG(PMIC6_IRQ_CHG_TIMEOUT,
PMIC6_IRQ_MASK1_OFFSET, BIT(0)),
REGMAP_IRQ_REG(PMIC6_IRQ_CHG_CHGEND,
PMIC6_IRQ_MASK1_OFFSET, BIT(1)),
REGMAP_IRQ_REG(PMIC6_IRQ_DC1_UV,
PMIC6_IRQ_MASK1_OFFSET, BIT(2)),
REGMAP_IRQ_REG(PMIC6_IRQ_DC1_OC,
PMIC6_IRQ_MASK1_OFFSET, BIT(3)),
REGMAP_IRQ_REG(PMIC6_IRQ_DC2_UV,
PMIC6_IRQ_MASK1_OFFSET, BIT(4)),
REGMAP_IRQ_REG(PMIC6_IRQ_DC2_OC,
PMIC6_IRQ_MASK1_OFFSET, BIT(5)),
REGMAP_IRQ_REG(PMIC6_IRQ_DC3_UV,
PMIC6_IRQ_MASK1_OFFSET, BIT(6)),
REGMAP_IRQ_REG(PMIC6_IRQ_DC3_OC,
PMIC6_IRQ_MASK1_OFFSET, BIT(7)),
/* PMIC6 IRQ status 2 register */
REGMAP_IRQ_REG(PMIC6_IRQ_VSYS_UVLO,
PMIC6_IRQ_MASK2_OFFSET, BIT(1)),
REGMAP_IRQ_REG(PMIC6_IRQ_LDO1_OV,
PMIC6_IRQ_MASK2_OFFSET, BIT(2)),
REGMAP_IRQ_REG(PMIC6_IRQ_LDO1_UV,
PMIC6_IRQ_MASK2_OFFSET, BIT(3)),
REGMAP_IRQ_REG(PMIC6_IRQ_LDO1_OC,
PMIC6_IRQ_MASK2_OFFSET, BIT(4)),
REGMAP_IRQ_REG(PMIC6_IRQ_LDO2_OV,
PMIC6_IRQ_MASK2_OFFSET, BIT(5)),
REGMAP_IRQ_REG(PMIC6_IRQ_LDO2_UV,
PMIC6_IRQ_MASK2_OFFSET, BIT(6)),
REGMAP_IRQ_REG(PMIC6_IRQ_LDO2_OC,
PMIC6_IRQ_MASK2_OFFSET, BIT(7)),
/* PMIC6 IRQ status 3 register */
REGMAP_IRQ_REG(PMIC6_IRQ_LDO3_OV,
PMIC6_IRQ_MASK3_OFFSET, BIT(0)),
REGMAP_IRQ_REG(PMIC6_IRQ_LDO3_UV,
PMIC6_IRQ_MASK3_OFFSET, BIT(1)),
REGMAP_IRQ_REG(PMIC6_IRQ_LDO3_OC,
PMIC6_IRQ_MASK3_OFFSET, BIT(2)),
REGMAP_IRQ_REG(PMIC6_IRQ_PWR_KEY,
PMIC6_IRQ_MASK3_OFFSET, BIT(6)),
REGMAP_IRQ_REG(PMIC6_IRQ_SAR,
PMIC6_IRQ_MASK3_OFFSET, BIT(7)),
};
static const struct regmap_irq_chip meson_pmic_irq_chip = {
.name = "meson_pmic-irq",
.irqs = meson_pmic_irqs,
.num_irqs = ARRAY_SIZE(meson_pmic_irqs),
.num_regs = 4,
.status_base = PMIC6_IRQ_STATUS_CLR0,
.mask_base = PMIC6_IRQ_MASK0,
.ack_base = PMIC6_IRQ_STATUS_CLR0,
.init_ack_masked = true,
.mask_invert = true,
.ack_invert = true,
};
int meson_pmic_irq_init(struct meson_pmic *meson_pmic)
{
const struct regmap_irq_chip *irq_chip;
int ret;
if (!meson_pmic->chip_irq) {
dev_err(meson_pmic->dev, "No IRQ configured\n");
return -EINVAL;
}
irq_chip = &meson_pmic_irq_chip;
ret = devm_regmap_add_irq_chip(meson_pmic->dev, meson_pmic->regmap,
meson_pmic->chip_irq, IRQF_TRIGGER_HIGH |
IRQF_ONESHOT | IRQF_SHARED,
meson_pmic->irq_base, irq_chip,
&meson_pmic->regmap_irq);
if (ret) {
dev_err(meson_pmic->dev, "Failed to reguest IRQ %d: %d\n",
meson_pmic->chip_irq, ret);
return ret;
}
return 0;
}
int meson_pmic_device_init(struct meson_pmic *meson_pmic, unsigned int irq)
{
struct pmic6_pdata *pdata = meson_pmic->dev->platform_data;
int ret;
if (pdata) {
meson_pmic->flags = pdata->flags;
meson_pmic->irq_base = pdata->irq_base;
} else {
meson_pmic->flags = 0;
meson_pmic->irq_base = -1;
}
meson_pmic->chip_irq = irq;
if (pdata && pdata->init) {
ret = pdata->init(meson_pmic);
if (ret != 0) {
dev_err(meson_pmic->dev,
"Platform initialization failed.\n");
return ret;
}
}
ret = meson_pmic_irq_init(meson_pmic);
if (ret) {
dev_err(meson_pmic->dev, "Cannot initialize interrupts.\n");
return ret;
}
meson_pmic->irq_base = regmap_irq_chip_get_base(meson_pmic->regmap_irq);
ret = devm_mfd_add_devices(meson_pmic->dev, PLATFORM_DEVID_NONE,
meson_pmic_common_devs,
ARRAY_SIZE(meson_pmic_common_devs),
NULL, meson_pmic->irq_base, NULL);
if (ret) {
dev_err(meson_pmic->dev, "Failed to add child devices\n");
return ret;
}
return ret;
}
static const struct of_device_id meson_pmic_dt_ids[] = {
{ .compatible = "amlogic,pmic6", },
{ }
};
MODULE_DEVICE_TABLE(of, meson_pmic_dt_ids);
static ssize_t read_reg_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = container_of(dev, struct i2c_client,
dev);
int ret;
int reg = 0;
u32 val;
struct meson_pmic *pmic = i2c_get_clientdata(client);
ret = kstrtoint(buf, 16, &reg);
if (ret) {
pr_err("%s: %d\n", __func__, __LINE__);
return ret;
}
ret = regmap_read(pmic->regmap, reg, &val);
if (ret)
pr_err("%s: %d\n", __func__, __LINE__);
pr_info("[0x%x] = 0x%x\n", reg, val);
return strlen(buf);
}
static ssize_t write_reg_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = container_of(dev, struct i2c_client,
dev);
int ret, i = 0, j = 0;
struct meson_pmic *pmic = i2c_get_clientdata(client);
int reg = 0, val = 0;
char tone[20];
while (buf[i] != '\0') {
if (buf[i] == 's') {
tone[j] = '\0';
ret = kstrtoint(tone, 16, &reg);
if (ret) {
pr_err("%s: %d\n", __func__, __LINE__);
return ret;
}
break;
}
tone[j] = buf[i];
i++;
j++;
}
ret = kstrtoint(&buf[i + 1], 16, &val);
if (ret) {
pr_err("%s: %d\n", __func__, __LINE__);
return ret;
}
ret = regmap_write(pmic->regmap, reg, val);
if (ret)
pr_err("%s: %d\n", __func__, __LINE__);
pr_info("[0x%x] = 0x%x\n", reg, val);
return strlen(buf);
}
static DEVICE_ATTR_WO(read_reg);
static DEVICE_ATTR_WO(write_reg);
static struct attribute *pmic_attrs[] = {
&dev_attr_read_reg.attr,
&dev_attr_write_reg.attr,
NULL,
};
static struct attribute_group pmic_attr_group = {
.attrs = pmic_attrs,
};
static int meson_pmic_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct meson_pmic *meson_pmic;
struct gpio_desc *pmic_irq;
int ret;
meson_pmic = devm_kzalloc(&i2c->dev,
sizeof(struct meson_pmic),
GFP_KERNEL);
if (!meson_pmic)
return -ENOMEM;
i2c_set_clientdata(i2c, meson_pmic);
meson_pmic->dev = &i2c->dev;
pmic_irq = gpiod_get(&i2c->dev, "pmic_irq", GPIOD_IN);
if (IS_ERR(pmic_irq)) {
dev_err(meson_pmic->dev, "Failed to claim gpio\n");
return PTR_ERR(pmic_irq);
}
meson_pmic->chip_irq = gpiod_to_irq(pmic_irq);
meson_pmic_regmap_config.rd_table = &meson_pmic_readable_table;
meson_pmic_regmap_config.wr_table = &meson_pmic_writeable_table;
meson_pmic_regmap_config.volatile_table = &meson_pmic_volatile_table;
meson_pmic->regmap = devm_regmap_init_i2c(i2c,
&meson_pmic_regmap_config);
if (IS_ERR(meson_pmic->regmap)) {
ret = PTR_ERR(meson_pmic->regmap);
dev_err(meson_pmic->dev, "Failed to allocate register map: %d\n",
ret);
return ret;
}
ret = sysfs_create_group(&meson_pmic->dev->kobj, &pmic_attr_group);
if (ret) {
dev_err(meson_pmic->dev,
"pmic sysfs group creation failed: %d\n", ret);
}
return meson_pmic_device_init(meson_pmic, meson_pmic->chip_irq);
}
static const struct i2c_device_id meson_pmic_i2c_id[] = {
{ "meson_pmic",},
{},
};
MODULE_DEVICE_TABLE(i2c, meson_pmic_i2c_id);
static struct i2c_driver meson_pmic_driver = {
.driver = {
.name = "meson_pmic",
.of_match_table = of_match_ptr(meson_pmic_dt_ids),
},
.probe = meson_pmic_probe,
.id_table = meson_pmic_i2c_id,
};
static int __init meson_pmic_i2c_init(void)
{
return i2c_add_driver(&meson_pmic_driver);
}
static void __exit meson_pmic_i2c_exit(void)
{
return i2c_del_driver(&meson_pmic_driver);
}
postcore_initcall(meson_pmic_i2c_init);
module_exit(meson_pmic_i2c_exit);
MODULE_DESCRIPTION("PMIC mfd driver for Amlogic");
MODULE_AUTHOR("Amlogic");
MODULE_LICENSE("GPL v2");