mirror of
https://github.com/physwizz/a155-U-u1.git
synced 2024-11-19 13:27:49 +00:00
1060 lines
26 KiB
C
1060 lines
26 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// Copyright (c) 2014-2018 MediaTek Inc.
|
|
|
|
/*
|
|
* Library for MediaTek External Interrupt Support
|
|
*
|
|
* Author: Maoguang Meng <maoguang.meng@mediatek.com>
|
|
* Sean Wang <sean.wang@mediatek.com>
|
|
* Po-Kai Chi <pk.chi@mediatek.com>
|
|
*
|
|
*/
|
|
|
|
#include <linux/atomic.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/gpio/driver.h>
|
|
#include <linux/io.h>
|
|
#include <linux/irqchip/chained_irq.h>
|
|
#include <linux/irqdomain.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include "mtk-eint.h"
|
|
|
|
#define MTK_EINT_EDGE_SENSITIVE 0
|
|
#define MTK_EINT_LEVEL_SENSITIVE 1
|
|
#define MTK_EINT_DBNC_SET_DBNC_BITS 4
|
|
#define MTK_EINT_DBNC_RST_BIT (0x1 << 1)
|
|
#define MTK_EINT_DBNC_SET_EN (0x1 << 0)
|
|
|
|
#define MTK_EINT_NO_OFFSET 0
|
|
|
|
static struct mtk_eint *global_eintc;
|
|
|
|
static const struct mtk_eint_regs mtk_generic_eint_regs = {
|
|
.stat = 0x000,
|
|
.ack = 0x040,
|
|
.mask = 0x080,
|
|
.mask_set = 0x0c0,
|
|
.mask_clr = 0x100,
|
|
.sens = 0x140,
|
|
.sens_set = 0x180,
|
|
.sens_clr = 0x1c0,
|
|
.soft = 0x200,
|
|
.soft_set = 0x240,
|
|
.soft_clr = 0x280,
|
|
.pol = 0x300,
|
|
.pol_set = 0x340,
|
|
.pol_clr = 0x380,
|
|
.dom_en = 0x400,
|
|
.dbnc_ctrl = 0x500,
|
|
.dbnc_set = 0x600,
|
|
.dbnc_clr = 0x700,
|
|
.raw_stat = 0xa00,
|
|
};
|
|
|
|
/*
|
|
* Return the iomem of specific register offset and decode the coordinate
|
|
* (instance, index) from global eint number.
|
|
* If return NULL, then it must be either out-of-range or do-not-support.
|
|
*/
|
|
static void __iomem *mtk_eint_get_offset(struct mtk_eint *eint,
|
|
unsigned int eint_num,
|
|
unsigned int offset,
|
|
unsigned int *instance,
|
|
unsigned int *index)
|
|
{
|
|
void __iomem *reg;
|
|
|
|
if (eint_num >= eint->total_pin_number ||
|
|
!eint->pins[eint_num].enabled) {
|
|
WARN_ON(1);
|
|
return NULL;
|
|
}
|
|
|
|
*instance = eint->pins[eint_num].instance;
|
|
*index = eint->pins[eint_num].index;
|
|
reg = eint->instances[*instance].base + offset + (*index / 32 * 4);
|
|
|
|
return reg;
|
|
}
|
|
|
|
/*
|
|
* Generate helper function to access property register of a dedicate pin.
|
|
*/
|
|
#define DEFINE_EINT_GET_FUNCTION(_NAME, _OFFSET) \
|
|
static unsigned int mtk_eint_get_##_NAME(struct mtk_eint *eint, \
|
|
unsigned int eint_num) \
|
|
{ \
|
|
unsigned int instance, index; \
|
|
void __iomem *reg = mtk_eint_get_offset(eint, eint_num, \
|
|
_OFFSET, \
|
|
&instance, &index); \
|
|
unsigned int bit = BIT(index & 0x1f);\
|
|
\
|
|
if (!reg) { \
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n", \
|
|
__func__, eint_num); \
|
|
return 0;\
|
|
} \
|
|
\
|
|
return !!(readl(reg) & bit); \
|
|
}
|
|
|
|
DEFINE_EINT_GET_FUNCTION(stat, eint->comp->regs->stat);
|
|
DEFINE_EINT_GET_FUNCTION(mask, eint->comp->regs->mask);
|
|
DEFINE_EINT_GET_FUNCTION(sens, eint->comp->regs->sens);
|
|
DEFINE_EINT_GET_FUNCTION(pol, eint->comp->regs->pol);
|
|
DEFINE_EINT_GET_FUNCTION(soft, eint->comp->regs->soft);
|
|
DEFINE_EINT_GET_FUNCTION(raw_stat, eint->comp->regs->raw_stat);
|
|
|
|
static unsigned int mtk_eint_can_en_debounce(struct mtk_eint *eint,
|
|
unsigned int eint_num)
|
|
{
|
|
unsigned int sens;
|
|
unsigned int instance, index;
|
|
void __iomem *reg = mtk_eint_get_offset(eint, eint_num,
|
|
eint->comp->regs->sens,
|
|
&instance, &index);
|
|
unsigned int bit = BIT(index & 0x1f);
|
|
|
|
if (!reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, eint_num);
|
|
return 0;
|
|
}
|
|
|
|
if (readl(reg) & bit)
|
|
sens = MTK_EINT_LEVEL_SENSITIVE;
|
|
else
|
|
sens = MTK_EINT_EDGE_SENSITIVE;
|
|
|
|
if (eint->pins[eint_num].debounce &&
|
|
sens != MTK_EINT_EDGE_SENSITIVE)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int mtk_eint_flip_edge(struct mtk_eint *eint, int eint_num)
|
|
{
|
|
int start_level, curr_level;
|
|
unsigned int reg_offset;
|
|
unsigned int instance, index, mask, port;
|
|
void __iomem *reg;
|
|
|
|
reg = mtk_eint_get_offset(eint, eint_num, MTK_EINT_NO_OFFSET,
|
|
&instance, &index);
|
|
|
|
if (!reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, eint_num);
|
|
return 0;
|
|
}
|
|
|
|
mask = BIT(index & 0x1f);
|
|
port = index >> 5;
|
|
reg = eint->instances[instance].base + port * 4;
|
|
|
|
curr_level = eint->gpio_xlate->get_gpio_state(eint->pctl, eint_num);
|
|
|
|
do {
|
|
start_level = curr_level;
|
|
if (start_level)
|
|
reg_offset = eint->comp->regs->pol_clr;
|
|
else
|
|
reg_offset = eint->comp->regs->pol_set;
|
|
|
|
writel(mask, reg + reg_offset);
|
|
|
|
curr_level = eint->gpio_xlate->get_gpio_state(eint->pctl,
|
|
eint_num);
|
|
} while (start_level != curr_level);
|
|
|
|
return start_level;
|
|
}
|
|
|
|
static void mtk_eint_mask(struct irq_data *d)
|
|
{
|
|
struct mtk_eint *eint = irq_data_get_irq_chip_data(d);
|
|
unsigned int instance, index;
|
|
void __iomem *reg = mtk_eint_get_offset(eint, d->hwirq,
|
|
eint->comp->regs->mask_set,
|
|
&instance, &index);
|
|
u32 mask = BIT(index & 0x1f);
|
|
|
|
if (!reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, d->hwirq);
|
|
return;
|
|
}
|
|
|
|
eint->instances[instance].cur_mask[index >> 5] &= ~mask;
|
|
|
|
writel(mask, reg);
|
|
}
|
|
|
|
static void mtk_eint_unmask(struct irq_data *d)
|
|
{
|
|
struct mtk_eint *eint = irq_data_get_irq_chip_data(d);
|
|
unsigned int instance, index;
|
|
void __iomem *reg = mtk_eint_get_offset(eint, d->hwirq,
|
|
eint->comp->regs->mask_clr,
|
|
&instance, &index);
|
|
u32 mask = BIT(index & 0x1f);
|
|
|
|
if (!reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, d->hwirq);
|
|
return;
|
|
}
|
|
|
|
eint->instances[instance].cur_mask[index >> 5] |= mask;
|
|
|
|
writel(mask, reg);
|
|
|
|
if (eint->pins[d->hwirq].dual_edge)
|
|
mtk_eint_flip_edge(eint, d->hwirq);
|
|
}
|
|
|
|
/*
|
|
* We need to do extra effort to clear edge-triggered EINT
|
|
* which located in eint_c due to hw design limitation.
|
|
*/
|
|
void mt6983_eint_ack(struct irq_data *d)
|
|
{
|
|
struct mtk_eint *eint = irq_data_get_irq_chip_data(d);
|
|
unsigned int instance, index;
|
|
void __iomem *sens_reg,
|
|
*ack_reg = mtk_eint_get_offset(eint, d->hwirq,
|
|
eint->comp->regs->sens,
|
|
&instance, &index);
|
|
unsigned int bit = BIT(index & 0x1f);
|
|
|
|
if (!ack_reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, d->hwirq);
|
|
return;
|
|
}
|
|
|
|
if (instance == 4) {
|
|
sens_reg = mtk_eint_get_offset(eint, d->hwirq,
|
|
eint->comp->regs->sens_clr,
|
|
&instance, &index);
|
|
writel(bit, sens_reg);
|
|
sens_reg = mtk_eint_get_offset(eint, d->hwirq,
|
|
eint->comp->regs->sens_set,
|
|
&instance, &index);
|
|
writel(bit, sens_reg);
|
|
} else
|
|
writel(bit, ack_reg);
|
|
}
|
|
|
|
static void mtk_eint_ack(struct irq_data *d)
|
|
{
|
|
struct mtk_eint *eint = irq_data_get_irq_chip_data(d);
|
|
unsigned int instance, index;
|
|
void __iomem *reg;
|
|
unsigned int bit;
|
|
|
|
if (eint->comp->ops.ack)
|
|
eint->comp->ops.ack(d);
|
|
else {
|
|
reg = mtk_eint_get_offset(eint, d->hwirq,
|
|
eint->comp->regs->ack,
|
|
&instance, &index);
|
|
bit = BIT(index & 0x1f);
|
|
if (!reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, d->hwirq);
|
|
return;
|
|
}
|
|
|
|
writel(bit, reg);
|
|
}
|
|
}
|
|
|
|
static void mtk_eint_soft_set(struct mtk_eint *eint,
|
|
unsigned int eint_num)
|
|
{
|
|
unsigned int instance, index;
|
|
void __iomem *reg = mtk_eint_get_offset(eint, eint_num,
|
|
eint->comp->regs->soft_set,
|
|
&instance, &index);
|
|
unsigned int bit = BIT(index & 0x1f);
|
|
|
|
if (!reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, eint_num);
|
|
return;
|
|
}
|
|
|
|
writel(bit, reg);
|
|
}
|
|
|
|
static void mtk_eint_soft_clr(struct mtk_eint *eint,
|
|
unsigned int eint_num)
|
|
{
|
|
unsigned int instance, index;
|
|
void __iomem *reg = mtk_eint_get_offset(eint, eint_num,
|
|
eint->comp->regs->soft_clr,
|
|
&instance, &index);
|
|
unsigned int bit = BIT(index & 0x1f);
|
|
|
|
if (!reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, eint_num);
|
|
return;
|
|
}
|
|
|
|
writel(bit, reg);
|
|
}
|
|
|
|
static int mtk_eint_set_type(struct irq_data *d, unsigned int type)
|
|
{
|
|
struct mtk_eint *eint = irq_data_get_irq_chip_data(d);
|
|
u32 mask;
|
|
unsigned int instance, index;
|
|
void __iomem *reg;
|
|
|
|
if (((type & IRQ_TYPE_EDGE_BOTH) && (type & IRQ_TYPE_LEVEL_MASK)) ||
|
|
((type & IRQ_TYPE_LEVEL_MASK) == IRQ_TYPE_LEVEL_MASK)) {
|
|
dev_err(eint->dev,
|
|
"Can't configure IRQ%d (EINT%lu) for type 0x%X\n",
|
|
d->irq, d->hwirq, type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH)
|
|
eint->pins[d->hwirq].dual_edge = 1;
|
|
else
|
|
eint->pins[d->hwirq].dual_edge = 0;
|
|
|
|
if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_EDGE_FALLING))
|
|
reg = mtk_eint_get_offset(eint, d->hwirq,
|
|
eint->comp->regs->pol_clr,
|
|
&instance, &index);
|
|
else
|
|
reg = mtk_eint_get_offset(eint, d->hwirq,
|
|
eint->comp->regs->pol_set,
|
|
&instance, &index);
|
|
|
|
mask = BIT(index & 0x1f);
|
|
writel(mask, reg);
|
|
|
|
if (type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING))
|
|
reg = mtk_eint_get_offset(eint, d->hwirq,
|
|
eint->comp->regs->sens_clr,
|
|
&instance, &index);
|
|
else
|
|
reg = mtk_eint_get_offset(eint, d->hwirq,
|
|
eint->comp->regs->sens_set,
|
|
&instance, &index);
|
|
|
|
if (!reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, d->hwirq);
|
|
return 0;
|
|
}
|
|
|
|
mask = BIT(index & 0x1f);
|
|
writel(mask, reg);
|
|
|
|
if (eint->pins[d->hwirq].dual_edge)
|
|
mtk_eint_flip_edge(eint, d->hwirq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mtk_eint_irq_set_wake(struct irq_data *d, unsigned int on)
|
|
{
|
|
struct mtk_eint *eint = irq_data_get_irq_chip_data(d);
|
|
unsigned int instance, index, shift, port;
|
|
void __iomem *reg = mtk_eint_get_offset(eint, d->hwirq,
|
|
MTK_EINT_NO_OFFSET,
|
|
&instance, &index);
|
|
|
|
if (!reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, d->hwirq);
|
|
return 0;
|
|
}
|
|
|
|
shift = index & 0x1f;
|
|
port = index >> 5;
|
|
|
|
if (on)
|
|
eint->instances[instance].wake_mask[port] |= BIT(shift);
|
|
else
|
|
eint->instances[instance].wake_mask[port] &= ~BIT(shift);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mtk_eint_irq_request_resources(struct irq_data *d)
|
|
{
|
|
struct mtk_eint *eint = irq_data_get_irq_chip_data(d);
|
|
struct gpio_chip *gpio_c;
|
|
unsigned int gpio_n;
|
|
int err;
|
|
|
|
err = eint->gpio_xlate->get_gpio_n(eint->pctl, d->hwirq,
|
|
&gpio_n, &gpio_c);
|
|
if (err < 0) {
|
|
dev_err(eint->dev, "Can not find pin\n");
|
|
goto err_out;
|
|
}
|
|
|
|
err = gpiochip_lock_as_irq(gpio_c, gpio_n);
|
|
if (err < 0) {
|
|
dev_err(eint->dev, "unable to lock HW IRQ %lu for IRQ\n",
|
|
irqd_to_hwirq(d));
|
|
goto err_out;
|
|
}
|
|
|
|
err = eint->gpio_xlate->set_gpio_as_eint(eint->pctl, d->hwirq);
|
|
if (err < 0) {
|
|
dev_err(eint->dev, "Can not eint mode\n");
|
|
goto err_out;
|
|
}
|
|
|
|
return 0;
|
|
err_out:
|
|
return err;
|
|
}
|
|
|
|
static void mtk_eint_irq_release_resources(struct irq_data *d)
|
|
{
|
|
struct mtk_eint *eint = irq_data_get_irq_chip_data(d);
|
|
struct gpio_chip *gpio_c;
|
|
unsigned int gpio_n;
|
|
|
|
eint->gpio_xlate->get_gpio_n(eint->pctl, d->hwirq, &gpio_n,
|
|
&gpio_c);
|
|
|
|
gpiochip_unlock_as_irq(gpio_c, gpio_n);
|
|
}
|
|
|
|
static struct irq_chip mtk_eint_irq_chip = {
|
|
.name = "mtk-eint",
|
|
.irq_disable = mtk_eint_mask,
|
|
.irq_mask = mtk_eint_mask,
|
|
.irq_unmask = mtk_eint_unmask,
|
|
.irq_ack = mtk_eint_ack,
|
|
.irq_set_type = mtk_eint_set_type,
|
|
.irq_set_wake = mtk_eint_irq_set_wake,
|
|
.irq_request_resources = mtk_eint_irq_request_resources,
|
|
.irq_release_resources = mtk_eint_irq_release_resources,
|
|
};
|
|
|
|
/*
|
|
* Configure all EINT pins as domain 0, which only belongs to AP.
|
|
*/
|
|
static unsigned int mtk_eint_hw_init(struct mtk_eint *eint)
|
|
{
|
|
void __iomem *reg_dom_en, *reg_mask_set;
|
|
unsigned int i, j;
|
|
|
|
for (i = 0; i < eint->instance_number; i++) {
|
|
reg_dom_en = eint->instances[i].base + eint->comp->regs->dom_en;
|
|
reg_mask_set = eint->instances[i].base + eint->comp->regs->mask_set;
|
|
for (j = 0; j < eint->instances[i].number; j += 32) {
|
|
writel(0xffffffff, reg_dom_en);
|
|
writel(0xffffffff, reg_mask_set);
|
|
reg_dom_en += 4;
|
|
reg_mask_set +=4;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void
|
|
mtk_eint_debounce_process(struct mtk_eint *eint, int eint_num)
|
|
{
|
|
unsigned int rst, ctrl_offset;
|
|
unsigned int bit, dbnc;
|
|
unsigned int instance, index;
|
|
void __iomem *reg;
|
|
|
|
reg = mtk_eint_get_offset(eint, eint_num, MTK_EINT_NO_OFFSET,
|
|
&instance, &index);
|
|
|
|
if (!reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, eint_num);
|
|
return;
|
|
}
|
|
|
|
ctrl_offset = (index / 4) * 4 + eint->comp->regs->dbnc_ctrl;
|
|
dbnc = readl(eint->instances[instance].base + ctrl_offset);
|
|
bit = MTK_EINT_DBNC_SET_EN << ((index % 4) * 8);
|
|
|
|
if ((bit & dbnc) > 0) {
|
|
ctrl_offset = (index / 4) * 4 + eint->comp->regs->dbnc_set;
|
|
rst = MTK_EINT_DBNC_RST_BIT << ((index % 4) * 8);
|
|
writel(rst, eint->instances[instance].base + ctrl_offset);
|
|
}
|
|
}
|
|
|
|
static void mtk_eint_irq_handler(struct irq_desc *desc)
|
|
{
|
|
struct irq_chip *chip = irq_desc_get_chip(desc);
|
|
struct mtk_eint *eint = irq_desc_get_handler_data(desc);
|
|
unsigned int status, i, j;
|
|
int shift, port, eint_num, virq;
|
|
unsigned int dual_edge, start_level, curr_level;
|
|
struct mtk_eint_instance eint_instance;
|
|
void __iomem *addr;
|
|
|
|
chained_irq_enter(chip, desc);
|
|
|
|
for (i = 0; i < eint->instance_number; i++) {
|
|
eint_instance = eint->instances[i];
|
|
|
|
/* Iterate all pins by port */
|
|
for (j = 0; j < eint_instance.number; j += 32) {
|
|
port = j >> 5;
|
|
status = readl(eint_instance.base + port * 4 +
|
|
eint->comp->regs->stat);
|
|
while (status) {
|
|
shift = __ffs(status);
|
|
status &= ~BIT(shift);
|
|
|
|
eint_num = eint->instances[i].pin_list[shift + j];
|
|
virq = irq_find_mapping(eint->domain, eint_num);
|
|
|
|
/*
|
|
* If we get an interrupt on pin that was only required
|
|
* for wake (but no real interrupt requested), mask the
|
|
* interrupt (as would mtk_eint_resume do anyway later
|
|
* in the resume sequence).
|
|
*/
|
|
if (eint->instances[i].wake_mask[port] & BIT(shift) &&
|
|
!(eint->instances[i].cur_mask[port] & BIT(shift))) {
|
|
addr = eint_instance.base + port * 4 +
|
|
eint->comp->regs->mask_set;
|
|
writel_relaxed(BIT(shift), addr);
|
|
}
|
|
|
|
dual_edge = eint->pins[eint_num].dual_edge;
|
|
if (dual_edge) {
|
|
/*
|
|
* Clear soft-irq in case we raised it last
|
|
* time.
|
|
*/
|
|
mtk_eint_soft_clr(eint, eint_num);
|
|
|
|
start_level =
|
|
eint->gpio_xlate->get_gpio_state(eint->pctl,
|
|
eint_num);
|
|
}
|
|
|
|
generic_handle_irq(virq);
|
|
|
|
if (dual_edge) {
|
|
curr_level = mtk_eint_flip_edge(eint, eint_num);
|
|
|
|
/*
|
|
* If level changed, we might lost one edge
|
|
* interrupt, raised it through soft-irq.
|
|
*/
|
|
if (start_level != curr_level)
|
|
mtk_eint_soft_set(eint, eint_num);
|
|
}
|
|
|
|
if (eint->pins[eint_num].debounce)
|
|
mtk_eint_debounce_process(eint, eint_num);
|
|
|
|
}
|
|
}
|
|
}
|
|
chained_irq_exit(chip, desc);
|
|
}
|
|
|
|
int mtk_eint_do_suspend(struct mtk_eint *eint)
|
|
{
|
|
unsigned int i, j, port;
|
|
|
|
for (i = 0; i < eint->instance_number; i++) {
|
|
struct mtk_eint_instance inst = eint->instances[i];
|
|
|
|
for (j = 0; j < inst.number; j += 32) {
|
|
port = j >> 5;
|
|
writel_relaxed(~inst.wake_mask[port],
|
|
inst.base + port*4 + eint->comp->regs->mask_set);
|
|
writel_relaxed(inst.wake_mask[port],
|
|
inst.base + port*4 + eint->comp->regs->mask_clr);
|
|
}
|
|
}
|
|
dsb(sy);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mtk_eint_do_suspend);
|
|
|
|
int mtk_eint_do_resume(struct mtk_eint *eint)
|
|
{
|
|
unsigned int i, j, port;
|
|
|
|
for (i = 0; i < eint->instance_number; i++) {
|
|
struct mtk_eint_instance inst = eint->instances[i];
|
|
|
|
for (j = 0; j < inst.number; j += 32) {
|
|
port = j >> 5;
|
|
writel_relaxed(~inst.cur_mask[port],
|
|
inst.base + port*4 + eint->comp->regs->mask_set);
|
|
writel_relaxed(inst.cur_mask[port],
|
|
inst.base + port*4 + eint->comp->regs->mask_clr);
|
|
}
|
|
}
|
|
dsb(sy);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mtk_eint_do_resume);
|
|
|
|
int mtk_eint_set_debounce(struct mtk_eint *eint, unsigned long eint_num,
|
|
unsigned int debounce)
|
|
{
|
|
int virq, eint_offset;
|
|
unsigned int set_offset, bit, clr_bit, clr_offset, rst, i, unmask,
|
|
dbnc;
|
|
static const unsigned int debounce_time[] = { 156, 313, 625, 1250,
|
|
20000, 40000, 80000, 160000, 320000, 640000 };
|
|
struct irq_data *d;
|
|
unsigned int instance, index;
|
|
void __iomem *reg;
|
|
|
|
/*
|
|
* Due to different number of bit field, we only decode
|
|
* the coordinate here, instead of get the VA.
|
|
*/
|
|
reg = mtk_eint_get_offset(eint, eint_num, MTK_EINT_NO_OFFSET,
|
|
&instance, &index);
|
|
|
|
if (!reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, eint_num);
|
|
return 0;
|
|
}
|
|
|
|
virq = irq_find_mapping(eint->domain, eint_num);
|
|
eint_offset = (index % 4) * 8;
|
|
d = irq_get_irq_data(virq);
|
|
|
|
reg = eint->instances[instance].base;
|
|
set_offset = (index / 4) * 4 + eint->comp->regs->dbnc_set;
|
|
clr_offset = (index / 4) * 4 + eint->comp->regs->dbnc_clr;
|
|
|
|
if (!mtk_eint_can_en_debounce(eint, eint_num))
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Check eint number to avoid access out-of-range
|
|
*/
|
|
dbnc = ARRAY_SIZE(debounce_time) - 1;
|
|
for (i = 0; i < ARRAY_SIZE(debounce_time); i++) {
|
|
if (debounce <= debounce_time[i]) {
|
|
dbnc = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!mtk_eint_get_mask(eint, eint_num)) {
|
|
mtk_eint_mask(d);
|
|
unmask = 1;
|
|
} else
|
|
unmask = 0;
|
|
|
|
clr_bit = 0xff << eint_offset;
|
|
writel(clr_bit, reg + clr_offset);
|
|
|
|
bit = ((dbnc << MTK_EINT_DBNC_SET_DBNC_BITS)
|
|
| MTK_EINT_DBNC_SET_EN) << eint_offset;
|
|
rst = MTK_EINT_DBNC_RST_BIT << eint_offset;
|
|
writel(rst | bit, reg + set_offset);
|
|
|
|
/*
|
|
* Delay should be (8T @ 32k) + de-bounce count-down time
|
|
* from dbc rst to work correctly.
|
|
*/
|
|
udelay(debounce_time[dbnc] + 250);
|
|
if (unmask == 1)
|
|
mtk_eint_unmask(d);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mtk_eint_set_debounce);
|
|
|
|
unsigned int mtk_eint_get_debounce_en(struct mtk_eint *eint,
|
|
unsigned int eint_num)
|
|
{
|
|
unsigned int instance, index, bit;
|
|
void __iomem *reg;
|
|
|
|
reg = mtk_eint_get_offset(eint, eint_num, MTK_EINT_NO_OFFSET,
|
|
&instance, &index);
|
|
|
|
if (!reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, eint_num);
|
|
return 0;
|
|
}
|
|
|
|
reg = eint->instances[instance].base +
|
|
(index / 4) * 4 + eint->comp->regs->dbnc_ctrl;
|
|
|
|
bit = MTK_EINT_DBNC_SET_EN << ((index % 4) * 8);
|
|
|
|
return (readl(reg) & bit) ? 1 : 0;
|
|
}
|
|
|
|
unsigned int mtk_eint_get_debounce_value(struct mtk_eint *eint,
|
|
unsigned int eint_num)
|
|
{
|
|
unsigned int instance, index, mask, offset;
|
|
void __iomem *reg;
|
|
|
|
reg = mtk_eint_get_offset(eint, eint_num, MTK_EINT_NO_OFFSET,
|
|
&instance, &index);
|
|
|
|
if (!reg) {
|
|
dev_err(eint->dev, "%s invalid eint_num %d\n",
|
|
__func__, eint_num);
|
|
return 0;
|
|
}
|
|
|
|
reg = eint->instances[instance].base +
|
|
(index / 4) * 4 + eint->comp->regs->dbnc_ctrl;
|
|
|
|
offset = MTK_EINT_DBNC_SET_DBNC_BITS + ((index % 4) * 8);
|
|
mask = 0xf << offset;
|
|
|
|
return ((readl(reg) & mask) >> offset);
|
|
}
|
|
|
|
int mtk_eint_find_irq(struct mtk_eint *eint, unsigned long eint_n)
|
|
{
|
|
int irq;
|
|
|
|
irq = irq_find_mapping(eint->domain, eint_n);
|
|
if (!irq)
|
|
return -EINVAL;
|
|
|
|
return irq;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mtk_eint_find_irq);
|
|
|
|
/*
|
|
* Dump the properties/states of the specific EINT pin.
|
|
* @eint_num: the global EINT number.
|
|
* @buf: the pointer of a string buffer.
|
|
* @buf_size: the size of the buffer.
|
|
*
|
|
* If the return value < 0, it means that the @eint_num is invalid;
|
|
* Otherwise, return 0;
|
|
*/
|
|
int dump_eint_pin_status(unsigned int eint_num, char *buf, unsigned int buf_size)
|
|
{
|
|
unsigned int len = 0, enabled, stat, raw_stat, soft, mask, sens, pol,
|
|
deb_en, deb_val;
|
|
|
|
if (eint_num < 0 || eint_num >= global_eintc->total_pin_number)
|
|
return -ENODEV;
|
|
|
|
enabled = global_eintc->pins[eint_num].enabled;
|
|
stat = mtk_eint_get_stat(global_eintc, eint_num);
|
|
raw_stat = mtk_eint_get_raw_stat(global_eintc, eint_num);
|
|
soft = mtk_eint_get_soft(global_eintc, eint_num);
|
|
mask = mtk_eint_get_mask(global_eintc, eint_num);
|
|
sens = mtk_eint_get_sens(global_eintc, eint_num);
|
|
pol = mtk_eint_get_pol(global_eintc, eint_num);
|
|
|
|
len += snprintf(buf + len, buf_size - len,
|
|
"%s=%u(%s)\n%s=%s_%s\n%s=%u\n%s=%u\n%s=%u\n%s=%u\n",
|
|
"Pin", eint_num, enabled ? "enabled" : "disabled",
|
|
"Type", (sens == 1) ? "level" : "edge",
|
|
(pol == 1) ? "high" : "low",
|
|
"Pending", stat,
|
|
"Raw", raw_stat,
|
|
"Soft", soft,
|
|
"Mask", mask);
|
|
|
|
if (mtk_eint_can_en_debounce(global_eintc, eint_num)) {
|
|
deb_en = mtk_eint_get_debounce_en(global_eintc, eint_num);
|
|
deb_val = mtk_eint_get_debounce_value(global_eintc, eint_num);
|
|
|
|
len += snprintf(buf + len, buf_size - len,
|
|
"Support debounce, %s=%u, %s=%u\n",
|
|
"enable", deb_en,
|
|
"setting", deb_val);
|
|
} else
|
|
len += snprintf(buf + len, buf_size - len,
|
|
"Not support debounce\n");
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dump_eint_pin_status);
|
|
|
|
static ssize_t eintc_status_show(struct device_driver *driver, char *buf)
|
|
{
|
|
struct mtk_eint *eint = global_eintc;
|
|
unsigned int i, j, len = 0,
|
|
instance_num = eint->instance_number;
|
|
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "=====EINTC Dump=====\n");
|
|
|
|
for (i = 0; i < instance_num; i++) {
|
|
struct mtk_eint_instance inst = eint->instances[i];
|
|
|
|
len += snprintf(buf + len, PAGE_SIZE - len,
|
|
"Instance %d name=%s with %u pins\n",
|
|
i, inst.name, inst.number);
|
|
|
|
for (j = 0; j < inst.number; j++)
|
|
len += snprintf(buf + len, PAGE_SIZE - len,
|
|
"%d ", inst.pin_list[j]);
|
|
|
|
len += snprintf(buf + len, PAGE_SIZE - len,
|
|
"\n", i, inst.pin_list[j]);
|
|
}
|
|
|
|
return strlen(buf);
|
|
}
|
|
|
|
static DRIVER_ATTR_RO(eintc_status);
|
|
|
|
static ssize_t eint_pin_status_show(struct device_driver *driver, char *buf)
|
|
{
|
|
struct mtk_eint *eint = global_eintc;
|
|
unsigned int len = 0;
|
|
|
|
len += snprintf(buf + len, PAGE_SIZE - len,
|
|
"=====EINT Pin Dump=====\n");
|
|
|
|
dump_eint_pin_status(eint->dump_target_eint,
|
|
buf + len, PAGE_SIZE - len);
|
|
|
|
return strlen(buf);
|
|
|
|
}
|
|
|
|
static ssize_t eint_pin_status_store(struct device_driver *driver,
|
|
const char *buf, size_t count)
|
|
{
|
|
int eint_num, ret;
|
|
|
|
ret = kstrtouint(buf, 10, &eint_num);
|
|
|
|
if (ret || eint_num >= global_eintc->total_pin_number) {
|
|
dev_err(global_eintc->dev,
|
|
"%s invalid input: %s.\n", __func__, buf);
|
|
goto err_out;
|
|
}
|
|
|
|
global_eintc->dump_target_eint = (unsigned int)eint_num;
|
|
|
|
err_out:
|
|
return count;
|
|
}
|
|
|
|
static DRIVER_ATTR_RW(eint_pin_status);
|
|
|
|
static const struct mtk_eint_compatible default_compat = {
|
|
.regs = &mtk_generic_eint_regs,
|
|
};
|
|
|
|
static const struct mtk_eint_compatible mt6983_compat = {
|
|
.ops = {
|
|
.ack = mt6983_eint_ack,
|
|
},
|
|
.regs = &mtk_generic_eint_regs,
|
|
};
|
|
|
|
static const struct of_device_id eint_compatible_ids[] = {
|
|
{ .compatible = "mediatek,mt6983-pinctrl", .data = &mt6983_compat },
|
|
{ }
|
|
};
|
|
|
|
int mtk_eint_do_init(struct mtk_eint *eint)
|
|
{
|
|
int i, matrix_number = 0;
|
|
struct device_node *node;
|
|
unsigned int ret, size, offset;
|
|
unsigned int id, inst, idx, support_deb;
|
|
|
|
const phandle *ph;
|
|
|
|
#if defined(MTK_EINT_DEBUG)
|
|
struct mtk_eint_pin pin;
|
|
#endif
|
|
|
|
ph = of_get_property(eint->dev->of_node, "mediatek,eint", NULL);
|
|
if (!ph) {
|
|
dev_err(eint->dev, "Cannot find EINT phandle in PIO node.\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
node = of_find_node_by_phandle(be32_to_cpup(ph));
|
|
if (!node) {
|
|
dev_err(eint->dev, "Cannot find EINT node by phandle.\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "mediatek,total-pin-number",
|
|
&eint->total_pin_number);
|
|
if (ret) {
|
|
dev_err(eint->dev,
|
|
"%s cannot read total-pin-number from device node.\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
} else
|
|
dev_info(eint->dev,
|
|
"%s eint total %u pins.\n", __func__, eint->total_pin_number);
|
|
|
|
ret = of_property_read_u32(node, "mediatek,instance-num",
|
|
&eint->instance_number);
|
|
if (ret)
|
|
eint->instance_number = 1; // only 1 instance in legacy chip
|
|
|
|
size = eint->instance_number * sizeof(struct mtk_eint_instance);
|
|
eint->instances = devm_kzalloc(eint->dev, size, GFP_KERNEL);
|
|
if (!eint->instances)
|
|
return -ENOMEM;
|
|
|
|
size = eint->total_pin_number * sizeof(struct mtk_eint_pin);
|
|
eint->pins = devm_kzalloc(eint->dev, size, GFP_KERNEL);
|
|
if (!eint->pins)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < eint->instance_number; i++) {
|
|
ret = of_property_read_string_index(node, "reg-name", i,
|
|
&(eint->instances[i].name));
|
|
if (ret) {
|
|
dev_info(eint->dev,
|
|
"%s cannot read the name of instance %d.\n",
|
|
__func__, i);
|
|
}
|
|
|
|
eint->instances[i].base = of_iomap(node, i);
|
|
if (!eint->instances[i].base)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
matrix_number = of_property_count_u32_elems(node, "mediatek,pins") / 4;
|
|
if (matrix_number < 0) {
|
|
matrix_number = eint->total_pin_number;
|
|
dev_info(eint->dev, "%s eint in legacy mode, assign the matrix number to %u.\n",
|
|
__func__, matrix_number);
|
|
} else
|
|
dev_info(eint->dev, "%s eint in new mode, assign the matrix number to %u.\n",
|
|
__func__, matrix_number);
|
|
|
|
for (i = 0; i < matrix_number; i++) {
|
|
offset = i * 4;
|
|
|
|
ret = of_property_read_u32_index(node, "mediatek,pins",
|
|
offset, &id);
|
|
ret |= of_property_read_u32_index(node, "mediatek,pins",
|
|
offset+1, &inst);
|
|
ret |= of_property_read_u32_index(node, "mediatek,pins",
|
|
offset+2, &idx);
|
|
ret |= of_property_read_u32_index(node, "mediatek,pins",
|
|
offset+3, &support_deb);
|
|
|
|
/* Legacy chip which no need to give coordinate list */
|
|
if (ret) {
|
|
id = i;
|
|
inst = 0;
|
|
idx = i;
|
|
support_deb = (i < 32) ? 1 : 0;
|
|
}
|
|
|
|
eint->pins[id].enabled = true;
|
|
eint->pins[id].instance = inst;
|
|
eint->pins[id].index = idx;
|
|
eint->pins[id].debounce = support_deb;
|
|
|
|
eint->instances[inst].pin_list[idx] = id;
|
|
eint->instances[inst].number++;
|
|
|
|
#if defined(MTK_EINT_DEBUG)
|
|
pin = eint->pins[id];
|
|
dev_info(eint->dev,
|
|
"EINT%u in (%u-%u, %u), deb = %u. %u",
|
|
id,
|
|
pin.instance,
|
|
eint->instances[inst].number,
|
|
pin.index,
|
|
pin.debounce,
|
|
eint->instances[pin.instance].pin_list[pin.index]);
|
|
#endif
|
|
}
|
|
|
|
for (i = 0; i < eint->instance_number; i++) {
|
|
size = (eint->instances[i].number / 32 + 1) * sizeof(unsigned int);
|
|
eint->instances[i].wake_mask =
|
|
devm_kzalloc(eint->dev, size, GFP_KERNEL);
|
|
eint->instances[i].cur_mask =
|
|
devm_kzalloc(eint->dev, size, GFP_KERNEL);
|
|
|
|
if (!eint->instances[i].wake_mask ||
|
|
!eint->instances[i].cur_mask)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
eint->comp = (struct mtk_eint_compatible *)
|
|
of_device_get_match_data(eint->dev);
|
|
|
|
if (!eint->comp)
|
|
eint->comp = &default_compat;
|
|
|
|
eint->irq = irq_of_parse_and_map(node, 0);
|
|
if (!eint->irq) {
|
|
dev_err(eint->dev,
|
|
"%s IRQ parse fail.\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
eint->domain = irq_domain_add_linear(eint->dev->of_node,
|
|
eint->total_pin_number,
|
|
&irq_domain_simple_ops, NULL);
|
|
if (!eint->domain)
|
|
return -ENOMEM;
|
|
|
|
mtk_eint_hw_init(eint);
|
|
for (i = 0; i < eint->total_pin_number; i++) {
|
|
int virq = irq_create_mapping(eint->domain, i);
|
|
|
|
irq_set_chip_and_handler(virq, &mtk_eint_irq_chip,
|
|
handle_level_irq);
|
|
irq_set_chip_data(virq, eint);
|
|
}
|
|
|
|
irq_set_chained_handler_and_data(eint->irq, mtk_eint_irq_handler,
|
|
eint);
|
|
|
|
ret = driver_create_file(eint->dev->driver,
|
|
&driver_attr_eintc_status);
|
|
|
|
ret |= driver_create_file(eint->dev->driver,
|
|
&driver_attr_eint_pin_status);
|
|
|
|
if (ret)
|
|
dev_err(eint->dev, "%s create sysfs files failed.\n", __func__);
|
|
|
|
global_eintc = eint;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mtk_eint_do_init);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("MediaTek EINT Driver");
|