322 lines
7.9 KiB
C
322 lines
7.9 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2022 MediaTek Inc.
|
|
*/
|
|
|
|
#include <dt-bindings/mfd/richtek,rt9490.h>
|
|
#include <linux/bits.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/irqdomain.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/mfd/core.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#define RT9490_REG_CHG_IRQ_FLAG0 0x22
|
|
#define RT9490_REG_CHG_IRQ_MASK0 0x28
|
|
#define RT9490_REG_DEVICE_INFO 0x48
|
|
#define RT9490_REG_ADD_IRQ_FLAG 0x4D
|
|
#define RT9490_REG_ADD_IRQ_MASK 0x4E
|
|
|
|
#define RT9490_NUM_IRQ_REGS 7
|
|
#define RT9490_ADD_IRQ_OFFSET 6
|
|
#define RT9490_DEVICE_ID_MASK GENMASK(6, 3)
|
|
#define RT9490_VENDOR_ID 0x60
|
|
//#define RT9490_VENDOR_ID 0x00
|
|
|
|
struct rt9490_data {
|
|
struct i2c_client *i2c;
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct irq_domain *irq_domain;
|
|
struct mutex lock;
|
|
struct mutex hidden_mode_lock;
|
|
struct irq_chip irq_chip;
|
|
u8 mask_buf[RT9490_NUM_IRQ_REGS];
|
|
int hidden_mode_cnt;
|
|
};
|
|
|
|
static const struct mfd_cell rt9490_devs[] = {
|
|
MFD_CELL_OF("rt9490-adc", NULL, NULL, 0, 0, "richtek,rt9490-adc"),
|
|
MFD_CELL_OF("rt9490-chg", NULL, NULL, 0, 0, "richtek,rt9490-chg")
|
|
};
|
|
|
|
static void rt9490_irq_bus_lock(struct irq_data *irq_data)
|
|
{
|
|
struct rt9490_data *data = irq_data_get_irq_chip_data(irq_data);
|
|
|
|
mutex_lock(&data->lock);
|
|
}
|
|
|
|
static void rt9490_irq_bus_unlock(struct irq_data *irq_data)
|
|
{
|
|
struct rt9490_data *data = irq_data_get_irq_chip_data(irq_data);
|
|
unsigned int reg_offset = irq_data->hwirq / 8;
|
|
unsigned int reg_addr;
|
|
int ret;
|
|
|
|
if (reg_offset == RT9490_ADD_IRQ_OFFSET)
|
|
reg_addr = RT9490_REG_ADD_IRQ_MASK;
|
|
else
|
|
reg_addr = RT9490_REG_CHG_IRQ_MASK0 + reg_offset;
|
|
|
|
ret = regmap_write(data->regmap, reg_addr, data->mask_buf[reg_offset]);
|
|
if (ret)
|
|
dev_err(data->dev, "Failed to write mask [%d]\n", reg_offset);
|
|
|
|
mutex_unlock(&data->lock);
|
|
}
|
|
|
|
static void rt9490_irq_enable(struct irq_data *irq_data)
|
|
{
|
|
struct rt9490_data *data = irq_data_get_irq_chip_data(irq_data);
|
|
|
|
data->mask_buf[irq_data->hwirq / 8] &= ~BIT(irq_data->hwirq % 8);
|
|
}
|
|
|
|
static void rt9490_irq_disable(struct irq_data *irq_data)
|
|
{
|
|
struct rt9490_data *data = irq_data_get_irq_chip_data(irq_data);
|
|
|
|
data->mask_buf[irq_data->hwirq / 8] |= BIT(irq_data->hwirq % 8);
|
|
}
|
|
|
|
static int rt9490_irq_map(struct irq_domain *h, unsigned int virq,
|
|
irq_hw_number_t hwirq)
|
|
{
|
|
struct rt9490_data *data = h->host_data;
|
|
struct i2c_client *i2c = to_i2c_client(data->dev);
|
|
|
|
irq_set_chip_data(virq, data);
|
|
irq_set_chip(virq, &data->irq_chip);
|
|
irq_set_nested_thread(virq, true);
|
|
irq_set_parent(virq, i2c->irq);
|
|
irq_set_noprobe(virq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct irq_domain_ops rt9490_irq_domain_ops = {
|
|
.map = rt9490_irq_map,
|
|
.xlate = irq_domain_xlate_onetwocell,
|
|
};
|
|
|
|
static irqreturn_t rt9490_irq_handler(int irq, void *priv)
|
|
{
|
|
struct rt9490_data *data = priv;
|
|
u8 status_buf[RT9490_NUM_IRQ_REGS], evts;
|
|
unsigned int add_event;
|
|
bool handled = false;
|
|
int i, j, ret;
|
|
|
|
ret = regmap_raw_read(data->regmap, RT9490_REG_CHG_IRQ_FLAG0,
|
|
status_buf, RT9490_ADD_IRQ_OFFSET);
|
|
|
|
ret |= regmap_read(data->regmap, RT9490_REG_ADD_IRQ_FLAG, &add_event);
|
|
if (ret) {
|
|
dev_err(data->dev, "Failed to read irq flag\n");
|
|
return IRQ_NONE;
|
|
}
|
|
status_buf[RT9490_ADD_IRQ_OFFSET] = add_event;
|
|
|
|
for (i = 0; i < RT9490_NUM_IRQ_REGS; i++) {
|
|
evts = status_buf[i] & ~data->mask_buf[i];
|
|
if (!evts)
|
|
continue;
|
|
|
|
for (j = 0; j < 8; j++) {
|
|
if (!(evts & BIT(j)))
|
|
continue;
|
|
|
|
handle_nested_irq(irq_find_mapping(data->irq_domain,
|
|
i * 8 + j));
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
return handled ? IRQ_HANDLED : IRQ_NONE;
|
|
}
|
|
|
|
static int rt9490_add_irq_chip(struct rt9490_data *data)
|
|
{
|
|
struct i2c_client *i2c = to_i2c_client(data->dev);
|
|
u8 status_buf[RT9490_ADD_IRQ_OFFSET];
|
|
unsigned int add_event;
|
|
int ret;
|
|
|
|
mutex_init(&data->lock);
|
|
|
|
data->irq_chip.name = dev_name(data->dev);
|
|
data->irq_chip.irq_bus_lock = rt9490_irq_bus_lock;
|
|
data->irq_chip.irq_bus_sync_unlock = rt9490_irq_bus_unlock;
|
|
data->irq_chip.irq_enable = rt9490_irq_enable;
|
|
data->irq_chip.irq_disable = rt9490_irq_disable;
|
|
|
|
/* Mask and read clear all events by default */
|
|
memset(data->mask_buf, 0xff, RT9490_NUM_IRQ_REGS);
|
|
ret = regmap_raw_write(data->regmap, RT9490_REG_CHG_IRQ_MASK0,
|
|
data->mask_buf, RT9490_ADD_IRQ_OFFSET);
|
|
|
|
ret |= regmap_write(data->regmap, RT9490_REG_ADD_IRQ_MASK,
|
|
data->mask_buf[RT9490_ADD_IRQ_OFFSET]);
|
|
if (ret) {
|
|
dev_err(data->dev, "Failed to write irq mask\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = regmap_raw_read(data->regmap, RT9490_REG_CHG_IRQ_FLAG0,
|
|
status_buf, RT9490_ADD_IRQ_OFFSET);
|
|
|
|
ret |= regmap_read(data->regmap, RT9490_REG_ADD_IRQ_FLAG, &add_event);
|
|
if (ret) {
|
|
dev_err(data->dev, "Failed to read clear irq flag\n");
|
|
return ret;
|
|
}
|
|
|
|
data->irq_domain = irq_domain_add_linear(data->dev->of_node,
|
|
RT9490_NUM_IRQ_REGS * 8,
|
|
&rt9490_irq_domain_ops, data);
|
|
if (!data->irq_domain) {
|
|
dev_err(data->dev, "Failed to create irq domain\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = request_threaded_irq(i2c->irq, NULL, rt9490_irq_handler,
|
|
IRQF_ONESHOT, dev_name(data->dev), data);
|
|
if (ret) {
|
|
dev_err(data->dev, "Failed to request irq(%d)\n", ret);
|
|
irq_domain_remove(data->irq_domain);
|
|
return ret;
|
|
}
|
|
|
|
device_init_wakeup(data->dev, true);
|
|
return 0;
|
|
}
|
|
|
|
static void rt9490_del_irq_chip(struct rt9490_data *data)
|
|
{
|
|
struct i2c_client *i2c = to_i2c_client(data->dev);
|
|
|
|
device_init_wakeup(data->dev, false);
|
|
free_irq(i2c->irq, data);
|
|
irq_domain_remove(data->irq_domain);
|
|
mutex_destroy(&data->lock);
|
|
}
|
|
|
|
static const struct regmap_config rt9490_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
.max_register = RT9490_REG_ADD_IRQ_MASK,
|
|
};
|
|
|
|
static int rt9490_check_vendor_info(struct rt9490_data *data)
|
|
{
|
|
unsigned int devid;
|
|
int ret;
|
|
|
|
ret = regmap_read(data->regmap, RT9490_REG_DEVICE_INFO, &devid);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if ((devid & RT9490_DEVICE_ID_MASK) != RT9490_VENDOR_ID) {
|
|
dev_err(data->dev, "VID not correct [0x%02x]\n", devid);
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_probe(struct i2c_client *i2c)
|
|
{
|
|
struct rt9490_data *data;
|
|
int ret;
|
|
|
|
data = devm_kzalloc(&i2c->dev, sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
mutex_init(&data->hidden_mode_lock);
|
|
data->hidden_mode_cnt = 0;
|
|
data->dev = &i2c->dev;
|
|
data->i2c = i2c;
|
|
i2c_set_clientdata(i2c, data);
|
|
|
|
data->regmap = devm_regmap_init_i2c(i2c, &rt9490_regmap_config);
|
|
if (IS_ERR(data->regmap))
|
|
return PTR_ERR(data->regmap);
|
|
|
|
ret = rt9490_check_vendor_info(data);
|
|
if (ret) {
|
|
dev_err(&i2c->dev, "Failed to check vendor info\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = rt9490_add_irq_chip(data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, rt9490_devs,
|
|
ARRAY_SIZE(rt9490_devs), NULL, 0,
|
|
data->irq_domain);
|
|
if (ret) {
|
|
dev_err(&i2c->dev, "Failed to add sub devices\n");
|
|
rt9490_del_irq_chip(data);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rt9490_remove(struct i2c_client *i2c)
|
|
{
|
|
struct rt9490_data *data = i2c_get_clientdata(i2c);
|
|
|
|
rt9490_del_irq_chip(data);
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused rt9490_suspend(struct device *dev)
|
|
{
|
|
struct i2c_client *i2c = to_i2c_client(dev);
|
|
|
|
if (device_may_wakeup(dev))
|
|
enable_irq_wake(i2c->irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused rt9490_resume(struct device *dev)
|
|
{
|
|
struct i2c_client *i2c = to_i2c_client(dev);
|
|
|
|
if (device_may_wakeup(dev))
|
|
disable_irq_wake(i2c->irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(rt9490_pm_ops, rt9490_suspend, rt9490_resume);
|
|
|
|
static const struct of_device_id rt9490_of_match_table[] = {
|
|
{ .compatible = "richtek,rt9490", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, rt9490_of_match_table);
|
|
|
|
static struct i2c_driver rt9490_driver = {
|
|
.driver = {
|
|
.name = "rt9490",
|
|
.of_match_table = rt9490_of_match_table,
|
|
.pm = &rt9490_pm_ops,
|
|
},
|
|
.probe_new = rt9490_probe,
|
|
.remove = rt9490_remove,
|
|
};
|
|
module_i2c_driver(rt9490_driver);
|
|
|
|
MODULE_DESCRIPTION("Richtek RT9490 MFD driver");
|
|
MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
|
|
MODULE_LICENSE("GPL");
|