|
@@ -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:
|