// SPDX-License-Identifier: GPL-2.0+
//
// Exynos specific support for Samsung pinctrl/gpiolib driver with eint support.
//
// Copyright (c) 2012 Samsung Electronics Co., Ltd.
// http://www.samsung.com
// Copyright (c) 2012 Linaro Ltd
// http://www.linaro.org
//
// Author: Thomas Abraham <thomas.ab@samsung.com>
//
// This file contains the Samsung Exynos specific information required by the
// the Samsung pinctrl/gpiolib driver. It also includes the implementation of
// external gpio and wakeup interrupt support.
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/irqdomain.h>
#include <linux/irq.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string_choices.h>
#include <linux/regmap.h>
#include <linux/err.h>
#include <linux/soc/samsung/exynos-pmu.h>
#include <linux/soc/samsung/exynos-regs-pmu.h>
#include "pinctrl-samsung.h"
#include "pinctrl-exynos.h"
#define MAX_WAKEUP_REG 3
struct exynos_irq_chip {
struct irq_chip chip;
u32 eint_con;
u32 eint_mask;
u32 eint_pend;
u32 eint_num_wakeup_reg;
u32 eint_wake_mask_reg;
void (*set_eint_wakeup_mask)(struct samsung_pinctrl_drv_data *drvdata,
struct exynos_irq_chip *irq_chip);
};
static u32 eint_wake_mask_values[MAX_WAKEUP_REG] = { EXYNOS_EINT_WAKEUP_MASK_DISABLED,
EXYNOS_EINT_WAKEUP_MASK_DISABLED,
EXYNOS_EINT_WAKEUP_MASK_DISABLED};
static inline struct exynos_irq_chip *to_exynos_irq_chip(struct irq_chip *chip)
{
return container_of(chip, struct exynos_irq_chip, chip);
}
static void exynos_irq_mask(struct irq_data *irqd)
{
struct irq_chip *chip = irq_data_get_irq_chip(irqd);
struct exynos_irq_chip *our_chip = to_exynos_irq_chip(chip);
struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd);
unsigned long reg_mask;
unsigned int mask;
unsigned long flags;
if (bank->eint_mask_offset)
reg_mask = bank->pctl_offset + bank->eint_mask_offset;
else
reg_mask = our_chip->eint_mask + bank->eint_offset;
if (clk_enable(bank->drvdata->pclk)) {
dev_err(bank->gpio_chip.parent,
"unable to enable clock for masking IRQ\n");
return;
}
raw_spin_lock_irqsave(&bank->slock, flags);
mask = readl(bank->eint_base + reg_mask);
mask |= 1 << irqd->hwirq;
writel(mask, bank->eint_base + reg_mask);
raw_spin_unlock_irqrestore(&bank->slock, flags);
clk_disable(bank->drvdata->pclk);
}
static void exynos_irq_ack(struct irq_data *irqd)
{
struct irq_chip *chip = irq_data_get_irq_chip(irqd);
struct exynos_irq_chip *our_chip = to_exynos_irq_chip(chip);
struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd);
unsigned long reg_pend;
if (bank->eint_pend_offset)
reg_pend = bank->pctl_offset + bank->eint_pend_offset;
else
reg_pend = our_chip->eint_pend + bank->eint_offset;
if (clk_enable(bank->drvdata->pclk)) {
dev_err(bank->gpio_chip.parent,
"unable to enable clock to ack IRQ\n");
return;
}
writel(1 << irqd->hwirq, bank->eint_base + reg_pend);
clk_disable(bank->drvdata->pclk);
}
static void exynos_irq_unmask(struct irq_data *irqd)
{
struct irq_chip *chip = irq_data_get_irq_chip(irqd);
struct exynos_irq_chip *our_chip = to_exynos_irq_chip(chip);
struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd);
unsigned long reg_mask;