| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * OWL SoC's Pinctrl driver |
| * |
| * Copyright (c) 2014 Actions Semi Inc. |
| * Author: David Liu <liuwei@actions-semi.com> |
| * |
| * Copyright (c) 2018 Linaro Ltd. |
| * Author: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> |
| */ |
| |
| #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/seq_file.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| |
| #include <linux/pinctrl/machine.h> |
| #include <linux/pinctrl/pinconf-generic.h> |
| #include <linux/pinctrl/pinconf.h> |
| #include <linux/pinctrl/pinctrl.h> |
| #include <linux/pinctrl/pinmux.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 |
| * @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; |
| 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); |
| irq_hw_number_t hwirq = irqd_to_hwirq(data); |
| const struct owl_gpio_port *port; |
| unsigned int gpio = hwirq; |
| void __iomem *gpio_base; |
| unsigned long flags; |
| 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); |
| |
| gpiochip_disable_irq(gc, hwirq); |
| } |
| |
| 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); |
| irq_hw_number_t hwirq = irqd_to_hwirq(data); |
| const struct owl_gpio_port *port; |
| unsigned int gpio = hwirq; |
| void __iomem *gpio_base; |
| unsigned long flags; |
| u32 value; |
| |
| port = owl_gpio_get_port(pctrl, &gpio); |
| if (WARN_ON(port == NULL)) |
| return; |
| |
| gpiochip_enable_irq(gc, hwirq); |
| |
| 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); |
| irq_hw_number_t hwirq = irqd_to_hwirq(data); |
| const struct owl_gpio_port *port; |
| unsigned int gpio = hwirq; |
| void __iomem *gpio_base; |
| unsigned long flags; |
| |
| /* |
| * 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, hwirq)) |
| irq_set_type(pctrl, hwirq, IRQ_TYPE_EDGE_FALLING); |
| else |
| irq_set_type(pctrl, hwirq, 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 const struct irq_chip owl_gpio_irqchip = { |
| .name = "owl-irq", |
| .irq_ack = owl_gpio_irq_ack, |
| .irq_mask = owl_gpio_irq_mask, |
| .irq_unmask = owl_gpio_irq_unmask, |
| .irq_set_type = owl_gpio_irq_set_type, |
| .flags = IRQCHIP_IMMUTABLE, |
| GPIOCHIP_IRQ_RESOURCE_HELPERS, |
| }; |
| |
| 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, 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) { |
| generic_handle_domain_irq(domain, offset + pin); |
| |
| /* 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; |
| |
| gpio_irq = &chip->irq; |
| gpio_irq_chip_set_chip(gpio_irq, &owl_gpio_irqchip); |
| 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; |
| } |