mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1001 lines
23 KiB
1001 lines
23 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
/* |
|
* OWL SoC's Pinctrl driver |
|
* |
|
* Copyright (c) 2014 Actions Semi Inc. |
|
* Author: David Liu <[email protected]> |
|
* |
|
* Copyright (c) 2018 Linaro Ltd. |
|
* Author: Manivannan Sadhasivam <[email protected]> |
|
*/ |
|
|
|
#include <linux/clk.h> |
|
#include <linux/err.h> |
|
#include <linux/gpio/driver.h> |
|
#include <linux/io.h> |
|
#include <linux/irq.h> |
|
#include <linux/module.h> |
|
#include <linux/of.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/pinctrl/machine.h> |
|
#include <linux/pinctrl/pinctrl.h> |
|
#include <linux/pinctrl/pinmux.h> |
|
#include <linux/pinctrl/pinconf.h> |
|
#include <linux/pinctrl/pinconf-generic.h> |
|
#include <linux/slab.h> |
|
#include <linux/spinlock.h> |
|
|
|
#include "../core.h" |
|
#include "../pinctrl-utils.h" |
|
#include "pinctrl-owl.h" |
|
|
|
/** |
|
* struct owl_pinctrl - pinctrl state of the device |
|
* @dev: device handle |
|
* @pctrldev: pinctrl handle |
|
* @chip: gpio chip |
|
* @lock: spinlock to protect registers |
|
* @clk: clock control |
|
* @soc: reference to soc_data |
|
* @base: pinctrl register base address |
|
* @irq_chip: IRQ chip information |
|
* @num_irq: number of possible interrupts |
|
* @irq: interrupt numbers |
|
*/ |
|
struct owl_pinctrl { |
|
struct device *dev; |
|
struct pinctrl_dev *pctrldev; |
|
struct gpio_chip chip; |
|
raw_spinlock_t lock; |
|
struct clk *clk; |
|
const struct owl_pinctrl_soc_data *soc; |
|
void __iomem *base; |
|
struct irq_chip irq_chip; |
|
unsigned int num_irq; |
|
unsigned int *irq; |
|
}; |
|
|
|
static void owl_update_bits(void __iomem *base, u32 mask, u32 val) |
|
{ |
|
u32 reg_val; |
|
|
|
reg_val = readl_relaxed(base); |
|
|
|
reg_val = (reg_val & ~mask) | (val & mask); |
|
|
|
writel_relaxed(reg_val, base); |
|
} |
|
|
|
static u32 owl_read_field(struct owl_pinctrl *pctrl, u32 reg, |
|
u32 bit, u32 width) |
|
{ |
|
u32 tmp, mask; |
|
|
|
tmp = readl_relaxed(pctrl->base + reg); |
|
mask = (1 << width) - 1; |
|
|
|
return (tmp >> bit) & mask; |
|
} |
|
|
|
static void owl_write_field(struct owl_pinctrl *pctrl, u32 reg, u32 arg, |
|
u32 bit, u32 width) |
|
{ |
|
u32 mask; |
|
|
|
mask = (1 << width) - 1; |
|
mask = mask << bit; |
|
|
|
owl_update_bits(pctrl->base + reg, mask, (arg << bit)); |
|
} |
|
|
|
static int owl_get_groups_count(struct pinctrl_dev *pctrldev) |
|
{ |
|
struct owl_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrldev); |
|
|
|
return pctrl->soc->ngroups; |
|
} |
|
|
|
static const char *owl_get_group_name(struct pinctrl_dev *pctrldev, |
|
unsigned int group) |
|
{ |
|
struct owl_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrldev); |
|
|
|
return pctrl->soc->groups[group].name; |
|
} |
|
|
|
static int owl_get_group_pins(struct pinctrl_dev *pctrldev, |
|
unsigned int group, |
|
const unsigned int **pins, |
|
unsigned int *num_pins) |
|
{ |
|
struct owl_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrldev); |
|
|
|
*pins = pctrl->soc->groups[group].pads; |
|
*num_pins = pctrl->soc->groups[group].npads; |
|
|
|
return 0; |
|
} |
|
|
|
static void owl_pin_dbg_show(struct pinctrl_dev *pctrldev, |
|
struct seq_file *s, |
|
unsigned int offset) |
|
{ |
|
struct owl_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrldev); |
|
|
|
seq_printf(s, "%s", dev_name(pctrl->dev)); |
|
} |
|
|
|
static const struct pinctrl_ops owl_pinctrl_ops = { |
|
.get_groups_count = owl_get_groups_count, |
|
.get_group_name = owl_get_group_name, |
|
.get_group_pins = owl_get_group_pins, |
|
.pin_dbg_show = owl_pin_dbg_show, |
|
.dt_node_to_map = pinconf_generic_dt_node_to_map_all, |
|
.dt_free_map = pinctrl_utils_free_map, |
|
}; |
|
|
|
static int owl_get_funcs_count(struct pinctrl_dev *pctrldev) |
|
{ |
|
struct owl_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrldev); |
|
|
|
return pctrl->soc->nfunctions; |
|
} |
|
|
|
static const char *owl_get_func_name(struct pinctrl_dev *pctrldev, |
|
unsigned int function) |
|
{ |
|
struct owl_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrldev); |
|
|
|
return pctrl->soc->functions[function].name; |
|
} |
|
|
|
static int owl_get_func_groups(struct pinctrl_dev *pctrldev, |
|
unsigned int function, |
|
const char * const **groups, |
|
unsigned int * const num_groups) |
|
{ |
|
struct owl_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrldev); |
|
|
|
*groups = pctrl->soc->functions[function].groups; |
|
*num_groups = pctrl->soc->functions[function].ngroups; |
|
|
|
return 0; |
|
} |
|
|
|
static inline int get_group_mfp_mask_val(const struct owl_pingroup *g, |
|
int function, |
|
u32 *mask, |
|
u32 *val) |
|
{ |
|
int id; |
|
u32 option_num; |
|
u32 option_mask; |
|
|
|
for (id = 0; id < g->nfuncs; id++) { |
|
if (g->funcs[id] == function) |
|
break; |
|
} |
|
if (WARN_ON(id == g->nfuncs)) |
|
return -EINVAL; |
|
|
|
option_num = (1 << g->mfpctl_width); |
|
if (id > option_num) |
|
id -= option_num; |
|
|
|
option_mask = option_num - 1; |
|
*mask = (option_mask << g->mfpctl_shift); |
|
*val = (id << g->mfpctl_shift); |
|
|
|
return 0; |
|
} |
|
|
|
static int owl_set_mux(struct pinctrl_dev *pctrldev, |
|
unsigned int function, |
|
unsigned int group) |
|
{ |
|
struct owl_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrldev); |
|
const struct owl_pingroup *g; |
|
unsigned long flags; |
|
u32 val, mask; |
|
|
|
g = &pctrl->soc->groups[group]; |
|
|
|
if (get_group_mfp_mask_val(g, function, &mask, &val)) |
|
return -EINVAL; |
|
|
|
raw_spin_lock_irqsave(&pctrl->lock, flags); |
|
|
|
owl_update_bits(pctrl->base + g->mfpctl_reg, mask, val); |
|
|
|
raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct pinmux_ops owl_pinmux_ops = { |
|
.get_functions_count = owl_get_funcs_count, |
|
.get_function_name = owl_get_func_name, |
|
.get_function_groups = owl_get_func_groups, |
|
.set_mux = owl_set_mux, |
|
}; |
|
|
|
static int owl_pad_pinconf_reg(const struct owl_padinfo *info, |
|
unsigned int param, |
|
u32 *reg, |
|
u32 *bit, |
|
u32 *width) |
|
{ |
|
switch (param) { |
|
case PIN_CONFIG_BIAS_BUS_HOLD: |
|
case PIN_CONFIG_BIAS_HIGH_IMPEDANCE: |
|
case PIN_CONFIG_BIAS_PULL_DOWN: |
|
case PIN_CONFIG_BIAS_PULL_UP: |
|
if (!info->pullctl) |
|
return -EINVAL; |
|
*reg = info->pullctl->reg; |
|
*bit = info->pullctl->shift; |
|
*width = info->pullctl->width; |
|
break; |
|
case PIN_CONFIG_INPUT_SCHMITT_ENABLE: |
|
if (!info->st) |
|
return -EINVAL; |
|
*reg = info->st->reg; |
|
*bit = info->st->shift; |
|
*width = info->st->width; |
|
break; |
|
default: |
|
return -ENOTSUPP; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int owl_pin_config_get(struct pinctrl_dev *pctrldev, |
|
unsigned int pin, |
|
unsigned long *config) |
|
{ |
|
int ret = 0; |
|
struct owl_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrldev); |
|
const struct owl_padinfo *info; |
|
unsigned int param = pinconf_to_config_param(*config); |
|
u32 reg, bit, width, arg; |
|
|
|
info = &pctrl->soc->padinfo[pin]; |
|
|
|
ret = owl_pad_pinconf_reg(info, param, ®, &bit, &width); |
|
if (ret) |
|
return ret; |
|
|
|
arg = owl_read_field(pctrl, reg, bit, width); |
|
|
|
if (!pctrl->soc->padctl_val2arg) |
|
return -ENOTSUPP; |
|
|
|
ret = pctrl->soc->padctl_val2arg(info, param, &arg); |
|
if (ret) |
|
return ret; |
|
|
|
*config = pinconf_to_config_packed(param, arg); |
|
|
|
return ret; |
|
} |
|
|
|
static int owl_pin_config_set(struct pinctrl_dev *pctrldev, |
|
unsigned int pin, |
|
unsigned long *configs, |
|
unsigned int num_configs) |
|
{ |
|
struct owl_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrldev); |
|
const struct owl_padinfo *info; |
|
unsigned long flags; |
|
unsigned int param; |
|
u32 reg, bit, width, arg; |
|
int ret = 0, i; |
|
|
|
info = &pctrl->soc->padinfo[pin]; |
|
|
|
for (i = 0; i < num_configs; i++) { |
|
param = pinconf_to_config_param(configs[i]); |
|
arg = pinconf_to_config_argument(configs[i]); |
|
|
|
ret = owl_pad_pinconf_reg(info, param, ®, &bit, &width); |
|
if (ret) |
|
return ret; |
|
|
|
if (!pctrl->soc->padctl_arg2val) |
|
return -ENOTSUPP; |
|
|
|
ret = pctrl->soc->padctl_arg2val(info, param, &arg); |
|
if (ret) |
|
return ret; |
|
|
|
raw_spin_lock_irqsave(&pctrl->lock, flags); |
|
|
|
owl_write_field(pctrl, reg, arg, bit, width); |
|
|
|
raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int owl_group_pinconf_reg(const struct owl_pingroup *g, |
|
unsigned int param, |
|
u32 *reg, |
|
u32 *bit, |
|
u32 *width) |
|
{ |
|
switch (param) { |
|
case PIN_CONFIG_DRIVE_STRENGTH: |
|
if (g->drv_reg < 0) |
|
return -EINVAL; |
|
*reg = g->drv_reg; |
|
*bit = g->drv_shift; |
|
*width = g->drv_width; |
|
break; |
|
case PIN_CONFIG_SLEW_RATE: |
|
if (g->sr_reg < 0) |
|
return -EINVAL; |
|
*reg = g->sr_reg; |
|
*bit = g->sr_shift; |
|
*width = g->sr_width; |
|
break; |
|
default: |
|
return -ENOTSUPP; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int owl_group_pinconf_arg2val(const struct owl_pingroup *g, |
|
unsigned int param, |
|
u32 *arg) |
|
{ |
|
switch (param) { |
|
case PIN_CONFIG_DRIVE_STRENGTH: |
|
switch (*arg) { |
|
case 2: |
|
*arg = OWL_PINCONF_DRV_2MA; |
|
break; |
|
case 4: |
|
*arg = OWL_PINCONF_DRV_4MA; |
|
break; |
|
case 8: |
|
*arg = OWL_PINCONF_DRV_8MA; |
|
break; |
|
case 12: |
|
*arg = OWL_PINCONF_DRV_12MA; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
break; |
|
case PIN_CONFIG_SLEW_RATE: |
|
if (*arg) |
|
*arg = OWL_PINCONF_SLEW_FAST; |
|
else |
|
*arg = OWL_PINCONF_SLEW_SLOW; |
|
break; |
|
default: |
|
return -ENOTSUPP; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int owl_group_pinconf_val2arg(const struct owl_pingroup *g, |
|
unsigned int param, |
|
u32 *arg) |
|
{ |
|
switch (param) { |
|
case PIN_CONFIG_DRIVE_STRENGTH: |
|
switch (*arg) { |
|
case OWL_PINCONF_DRV_2MA: |
|
*arg = 2; |
|
break; |
|
case OWL_PINCONF_DRV_4MA: |
|
*arg = 4; |
|
break; |
|
case OWL_PINCONF_DRV_8MA: |
|
*arg = 8; |
|
break; |
|
case OWL_PINCONF_DRV_12MA: |
|
*arg = 12; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
break; |
|
case PIN_CONFIG_SLEW_RATE: |
|
if (*arg) |
|
*arg = 1; |
|
else |
|
*arg = 0; |
|
break; |
|
default: |
|
return -ENOTSUPP; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int owl_group_config_get(struct pinctrl_dev *pctrldev, |
|
unsigned int group, |
|
unsigned long *config) |
|
{ |
|
const struct owl_pingroup *g; |
|
struct owl_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrldev); |
|
unsigned int param = pinconf_to_config_param(*config); |
|
u32 reg, bit, width, arg; |
|
int ret; |
|
|
|
g = &pctrl->soc->groups[group]; |
|
|
|
ret = owl_group_pinconf_reg(g, param, ®, &bit, &width); |
|
if (ret) |
|
return ret; |
|
|
|
arg = owl_read_field(pctrl, reg, bit, width); |
|
|
|
ret = owl_group_pinconf_val2arg(g, param, &arg); |
|
if (ret) |
|
return ret; |
|
|
|
*config = pinconf_to_config_packed(param, arg); |
|
|
|
return ret; |
|
} |
|
|
|
static int owl_group_config_set(struct pinctrl_dev *pctrldev, |
|
unsigned int group, |
|
unsigned long *configs, |
|
unsigned int num_configs) |
|
{ |
|
const struct owl_pingroup *g; |
|
struct owl_pinctrl *pctrl = pinctrl_dev_get_drvdata(pctrldev); |
|
unsigned long flags; |
|
unsigned int param; |
|
u32 reg, bit, width, arg; |
|
int ret, i; |
|
|
|
g = &pctrl->soc->groups[group]; |
|
|
|
for (i = 0; i < num_configs; i++) { |
|
param = pinconf_to_config_param(configs[i]); |
|
arg = pinconf_to_config_argument(configs[i]); |
|
|
|
ret = owl_group_pinconf_reg(g, param, ®, &bit, &width); |
|
if (ret) |
|
return ret; |
|
|
|
ret = owl_group_pinconf_arg2val(g, param, &arg); |
|
if (ret) |
|
return ret; |
|
|
|
/* Update register */ |
|
raw_spin_lock_irqsave(&pctrl->lock, flags); |
|
|
|
owl_write_field(pctrl, reg, arg, bit, width); |
|
|
|
raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static const struct pinconf_ops owl_pinconf_ops = { |
|
.is_generic = true, |
|
.pin_config_get = owl_pin_config_get, |
|
.pin_config_set = owl_pin_config_set, |
|
.pin_config_group_get = owl_group_config_get, |
|
.pin_config_group_set = owl_group_config_set, |
|
}; |
|
|
|
static struct pinctrl_desc owl_pinctrl_desc = { |
|
.pctlops = &owl_pinctrl_ops, |
|
.pmxops = &owl_pinmux_ops, |
|
.confops = &owl_pinconf_ops, |
|
.owner = THIS_MODULE, |
|
}; |
|
|
|
static const struct owl_gpio_port * |
|
owl_gpio_get_port(struct owl_pinctrl *pctrl, unsigned int *pin) |
|
{ |
|
unsigned int start = 0, i; |
|
|
|
for (i = 0; i < pctrl->soc->nports; i++) { |
|
const struct owl_gpio_port *port = &pctrl->soc->ports[i]; |
|
|
|
if (*pin >= start && *pin < start + port->pins) { |
|
*pin -= start; |
|
return port; |
|
} |
|
|
|
start += port->pins; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
static void owl_gpio_update_reg(void __iomem *base, unsigned int pin, int flag) |
|
{ |
|
u32 val; |
|
|
|
val = readl_relaxed(base); |
|
|
|
if (flag) |
|
val |= BIT(pin); |
|
else |
|
val &= ~BIT(pin); |
|
|
|
writel_relaxed(val, base); |
|
} |
|
|
|
static int owl_gpio_request(struct gpio_chip *chip, unsigned int offset) |
|
{ |
|
struct owl_pinctrl *pctrl = gpiochip_get_data(chip); |
|
const struct owl_gpio_port *port; |
|
void __iomem *gpio_base; |
|
unsigned long flags; |
|
|
|
port = owl_gpio_get_port(pctrl, &offset); |
|
if (WARN_ON(port == NULL)) |
|
return -ENODEV; |
|
|
|
gpio_base = pctrl->base + port->offset; |
|
|
|
/* |
|
* GPIOs have higher priority over other modules, so either setting |
|
* them as OUT or IN is sufficient |
|
*/ |
|
raw_spin_lock_irqsave(&pctrl->lock, flags); |
|
owl_gpio_update_reg(gpio_base + port->outen, offset, true); |
|
raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static void owl_gpio_free(struct gpio_chip *chip, unsigned int offset) |
|
{ |
|
struct owl_pinctrl *pctrl = gpiochip_get_data(chip); |
|
const struct owl_gpio_port *port; |
|
void __iomem *gpio_base; |
|
unsigned long flags; |
|
|
|
port = owl_gpio_get_port(pctrl, &offset); |
|
if (WARN_ON(port == NULL)) |
|
return; |
|
|
|
gpio_base = pctrl->base + port->offset; |
|
|
|
raw_spin_lock_irqsave(&pctrl->lock, flags); |
|
/* disable gpio output */ |
|
owl_gpio_update_reg(gpio_base + port->outen, offset, false); |
|
|
|
/* disable gpio input */ |
|
owl_gpio_update_reg(gpio_base + port->inen, offset, false); |
|
raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
|
} |
|
|
|
static int owl_gpio_get(struct gpio_chip *chip, unsigned int offset) |
|
{ |
|
struct owl_pinctrl *pctrl = gpiochip_get_data(chip); |
|
const struct owl_gpio_port *port; |
|
void __iomem *gpio_base; |
|
unsigned long flags; |
|
u32 val; |
|
|
|
port = owl_gpio_get_port(pctrl, &offset); |
|
if (WARN_ON(port == NULL)) |
|
return -ENODEV; |
|
|
|
gpio_base = pctrl->base + port->offset; |
|
|
|
raw_spin_lock_irqsave(&pctrl->lock, flags); |
|
val = readl_relaxed(gpio_base + port->dat); |
|
raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
|
|
|
return !!(val & BIT(offset)); |
|
} |
|
|
|
static void owl_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) |
|
{ |
|
struct owl_pinctrl *pctrl = gpiochip_get_data(chip); |
|
const struct owl_gpio_port *port; |
|
void __iomem *gpio_base; |
|
unsigned long flags; |
|
|
|
port = owl_gpio_get_port(pctrl, &offset); |
|
if (WARN_ON(port == NULL)) |
|
return; |
|
|
|
gpio_base = pctrl->base + port->offset; |
|
|
|
raw_spin_lock_irqsave(&pctrl->lock, flags); |
|
owl_gpio_update_reg(gpio_base + port->dat, offset, value); |
|
raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
|
} |
|
|
|
static int owl_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) |
|
{ |
|
struct owl_pinctrl *pctrl = gpiochip_get_data(chip); |
|
const struct owl_gpio_port *port; |
|
void __iomem *gpio_base; |
|
unsigned long flags; |
|
|
|
port = owl_gpio_get_port(pctrl, &offset); |
|
if (WARN_ON(port == NULL)) |
|
return -ENODEV; |
|
|
|
gpio_base = pctrl->base + port->offset; |
|
|
|
raw_spin_lock_irqsave(&pctrl->lock, flags); |
|
owl_gpio_update_reg(gpio_base + port->outen, offset, false); |
|
owl_gpio_update_reg(gpio_base + port->inen, offset, true); |
|
raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static int owl_gpio_direction_output(struct gpio_chip *chip, |
|
unsigned int offset, int value) |
|
{ |
|
struct owl_pinctrl *pctrl = gpiochip_get_data(chip); |
|
const struct owl_gpio_port *port; |
|
void __iomem *gpio_base; |
|
unsigned long flags; |
|
|
|
port = owl_gpio_get_port(pctrl, &offset); |
|
if (WARN_ON(port == NULL)) |
|
return -ENODEV; |
|
|
|
gpio_base = pctrl->base + port->offset; |
|
|
|
raw_spin_lock_irqsave(&pctrl->lock, flags); |
|
owl_gpio_update_reg(gpio_base + port->inen, offset, false); |
|
owl_gpio_update_reg(gpio_base + port->outen, offset, true); |
|
owl_gpio_update_reg(gpio_base + port->dat, offset, value); |
|
raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static void irq_set_type(struct owl_pinctrl *pctrl, int gpio, unsigned int type) |
|
{ |
|
const struct owl_gpio_port *port; |
|
void __iomem *gpio_base; |
|
unsigned long flags; |
|
unsigned int offset, value, irq_type = 0; |
|
|
|
switch (type) { |
|
case IRQ_TYPE_EDGE_BOTH: |
|
/* |
|
* Since the hardware doesn't support interrupts on both edges, |
|
* emulate it in the software by setting the single edge |
|
* interrupt and switching to the opposite edge while ACKing |
|
* the interrupt |
|
*/ |
|
if (owl_gpio_get(&pctrl->chip, gpio)) |
|
irq_type = OWL_GPIO_INT_EDGE_FALLING; |
|
else |
|
irq_type = OWL_GPIO_INT_EDGE_RISING; |
|
break; |
|
|
|
case IRQ_TYPE_EDGE_RISING: |
|
irq_type = OWL_GPIO_INT_EDGE_RISING; |
|
break; |
|
|
|
case IRQ_TYPE_EDGE_FALLING: |
|
irq_type = OWL_GPIO_INT_EDGE_FALLING; |
|
break; |
|
|
|
case IRQ_TYPE_LEVEL_HIGH: |
|
irq_type = OWL_GPIO_INT_LEVEL_HIGH; |
|
break; |
|
|
|
case IRQ_TYPE_LEVEL_LOW: |
|
irq_type = OWL_GPIO_INT_LEVEL_LOW; |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
|
|
port = owl_gpio_get_port(pctrl, &gpio); |
|
if (WARN_ON(port == NULL)) |
|
return; |
|
|
|
gpio_base = pctrl->base + port->offset; |
|
|
|
raw_spin_lock_irqsave(&pctrl->lock, flags); |
|
|
|
offset = (gpio < 16) ? 4 : 0; |
|
value = readl_relaxed(gpio_base + port->intc_type + offset); |
|
value &= ~(OWL_GPIO_INT_MASK << ((gpio % 16) * 2)); |
|
value |= irq_type << ((gpio % 16) * 2); |
|
writel_relaxed(value, gpio_base + port->intc_type + offset); |
|
|
|
raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
|
} |
|
|
|
static void owl_gpio_irq_mask(struct irq_data *data) |
|
{ |
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(data); |
|
struct owl_pinctrl *pctrl = gpiochip_get_data(gc); |
|
const struct owl_gpio_port *port; |
|
void __iomem *gpio_base; |
|
unsigned long flags; |
|
unsigned int gpio = data->hwirq; |
|
u32 val; |
|
|
|
port = owl_gpio_get_port(pctrl, &gpio); |
|
if (WARN_ON(port == NULL)) |
|
return; |
|
|
|
gpio_base = pctrl->base + port->offset; |
|
|
|
raw_spin_lock_irqsave(&pctrl->lock, flags); |
|
|
|
owl_gpio_update_reg(gpio_base + port->intc_msk, gpio, false); |
|
|
|
/* disable port interrupt if no interrupt pending bit is active */ |
|
val = readl_relaxed(gpio_base + port->intc_msk); |
|
if (val == 0) |
|
owl_gpio_update_reg(gpio_base + port->intc_ctl, |
|
OWL_GPIO_CTLR_ENABLE + port->shared_ctl_offset * 5, false); |
|
|
|
raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
|
} |
|
|
|
static void owl_gpio_irq_unmask(struct irq_data *data) |
|
{ |
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(data); |
|
struct owl_pinctrl *pctrl = gpiochip_get_data(gc); |
|
const struct owl_gpio_port *port; |
|
void __iomem *gpio_base; |
|
unsigned long flags; |
|
unsigned int gpio = data->hwirq; |
|
u32 value; |
|
|
|
port = owl_gpio_get_port(pctrl, &gpio); |
|
if (WARN_ON(port == NULL)) |
|
return; |
|
|
|
gpio_base = pctrl->base + port->offset; |
|
raw_spin_lock_irqsave(&pctrl->lock, flags); |
|
|
|
/* enable port interrupt */ |
|
value = readl_relaxed(gpio_base + port->intc_ctl); |
|
value |= ((BIT(OWL_GPIO_CTLR_ENABLE) | BIT(OWL_GPIO_CTLR_SAMPLE_CLK_24M)) |
|
<< port->shared_ctl_offset * 5); |
|
writel_relaxed(value, gpio_base + port->intc_ctl); |
|
|
|
/* enable GPIO interrupt */ |
|
owl_gpio_update_reg(gpio_base + port->intc_msk, gpio, true); |
|
|
|
raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
|
} |
|
|
|
static void owl_gpio_irq_ack(struct irq_data *data) |
|
{ |
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(data); |
|
struct owl_pinctrl *pctrl = gpiochip_get_data(gc); |
|
const struct owl_gpio_port *port; |
|
void __iomem *gpio_base; |
|
unsigned long flags; |
|
unsigned int gpio = data->hwirq; |
|
|
|
/* |
|
* Switch the interrupt edge to the opposite edge of the interrupt |
|
* which got triggered for the case of emulating both edges |
|
*/ |
|
if (irqd_get_trigger_type(data) == IRQ_TYPE_EDGE_BOTH) { |
|
if (owl_gpio_get(gc, gpio)) |
|
irq_set_type(pctrl, gpio, IRQ_TYPE_EDGE_FALLING); |
|
else |
|
irq_set_type(pctrl, gpio, IRQ_TYPE_EDGE_RISING); |
|
} |
|
|
|
port = owl_gpio_get_port(pctrl, &gpio); |
|
if (WARN_ON(port == NULL)) |
|
return; |
|
|
|
gpio_base = pctrl->base + port->offset; |
|
|
|
raw_spin_lock_irqsave(&pctrl->lock, flags); |
|
|
|
owl_gpio_update_reg(gpio_base + port->intc_ctl, |
|
OWL_GPIO_CTLR_PENDING + port->shared_ctl_offset * 5, true); |
|
|
|
raw_spin_unlock_irqrestore(&pctrl->lock, flags); |
|
} |
|
|
|
static int owl_gpio_irq_set_type(struct irq_data *data, unsigned int type) |
|
{ |
|
struct gpio_chip *gc = irq_data_get_irq_chip_data(data); |
|
struct owl_pinctrl *pctrl = gpiochip_get_data(gc); |
|
|
|
if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) |
|
irq_set_handler_locked(data, handle_level_irq); |
|
else |
|
irq_set_handler_locked(data, handle_edge_irq); |
|
|
|
irq_set_type(pctrl, data->hwirq, type); |
|
|
|
return 0; |
|
} |
|
|
|
static void owl_gpio_irq_handler(struct irq_desc *desc) |
|
{ |
|
struct owl_pinctrl *pctrl = irq_desc_get_handler_data(desc); |
|
struct irq_chip *chip = irq_desc_get_chip(desc); |
|
struct irq_domain *domain = pctrl->chip.irq.domain; |
|
unsigned int parent = irq_desc_get_irq(desc); |
|
const struct owl_gpio_port *port; |
|
void __iomem *base; |
|
unsigned int pin, irq, offset = 0, i; |
|
unsigned long pending_irq; |
|
|
|
chained_irq_enter(chip, desc); |
|
|
|
for (i = 0; i < pctrl->soc->nports; i++) { |
|
port = &pctrl->soc->ports[i]; |
|
base = pctrl->base + port->offset; |
|
|
|
/* skip ports that are not associated with this irq */ |
|
if (parent != pctrl->irq[i]) |
|
goto skip; |
|
|
|
pending_irq = readl_relaxed(base + port->intc_pd); |
|
|
|
for_each_set_bit(pin, &pending_irq, port->pins) { |
|
irq = irq_find_mapping(domain, offset + pin); |
|
generic_handle_irq(irq); |
|
|
|
/* clear pending interrupt */ |
|
owl_gpio_update_reg(base + port->intc_pd, pin, true); |
|
} |
|
|
|
skip: |
|
offset += port->pins; |
|
} |
|
|
|
chained_irq_exit(chip, desc); |
|
} |
|
|
|
static int owl_gpio_init(struct owl_pinctrl *pctrl) |
|
{ |
|
struct gpio_chip *chip; |
|
struct gpio_irq_chip *gpio_irq; |
|
int ret, i, j, offset; |
|
|
|
chip = &pctrl->chip; |
|
chip->base = -1; |
|
chip->ngpio = pctrl->soc->ngpios; |
|
chip->label = dev_name(pctrl->dev); |
|
chip->parent = pctrl->dev; |
|
chip->owner = THIS_MODULE; |
|
chip->of_node = pctrl->dev->of_node; |
|
|
|
pctrl->irq_chip.name = chip->of_node->name; |
|
pctrl->irq_chip.irq_ack = owl_gpio_irq_ack; |
|
pctrl->irq_chip.irq_mask = owl_gpio_irq_mask; |
|
pctrl->irq_chip.irq_unmask = owl_gpio_irq_unmask; |
|
pctrl->irq_chip.irq_set_type = owl_gpio_irq_set_type; |
|
|
|
gpio_irq = &chip->irq; |
|
gpio_irq->chip = &pctrl->irq_chip; |
|
gpio_irq->handler = handle_simple_irq; |
|
gpio_irq->default_type = IRQ_TYPE_NONE; |
|
gpio_irq->parent_handler = owl_gpio_irq_handler; |
|
gpio_irq->parent_handler_data = pctrl; |
|
gpio_irq->num_parents = pctrl->num_irq; |
|
gpio_irq->parents = pctrl->irq; |
|
|
|
gpio_irq->map = devm_kcalloc(pctrl->dev, chip->ngpio, |
|
sizeof(*gpio_irq->map), GFP_KERNEL); |
|
if (!gpio_irq->map) |
|
return -ENOMEM; |
|
|
|
for (i = 0, offset = 0; i < pctrl->soc->nports; i++) { |
|
const struct owl_gpio_port *port = &pctrl->soc->ports[i]; |
|
|
|
for (j = 0; j < port->pins; j++) |
|
gpio_irq->map[offset + j] = gpio_irq->parents[i]; |
|
|
|
offset += port->pins; |
|
} |
|
|
|
ret = gpiochip_add_data(&pctrl->chip, pctrl); |
|
if (ret) { |
|
dev_err(pctrl->dev, "failed to register gpiochip\n"); |
|
return ret; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int owl_pinctrl_probe(struct platform_device *pdev, |
|
struct owl_pinctrl_soc_data *soc_data) |
|
{ |
|
struct owl_pinctrl *pctrl; |
|
int ret, i; |
|
|
|
pctrl = devm_kzalloc(&pdev->dev, sizeof(*pctrl), GFP_KERNEL); |
|
if (!pctrl) |
|
return -ENOMEM; |
|
|
|
pctrl->base = devm_platform_ioremap_resource(pdev, 0); |
|
if (IS_ERR(pctrl->base)) |
|
return PTR_ERR(pctrl->base); |
|
|
|
/* enable GPIO/MFP clock */ |
|
pctrl->clk = devm_clk_get(&pdev->dev, NULL); |
|
if (IS_ERR(pctrl->clk)) { |
|
dev_err(&pdev->dev, "no clock defined\n"); |
|
return PTR_ERR(pctrl->clk); |
|
} |
|
|
|
ret = clk_prepare_enable(pctrl->clk); |
|
if (ret) { |
|
dev_err(&pdev->dev, "clk enable failed\n"); |
|
return ret; |
|
} |
|
|
|
raw_spin_lock_init(&pctrl->lock); |
|
|
|
owl_pinctrl_desc.name = dev_name(&pdev->dev); |
|
owl_pinctrl_desc.pins = soc_data->pins; |
|
owl_pinctrl_desc.npins = soc_data->npins; |
|
|
|
pctrl->chip.direction_input = owl_gpio_direction_input; |
|
pctrl->chip.direction_output = owl_gpio_direction_output; |
|
pctrl->chip.get = owl_gpio_get; |
|
pctrl->chip.set = owl_gpio_set; |
|
pctrl->chip.request = owl_gpio_request; |
|
pctrl->chip.free = owl_gpio_free; |
|
|
|
pctrl->soc = soc_data; |
|
pctrl->dev = &pdev->dev; |
|
|
|
pctrl->pctrldev = devm_pinctrl_register(&pdev->dev, |
|
&owl_pinctrl_desc, pctrl); |
|
if (IS_ERR(pctrl->pctrldev)) { |
|
dev_err(&pdev->dev, "could not register Actions OWL pinmux driver\n"); |
|
ret = PTR_ERR(pctrl->pctrldev); |
|
goto err_exit; |
|
} |
|
|
|
ret = platform_irq_count(pdev); |
|
if (ret < 0) |
|
goto err_exit; |
|
|
|
pctrl->num_irq = ret; |
|
|
|
pctrl->irq = devm_kcalloc(&pdev->dev, pctrl->num_irq, |
|
sizeof(*pctrl->irq), GFP_KERNEL); |
|
if (!pctrl->irq) { |
|
ret = -ENOMEM; |
|
goto err_exit; |
|
} |
|
|
|
for (i = 0; i < pctrl->num_irq ; i++) { |
|
ret = platform_get_irq(pdev, i); |
|
if (ret < 0) |
|
goto err_exit; |
|
pctrl->irq[i] = ret; |
|
} |
|
|
|
ret = owl_gpio_init(pctrl); |
|
if (ret) |
|
goto err_exit; |
|
|
|
platform_set_drvdata(pdev, pctrl); |
|
|
|
return 0; |
|
|
|
err_exit: |
|
clk_disable_unprepare(pctrl->clk); |
|
|
|
return ret; |
|
}
|
|
|