pinctrl: mediatek: Add EINT support to MTK pinctrl driver.
MTK SoC support external interrupt(EINT) from most SoC pins. Add EINT support to pinctrl driver. Signed-off-by: Maoguang Meng <maoguang.meng@mediatek.com> Signed-off-by: Hongzhou Yang <hongzhou.yang@mediatek.com> Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
This commit is contained in:
parent
a6df410d42
commit
d9819eb9cd
|
@ -314,6 +314,29 @@ static const struct mtk_pinctrl_devdata mt8135_pinctrl_data = {
|
|||
.port_shf = 4,
|
||||
.port_mask = 0xf,
|
||||
.port_align = 4,
|
||||
.chip_type = MTK_CHIP_TYPE_BASE,
|
||||
.eint_offsets = {
|
||||
.name = "mt8135_eint",
|
||||
.stat = 0x000,
|
||||
.ack = 0x040,
|
||||
.mask = 0x080,
|
||||
.mask_set = 0x0c0,
|
||||
.mask_clr = 0x100,
|
||||
.sens = 0x140,
|
||||
.sens_set = 0x180,
|
||||
.sens_clr = 0x1c0,
|
||||
.pol = 0x300,
|
||||
.pol_set = 0x340,
|
||||
.pol_clr = 0x380,
|
||||
.dom_en = 0x400,
|
||||
.dbnc_ctrl = 0x500,
|
||||
.dbnc_set = 0x600,
|
||||
.dbnc_clr = 0x700,
|
||||
.port_mask = 7,
|
||||
.ports = 6,
|
||||
},
|
||||
.ap_num = 192,
|
||||
.db_cnt = 16,
|
||||
};
|
||||
|
||||
static int mt8135_pinctrl_probe(struct platform_device *pdev)
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <linux/bitops.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/delay.h>
|
||||
#include <dt-bindings/pinctrl/mt65xx.h>
|
||||
|
||||
#include "../core.h"
|
||||
|
@ -560,6 +561,21 @@ static int mtk_pmx_set_mode(struct pinctrl_dev *pctldev,
|
|||
reg_addr, mask, val);
|
||||
}
|
||||
|
||||
static const struct mtk_desc_pin *
|
||||
mtk_find_pin_by_eint_num(struct mtk_pinctrl *pctl, unsigned int eint_num)
|
||||
{
|
||||
int i;
|
||||
const struct mtk_desc_pin *pin;
|
||||
|
||||
for (i = 0; i < pctl->devdata->npins; i++) {
|
||||
pin = pctl->devdata->pins + i;
|
||||
if (pin->eint.eintnum == eint_num)
|
||||
return pin;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int mtk_pmx_set_mux(struct pinctrl_dev *pctldev,
|
||||
unsigned function,
|
||||
unsigned group)
|
||||
|
@ -647,6 +663,199 @@ static int mtk_gpio_get(struct gpio_chip *chip, unsigned offset)
|
|||
return !!(read_val & bit);
|
||||
}
|
||||
|
||||
static int mtk_gpio_to_irq(struct gpio_chip *chip, unsigned offset)
|
||||
{
|
||||
const struct mtk_desc_pin *pin;
|
||||
struct mtk_pinctrl *pctl = dev_get_drvdata(chip->dev);
|
||||
int irq;
|
||||
|
||||
pin = pctl->devdata->pins + offset;
|
||||
if (pin->eint.eintnum == NO_EINT_SUPPORT)
|
||||
return -EINVAL;
|
||||
|
||||
irq = irq_find_mapping(pctl->domain, pin->eint.eintnum);
|
||||
if (!irq)
|
||||
return -EINVAL;
|
||||
|
||||
return irq;
|
||||
}
|
||||
|
||||
static int mtk_pinctrl_irq_request_resources(struct irq_data *d)
|
||||
{
|
||||
struct mtk_pinctrl *pctl = irq_data_get_irq_chip_data(d);
|
||||
const struct mtk_desc_pin *pin;
|
||||
int ret;
|
||||
|
||||
pin = mtk_find_pin_by_eint_num(pctl, d->hwirq);
|
||||
|
||||
if (!pin) {
|
||||
dev_err(pctl->dev, "Can not find pin\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = gpiochip_lock_as_irq(pctl->chip, pin->pin.number);
|
||||
if (ret) {
|
||||
dev_err(pctl->dev, "unable to lock HW IRQ %lu for IRQ\n",
|
||||
irqd_to_hwirq(d));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set mux to INT mode */
|
||||
mtk_pmx_set_mode(pctl->pctl_dev, pin->pin.number, pin->eint.eintmux);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mtk_pinctrl_irq_release_resources(struct irq_data *d)
|
||||
{
|
||||
struct mtk_pinctrl *pctl = irq_data_get_irq_chip_data(d);
|
||||
const struct mtk_desc_pin *pin;
|
||||
|
||||
pin = mtk_find_pin_by_eint_num(pctl, d->hwirq);
|
||||
|
||||
if (!pin) {
|
||||
dev_err(pctl->dev, "Can not find pin\n");
|
||||
return;
|
||||
}
|
||||
|
||||
gpiochip_unlock_as_irq(pctl->chip, pin->pin.number);
|
||||
}
|
||||
|
||||
static void __iomem *mtk_eint_get_offset(struct mtk_pinctrl *pctl,
|
||||
unsigned int eint_num, unsigned int offset)
|
||||
{
|
||||
unsigned int eint_base = 0;
|
||||
void __iomem *reg;
|
||||
|
||||
if (eint_num >= pctl->devdata->ap_num)
|
||||
eint_base = pctl->devdata->ap_num;
|
||||
|
||||
reg = pctl->eint_reg_base + offset + ((eint_num - eint_base) / 32) * 4;
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
/*
|
||||
* mtk_can_en_debounce: Check the EINT number is able to enable debounce or not
|
||||
* @eint_num: the EINT number to setmtk_pinctrl
|
||||
*/
|
||||
static unsigned int mtk_eint_can_en_debounce(struct mtk_pinctrl *pctl,
|
||||
unsigned int eint_num)
|
||||
{
|
||||
unsigned int sens;
|
||||
unsigned int bit = BIT(eint_num % 32);
|
||||
const struct mtk_eint_offsets *eint_offsets =
|
||||
&pctl->devdata->eint_offsets;
|
||||
|
||||
void __iomem *reg = mtk_eint_get_offset(pctl, eint_num,
|
||||
eint_offsets->sens);
|
||||
|
||||
if (readl(reg) & bit)
|
||||
sens = MT_LEVEL_SENSITIVE;
|
||||
else
|
||||
sens = MT_EDGE_SENSITIVE;
|
||||
|
||||
if ((eint_num < pctl->devdata->db_cnt) && (sens != MT_EDGE_SENSITIVE))
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* mtk_eint_get_mask: To get the eint mask
|
||||
* @eint_num: the EINT number to get
|
||||
*/
|
||||
static unsigned int mtk_eint_get_mask(struct mtk_pinctrl *pctl,
|
||||
unsigned int eint_num)
|
||||
{
|
||||
unsigned int bit = BIT(eint_num % 32);
|
||||
const struct mtk_eint_offsets *eint_offsets =
|
||||
&pctl->devdata->eint_offsets;
|
||||
|
||||
void __iomem *reg = mtk_eint_get_offset(pctl, eint_num,
|
||||
eint_offsets->mask);
|
||||
|
||||
return !!(readl(reg) & bit);
|
||||
}
|
||||
|
||||
static void mtk_eint_mask(struct irq_data *d)
|
||||
{
|
||||
struct mtk_pinctrl *pctl = irq_data_get_irq_chip_data(d);
|
||||
const struct mtk_eint_offsets *eint_offsets =
|
||||
&pctl->devdata->eint_offsets;
|
||||
u32 mask = BIT(d->hwirq & 0x1f);
|
||||
void __iomem *reg = mtk_eint_get_offset(pctl, d->hwirq,
|
||||
eint_offsets->mask_set);
|
||||
|
||||
writel(mask, reg);
|
||||
}
|
||||
|
||||
static void mtk_eint_unmask(struct irq_data *d)
|
||||
{
|
||||
struct mtk_pinctrl *pctl = irq_data_get_irq_chip_data(d);
|
||||
const struct mtk_eint_offsets *eint_offsets =
|
||||
&pctl->devdata->eint_offsets;
|
||||
u32 mask = BIT(d->hwirq & 0x1f);
|
||||
void __iomem *reg = mtk_eint_get_offset(pctl, d->hwirq,
|
||||
eint_offsets->mask_clr);
|
||||
|
||||
writel(mask, reg);
|
||||
}
|
||||
|
||||
static int mtk_gpio_set_debounce(struct gpio_chip *chip, unsigned offset,
|
||||
unsigned debounce)
|
||||
{
|
||||
struct mtk_pinctrl *pctl = dev_get_drvdata(chip->dev);
|
||||
int eint_num, virq, eint_offset;
|
||||
unsigned int set_offset, bit, clr_bit, clr_offset, rst, i, unmask, dbnc;
|
||||
static const unsigned int dbnc_arr[] = {0 , 1, 16, 32, 64, 128, 256};
|
||||
const struct mtk_desc_pin *pin;
|
||||
struct irq_data *d;
|
||||
|
||||
pin = pctl->devdata->pins + offset;
|
||||
if (pin->eint.eintnum == NO_EINT_SUPPORT)
|
||||
return -EINVAL;
|
||||
|
||||
eint_num = pin->eint.eintnum;
|
||||
virq = irq_find_mapping(pctl->domain, eint_num);
|
||||
eint_offset = (eint_num % 4) * 8;
|
||||
d = irq_get_irq_data(virq);
|
||||
|
||||
set_offset = (eint_num / 4) * 4 + pctl->devdata->eint_offsets.dbnc_set;
|
||||
clr_offset = (eint_num / 4) * 4 + pctl->devdata->eint_offsets.dbnc_clr;
|
||||
if (!mtk_eint_can_en_debounce(pctl, eint_num))
|
||||
return -ENOSYS;
|
||||
|
||||
dbnc = ARRAY_SIZE(dbnc_arr);
|
||||
for (i = 0; i < ARRAY_SIZE(dbnc_arr); i++) {
|
||||
if (debounce <= dbnc_arr[i]) {
|
||||
dbnc = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mtk_eint_get_mask(pctl, eint_num)) {
|
||||
mtk_eint_mask(d);
|
||||
unmask = 1;
|
||||
}
|
||||
|
||||
clr_bit = 0xff << eint_offset;
|
||||
writel(clr_bit, pctl->eint_reg_base + clr_offset);
|
||||
|
||||
bit = ((dbnc << EINT_DBNC_SET_DBNC_BITS) | EINT_DBNC_SET_EN) <<
|
||||
eint_offset;
|
||||
rst = EINT_DBNC_RST_BIT << eint_offset;
|
||||
writel(rst | bit, pctl->eint_reg_base + set_offset);
|
||||
|
||||
/* Delay a while (more than 2T) to wait for hw debounce counter reset
|
||||
work correctly */
|
||||
udelay(1);
|
||||
if (unmask == 1)
|
||||
mtk_eint_unmask(d);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct gpio_chip mtk_gpio_chip = {
|
||||
.owner = THIS_MODULE,
|
||||
.request = mtk_gpio_request,
|
||||
|
@ -655,9 +864,134 @@ static struct gpio_chip mtk_gpio_chip = {
|
|||
.direction_output = mtk_gpio_direction_output,
|
||||
.get = mtk_gpio_get,
|
||||
.set = mtk_gpio_set,
|
||||
.to_irq = mtk_gpio_to_irq,
|
||||
.set_debounce = mtk_gpio_set_debounce,
|
||||
.of_gpio_n_cells = 2,
|
||||
};
|
||||
|
||||
static int mtk_eint_set_type(struct irq_data *d,
|
||||
unsigned int type)
|
||||
{
|
||||
struct mtk_pinctrl *pctl = irq_data_get_irq_chip_data(d);
|
||||
const struct mtk_eint_offsets *eint_offsets =
|
||||
&pctl->devdata->eint_offsets;
|
||||
u32 mask = BIT(d->hwirq & 0x1f);
|
||||
void __iomem *reg;
|
||||
|
||||
if (((type & IRQ_TYPE_EDGE_BOTH) && (type & IRQ_TYPE_LEVEL_MASK)) ||
|
||||
((type & IRQ_TYPE_EDGE_BOTH) == IRQ_TYPE_EDGE_BOTH) ||
|
||||
((type & IRQ_TYPE_LEVEL_MASK) == IRQ_TYPE_LEVEL_MASK)) {
|
||||
dev_err(pctl->dev, "Can't configure IRQ%d (EINT%lu) for type 0x%X\n",
|
||||
d->irq, d->hwirq, type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_EDGE_FALLING)) {
|
||||
reg = mtk_eint_get_offset(pctl, d->hwirq,
|
||||
eint_offsets->pol_clr);
|
||||
writel(mask, reg);
|
||||
} else {
|
||||
reg = mtk_eint_get_offset(pctl, d->hwirq,
|
||||
eint_offsets->pol_set);
|
||||
writel(mask, reg);
|
||||
}
|
||||
|
||||
if (type & (IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING)) {
|
||||
reg = mtk_eint_get_offset(pctl, d->hwirq,
|
||||
eint_offsets->sens_clr);
|
||||
writel(mask, reg);
|
||||
} else {
|
||||
reg = mtk_eint_get_offset(pctl, d->hwirq,
|
||||
eint_offsets->sens_set);
|
||||
writel(mask, reg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mtk_eint_ack(struct irq_data *d)
|
||||
{
|
||||
struct mtk_pinctrl *pctl = irq_data_get_irq_chip_data(d);
|
||||
const struct mtk_eint_offsets *eint_offsets =
|
||||
&pctl->devdata->eint_offsets;
|
||||
u32 mask = BIT(d->hwirq & 0x1f);
|
||||
void __iomem *reg = mtk_eint_get_offset(pctl, d->hwirq,
|
||||
eint_offsets->ack);
|
||||
|
||||
writel(mask, reg);
|
||||
}
|
||||
|
||||
static struct irq_chip mtk_pinctrl_irq_chip = {
|
||||
.name = "mt-eint",
|
||||
.irq_mask = mtk_eint_mask,
|
||||
.irq_unmask = mtk_eint_unmask,
|
||||
.irq_ack = mtk_eint_ack,
|
||||
.irq_set_type = mtk_eint_set_type,
|
||||
.irq_request_resources = mtk_pinctrl_irq_request_resources,
|
||||
.irq_release_resources = mtk_pinctrl_irq_release_resources,
|
||||
};
|
||||
|
||||
static unsigned int mtk_eint_init(struct mtk_pinctrl *pctl)
|
||||
{
|
||||
const struct mtk_eint_offsets *eint_offsets =
|
||||
&pctl->devdata->eint_offsets;
|
||||
void __iomem *reg = pctl->eint_reg_base + eint_offsets->dom_en;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < pctl->devdata->ap_num; i += 32) {
|
||||
writel(0xffffffff, reg);
|
||||
reg += 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
mtk_eint_debounce_process(struct mtk_pinctrl *pctl, int index)
|
||||
{
|
||||
unsigned int rst, ctrl_offset;
|
||||
unsigned int bit, dbnc;
|
||||
const struct mtk_eint_offsets *eint_offsets =
|
||||
&pctl->devdata->eint_offsets;
|
||||
|
||||
ctrl_offset = (index / 4) * 4 + eint_offsets->dbnc_ctrl;
|
||||
dbnc = readl(pctl->eint_reg_base + ctrl_offset);
|
||||
bit = EINT_DBNC_SET_EN << ((index % 4) * 8);
|
||||
if ((bit & dbnc) > 0) {
|
||||
ctrl_offset = (index / 4) * 4 + eint_offsets->dbnc_set;
|
||||
rst = EINT_DBNC_RST_BIT << ((index % 4) * 8);
|
||||
writel(rst, pctl->eint_reg_base + ctrl_offset);
|
||||
}
|
||||
}
|
||||
|
||||
static void mtk_eint_irq_handler(unsigned irq, struct irq_desc *desc)
|
||||
{
|
||||
struct irq_chip *chip = irq_get_chip(irq);
|
||||
struct mtk_pinctrl *pctl = irq_get_handler_data(irq);
|
||||
unsigned int status, eint_num;
|
||||
int offset, index, virq;
|
||||
const struct mtk_eint_offsets *eint_offsets =
|
||||
&pctl->devdata->eint_offsets;
|
||||
void __iomem *reg = mtk_eint_get_offset(pctl, 0, eint_offsets->stat);
|
||||
|
||||
chained_irq_enter(chip, desc);
|
||||
for (eint_num = 0; eint_num < pctl->devdata->ap_num; eint_num += 32) {
|
||||
status = readl(reg);
|
||||
reg += 4;
|
||||
while (status) {
|
||||
offset = __ffs(status);
|
||||
index = eint_num + offset;
|
||||
virq = irq_find_mapping(pctl->domain, index);
|
||||
status &= ~BIT(offset);
|
||||
|
||||
generic_handle_irq(virq);
|
||||
|
||||
if (index < pctl->devdata->db_cnt)
|
||||
mtk_eint_debounce_process(pctl , index);
|
||||
}
|
||||
}
|
||||
chained_irq_exit(chip, desc);
|
||||
}
|
||||
|
||||
static int mtk_pctrl_build_state(struct platform_device *pdev)
|
||||
{
|
||||
struct mtk_pinctrl *pctl = platform_get_drvdata(pdev);
|
||||
|
@ -705,7 +1039,8 @@ int mtk_pctrl_init(struct platform_device *pdev,
|
|||
struct mtk_pinctrl *pctl;
|
||||
struct device_node *np = pdev->dev.of_node, *node;
|
||||
struct property *prop;
|
||||
int i, ret;
|
||||
struct resource *res;
|
||||
int i, ret, irq;
|
||||
|
||||
pctl = devm_kzalloc(&pdev->dev, sizeof(*pctl), GFP_KERNEL);
|
||||
if (!pctl)
|
||||
|
@ -786,6 +1121,48 @@ int mtk_pctrl_init(struct platform_device *pdev,
|
|||
goto chip_error;
|
||||
}
|
||||
|
||||
/* Get EINT register base from dts. */
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "Unable to get Pinctrl resource\n");
|
||||
ret = -EINVAL;
|
||||
goto chip_error;
|
||||
}
|
||||
|
||||
pctl->eint_reg_base = devm_ioremap_resource(&pdev->dev, res);
|
||||
if (IS_ERR(pctl->eint_reg_base)) {
|
||||
ret = -EINVAL;
|
||||
goto chip_error;
|
||||
}
|
||||
|
||||
irq = irq_of_parse_and_map(np, 0);
|
||||
if (!irq) {
|
||||
dev_err(&pdev->dev, "couldn't parse and map irq\n");
|
||||
ret = -EINVAL;
|
||||
goto chip_error;
|
||||
}
|
||||
|
||||
pctl->domain = irq_domain_add_linear(np,
|
||||
pctl->devdata->ap_num, &irq_domain_simple_ops, NULL);
|
||||
if (!pctl->domain) {
|
||||
dev_err(&pdev->dev, "Couldn't register IRQ domain\n");
|
||||
ret = -ENOMEM;
|
||||
goto chip_error;
|
||||
}
|
||||
|
||||
mtk_eint_init(pctl);
|
||||
for (i = 0; i < pctl->devdata->ap_num; i++) {
|
||||
int virq = irq_create_mapping(pctl->domain, i);
|
||||
|
||||
irq_set_chip_and_handler(virq, &mtk_pinctrl_irq_chip,
|
||||
handle_level_irq);
|
||||
irq_set_chip_data(virq, pctl);
|
||||
set_irq_flags(virq, IRQF_VALID);
|
||||
};
|
||||
|
||||
irq_set_chained_handler(irq, mtk_eint_irq_handler);
|
||||
irq_set_handler_data(irq, pctl);
|
||||
set_irq_flags(irq, IRQF_VALID);
|
||||
return 0;
|
||||
|
||||
chip_error:
|
||||
|
|
|
@ -16,10 +16,16 @@
|
|||
#define __PINCTRL_MTK_COMMON_H
|
||||
|
||||
#include <linux/pinctrl/pinctrl.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define NO_EINT_SUPPORT 255
|
||||
#define MTK_CHIP_TYPE_BASE 0
|
||||
#define MTK_CHIP_TYPE_PMIC 1
|
||||
#define MT_EDGE_SENSITIVE 0
|
||||
#define MT_LEVEL_SENSITIVE 1
|
||||
#define EINT_DBNC_SET_DBNC_BITS 4
|
||||
#define EINT_DBNC_RST_BIT (0x1 << 1)
|
||||
#define EINT_DBNC_SET_EN (0x1 << 0)
|
||||
|
||||
struct mtk_desc_function {
|
||||
const char *name;
|
||||
|
@ -115,6 +121,27 @@ struct mtk_pin_drv_grp {
|
|||
.grp = _grp, \
|
||||
}
|
||||
|
||||
struct mtk_eint_offsets {
|
||||
const char *name;
|
||||
unsigned int stat;
|
||||
unsigned int ack;
|
||||
unsigned int mask;
|
||||
unsigned int mask_set;
|
||||
unsigned int mask_clr;
|
||||
unsigned int sens;
|
||||
unsigned int sens_set;
|
||||
unsigned int sens_clr;
|
||||
unsigned int pol;
|
||||
unsigned int pol_set;
|
||||
unsigned int pol_clr;
|
||||
unsigned int dom_en;
|
||||
unsigned int dbnc_ctrl;
|
||||
unsigned int dbnc_set;
|
||||
unsigned int dbnc_clr;
|
||||
u8 port_mask;
|
||||
u8 ports;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mtk_pinctrl_devdata - Provide HW GPIO related data.
|
||||
* @pins: An array describing all pins the pin controller affects.
|
||||
|
@ -165,6 +192,10 @@ struct mtk_pinctrl_devdata {
|
|||
unsigned char port_shf;
|
||||
unsigned char port_mask;
|
||||
unsigned char port_align;
|
||||
unsigned char chip_type;
|
||||
struct mtk_eint_offsets eint_offsets;
|
||||
unsigned int ap_num;
|
||||
unsigned int db_cnt;
|
||||
};
|
||||
|
||||
struct mtk_pinctrl {
|
||||
|
@ -177,6 +208,8 @@ struct mtk_pinctrl {
|
|||
const char **grp_names;
|
||||
struct pinctrl_dev *pctl_dev;
|
||||
const struct mtk_pinctrl_devdata *devdata;
|
||||
void __iomem *eint_reg_base;
|
||||
struct irq_domain *domain;
|
||||
};
|
||||
|
||||
int mtk_pctrl_init(struct platform_device *pdev,
|
||||
|
|
Loading…
Reference in New Issue