| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * GPIO driver for the IP block found in the Nomadik SoC; it is an AMBA device, |
| * managing 32 pins with alternate functions. It can also handle the STA2X11 |
| * block from ST. |
| * |
| * The GPIO chips are shared with pinctrl-nomadik if used; it needs access for |
| * pinmuxing functionality and others. |
| * |
| * This driver also handles the mobileye,eyeq5-gpio compatible. It is an STA2X11 |
| * but with only data, direction and interrupts register active. We want to |
| * avoid touching SLPM, RWIMSC, FWIMSC, AFSLA and AFSLB registers; that is, |
| * wake and alternate function registers. It is NOT compatible with |
| * pinctrl-nomadik. |
| * |
| * Copyright (C) 2008,2009 STMicroelectronics |
| * Copyright (C) 2009 Alessandro Rubini <rubini@unipv.it> |
| * Rewritten based on work by Prafulla WADASKAR <prafulla.wadaskar@st.com> |
| * Copyright (C) 2011-2013 Linus Walleij <linus.walleij@linaro.org> |
| */ |
| #include <linux/cleanup.h> |
| #include <linux/clk.h> |
| #include <linux/gpio/driver.h> |
| #include <linux/interrupt.h> |
| #include <linux/kernel.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/pinctrl/pinctrl.h> |
| #include <linux/platform_device.h> |
| #include <linux/property.h> |
| #include <linux/reset.h> |
| #include <linux/seq_file.h> |
| #include <linux/slab.h> |
| #include <linux/types.h> |
| |
| #include <linux/gpio/gpio-nomadik.h> |
| |
| #ifndef CONFIG_PINCTRL_NOMADIK |
| static DEFINE_SPINLOCK(nmk_gpio_slpm_lock); |
| #endif |
| |
| void __nmk_gpio_set_slpm(struct nmk_gpio_chip *nmk_chip, unsigned int offset, |
| enum nmk_gpio_slpm mode) |
| { |
| u32 slpm; |
| |
| /* We should NOT have been called. */ |
| if (WARN_ON(nmk_chip->is_mobileye_soc)) |
| return; |
| |
| slpm = readl(nmk_chip->addr + NMK_GPIO_SLPC); |
| if (mode == NMK_GPIO_SLPM_NOCHANGE) |
| slpm |= BIT(offset); |
| else |
| slpm &= ~BIT(offset); |
| writel(slpm, nmk_chip->addr + NMK_GPIO_SLPC); |
| } |
| |
| static void __nmk_gpio_set_output(struct nmk_gpio_chip *nmk_chip, |
| unsigned int offset, int val) |
| { |
| if (val) |
| writel(BIT(offset), nmk_chip->addr + NMK_GPIO_DATS); |
| else |
| writel(BIT(offset), nmk_chip->addr + NMK_GPIO_DATC); |
| } |
| |
| void __nmk_gpio_make_output(struct nmk_gpio_chip *nmk_chip, |
| unsigned int offset, int val) |
| { |
| writel(BIT(offset), nmk_chip->addr + NMK_GPIO_DIRS); |
| __nmk_gpio_set_output(nmk_chip, offset, val); |
| } |
| |
| /* IRQ functions */ |
| |
| static void nmk_gpio_irq_ack(struct irq_data *d) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); |
| |
| clk_enable(nmk_chip->clk); |
| writel(BIT(d->hwirq), nmk_chip->addr + NMK_GPIO_IC); |
| clk_disable(nmk_chip->clk); |
| } |
| |
| enum nmk_gpio_irq_type { |
| NORMAL, |
| WAKE, |
| }; |
| |
| static void __nmk_gpio_irq_modify(struct nmk_gpio_chip *nmk_chip, |
| int offset, enum nmk_gpio_irq_type which, |
| bool enable) |
| { |
| u32 *rimscval; |
| u32 *fimscval; |
| u32 rimscreg; |
| u32 fimscreg; |
| |
| if (which == NORMAL) { |
| rimscreg = NMK_GPIO_RIMSC; |
| fimscreg = NMK_GPIO_FIMSC; |
| rimscval = &nmk_chip->rimsc; |
| fimscval = &nmk_chip->fimsc; |
| } else { |
| /* We should NOT have been called. */ |
| if (WARN_ON(nmk_chip->is_mobileye_soc)) |
| return; |
| rimscreg = NMK_GPIO_RWIMSC; |
| fimscreg = NMK_GPIO_FWIMSC; |
| rimscval = &nmk_chip->rwimsc; |
| fimscval = &nmk_chip->fwimsc; |
| } |
| |
| /* we must individually set/clear the two edges */ |
| if (nmk_chip->edge_rising & BIT(offset)) { |
| if (enable) |
| *rimscval |= BIT(offset); |
| else |
| *rimscval &= ~BIT(offset); |
| writel(*rimscval, nmk_chip->addr + rimscreg); |
| } |
| if (nmk_chip->edge_falling & BIT(offset)) { |
| if (enable) |
| *fimscval |= BIT(offset); |
| else |
| *fimscval &= ~BIT(offset); |
| writel(*fimscval, nmk_chip->addr + fimscreg); |
| } |
| } |
| |
| static void __nmk_gpio_set_wake(struct nmk_gpio_chip *nmk_chip, |
| int offset, bool on) |
| { |
| /* We should NOT have been called. */ |
| if (WARN_ON(nmk_chip->is_mobileye_soc)) |
| return; |
| |
| /* |
| * Ensure WAKEUP_ENABLE is on. No need to disable it if wakeup is |
| * disabled, since setting SLPM to 1 increases power consumption, and |
| * wakeup is anyhow controlled by the RIMSC and FIMSC registers. |
| */ |
| if (nmk_chip->sleepmode && on) { |
| __nmk_gpio_set_slpm(nmk_chip, offset, |
| NMK_GPIO_SLPM_WAKEUP_ENABLE); |
| } |
| |
| __nmk_gpio_irq_modify(nmk_chip, offset, WAKE, on); |
| } |
| |
| static void nmk_gpio_irq_maskunmask(struct nmk_gpio_chip *nmk_chip, |
| struct irq_data *d, bool enable) |
| { |
| unsigned long flags; |
| |
| clk_enable(nmk_chip->clk); |
| spin_lock_irqsave(&nmk_gpio_slpm_lock, flags); |
| spin_lock(&nmk_chip->lock); |
| |
| __nmk_gpio_irq_modify(nmk_chip, d->hwirq, NORMAL, enable); |
| |
| if (!nmk_chip->is_mobileye_soc && !(nmk_chip->real_wake & BIT(d->hwirq))) |
| __nmk_gpio_set_wake(nmk_chip, d->hwirq, enable); |
| |
| spin_unlock(&nmk_chip->lock); |
| spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags); |
| clk_disable(nmk_chip->clk); |
| } |
| |
| static void nmk_gpio_irq_mask(struct irq_data *d) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); |
| |
| nmk_gpio_irq_maskunmask(nmk_chip, d, false); |
| gpiochip_disable_irq(gc, irqd_to_hwirq(d)); |
| } |
| |
| static void nmk_gpio_irq_unmask(struct irq_data *d) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); |
| |
| gpiochip_enable_irq(gc, irqd_to_hwirq(d)); |
| nmk_gpio_irq_maskunmask(nmk_chip, d, true); |
| } |
| |
| static int nmk_gpio_irq_set_wake(struct irq_data *d, unsigned int on) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); |
| unsigned long flags; |
| |
| /* Handler is registered in all cases. */ |
| if (nmk_chip->is_mobileye_soc) |
| return -ENXIO; |
| |
| clk_enable(nmk_chip->clk); |
| spin_lock_irqsave(&nmk_gpio_slpm_lock, flags); |
| spin_lock(&nmk_chip->lock); |
| |
| if (irqd_irq_disabled(d)) |
| __nmk_gpio_set_wake(nmk_chip, d->hwirq, on); |
| |
| if (on) |
| nmk_chip->real_wake |= BIT(d->hwirq); |
| else |
| nmk_chip->real_wake &= ~BIT(d->hwirq); |
| |
| spin_unlock(&nmk_chip->lock); |
| spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags); |
| clk_disable(nmk_chip->clk); |
| |
| return 0; |
| } |
| |
| static int nmk_gpio_irq_set_type(struct irq_data *d, unsigned int type) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); |
| bool enabled = !irqd_irq_disabled(d); |
| bool wake = irqd_is_wakeup_set(d); |
| unsigned long flags; |
| |
| if (type & IRQ_TYPE_LEVEL_HIGH) |
| return -EINVAL; |
| if (type & IRQ_TYPE_LEVEL_LOW) |
| return -EINVAL; |
| |
| clk_enable(nmk_chip->clk); |
| spin_lock_irqsave(&nmk_chip->lock, flags); |
| |
| if (enabled) |
| __nmk_gpio_irq_modify(nmk_chip, d->hwirq, NORMAL, false); |
| |
| if (!nmk_chip->is_mobileye_soc && (enabled || wake)) |
| __nmk_gpio_irq_modify(nmk_chip, d->hwirq, WAKE, false); |
| |
| nmk_chip->edge_rising &= ~BIT(d->hwirq); |
| if (type & IRQ_TYPE_EDGE_RISING) |
| nmk_chip->edge_rising |= BIT(d->hwirq); |
| |
| nmk_chip->edge_falling &= ~BIT(d->hwirq); |
| if (type & IRQ_TYPE_EDGE_FALLING) |
| nmk_chip->edge_falling |= BIT(d->hwirq); |
| |
| if (enabled) |
| __nmk_gpio_irq_modify(nmk_chip, d->hwirq, NORMAL, true); |
| |
| if (!nmk_chip->is_mobileye_soc && (enabled || wake)) |
| __nmk_gpio_irq_modify(nmk_chip, d->hwirq, WAKE, true); |
| |
| spin_unlock_irqrestore(&nmk_chip->lock, flags); |
| clk_disable(nmk_chip->clk); |
| |
| return 0; |
| } |
| |
| static unsigned int nmk_gpio_irq_startup(struct irq_data *d) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); |
| |
| clk_enable(nmk_chip->clk); |
| nmk_gpio_irq_unmask(d); |
| return 0; |
| } |
| |
| static void nmk_gpio_irq_shutdown(struct irq_data *d) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); |
| |
| nmk_gpio_irq_mask(d); |
| clk_disable(nmk_chip->clk); |
| } |
| |
| static irqreturn_t nmk_gpio_irq_handler(int irq, void *dev_id) |
| { |
| struct nmk_gpio_chip *nmk_chip = dev_id; |
| struct gpio_chip *chip = &nmk_chip->chip; |
| unsigned long mask = GENMASK(chip->ngpio - 1, 0); |
| unsigned long status; |
| int bit; |
| |
| clk_enable(nmk_chip->clk); |
| |
| status = readl(nmk_chip->addr + NMK_GPIO_IS); |
| |
| /* Ensure we cannot leave pending bits; this should never occur. */ |
| if (unlikely(status & ~mask)) |
| writel(status & ~mask, nmk_chip->addr + NMK_GPIO_IC); |
| |
| clk_disable(nmk_chip->clk); |
| |
| for_each_set_bit(bit, &status, chip->ngpio) |
| generic_handle_domain_irq_safe(chip->irq.domain, bit); |
| |
| return IRQ_RETVAL((status & mask) != 0); |
| } |
| |
| /* I/O Functions */ |
| |
| static int nmk_gpio_get_dir(struct gpio_chip *chip, unsigned int offset) |
| { |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); |
| int dir; |
| |
| clk_enable(nmk_chip->clk); |
| |
| dir = readl(nmk_chip->addr + NMK_GPIO_DIR) & BIT(offset); |
| |
| clk_disable(nmk_chip->clk); |
| |
| if (dir) |
| return GPIO_LINE_DIRECTION_OUT; |
| |
| return GPIO_LINE_DIRECTION_IN; |
| } |
| |
| static int nmk_gpio_make_input(struct gpio_chip *chip, unsigned int offset) |
| { |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); |
| |
| clk_enable(nmk_chip->clk); |
| |
| writel(BIT(offset), nmk_chip->addr + NMK_GPIO_DIRC); |
| |
| clk_disable(nmk_chip->clk); |
| |
| return 0; |
| } |
| |
| static int nmk_gpio_get_input(struct gpio_chip *chip, unsigned int offset) |
| { |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); |
| int value; |
| |
| clk_enable(nmk_chip->clk); |
| |
| value = !!(readl(nmk_chip->addr + NMK_GPIO_DAT) & BIT(offset)); |
| |
| clk_disable(nmk_chip->clk); |
| |
| return value; |
| } |
| |
| static void nmk_gpio_set_output(struct gpio_chip *chip, unsigned int offset, |
| int val) |
| { |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); |
| |
| clk_enable(nmk_chip->clk); |
| |
| __nmk_gpio_set_output(nmk_chip, offset, val); |
| |
| clk_disable(nmk_chip->clk); |
| } |
| |
| static int nmk_gpio_make_output(struct gpio_chip *chip, unsigned int offset, |
| int val) |
| { |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); |
| |
| clk_enable(nmk_chip->clk); |
| |
| __nmk_gpio_make_output(nmk_chip, offset, val); |
| |
| clk_disable(nmk_chip->clk); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_DEBUG_FS |
| |
| static int nmk_gpio_get_mode(struct nmk_gpio_chip *nmk_chip, int offset) |
| { |
| u32 afunc, bfunc; |
| |
| /* We don't support modes. */ |
| if (nmk_chip->is_mobileye_soc) |
| return NMK_GPIO_ALT_GPIO; |
| |
| clk_enable(nmk_chip->clk); |
| |
| afunc = readl(nmk_chip->addr + NMK_GPIO_AFSLA) & BIT(offset); |
| bfunc = readl(nmk_chip->addr + NMK_GPIO_AFSLB) & BIT(offset); |
| |
| clk_disable(nmk_chip->clk); |
| |
| return (afunc ? NMK_GPIO_ALT_A : 0) | (bfunc ? NMK_GPIO_ALT_B : 0); |
| } |
| |
| void nmk_gpio_dbg_show_one(struct seq_file *s, struct pinctrl_dev *pctldev, |
| struct gpio_chip *chip, unsigned int offset, |
| unsigned int gpio) |
| { |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); |
| int mode; |
| bool is_out; |
| bool data_out; |
| bool pull; |
| static const char * const modes[] = { |
| [NMK_GPIO_ALT_GPIO] = "gpio", |
| [NMK_GPIO_ALT_A] = "altA", |
| [NMK_GPIO_ALT_B] = "altB", |
| [NMK_GPIO_ALT_C] = "altC", |
| [NMK_GPIO_ALT_C + 1] = "altC1", |
| [NMK_GPIO_ALT_C + 2] = "altC2", |
| [NMK_GPIO_ALT_C + 3] = "altC3", |
| [NMK_GPIO_ALT_C + 4] = "altC4", |
| }; |
| |
| char *label = gpiochip_dup_line_label(chip, offset); |
| if (IS_ERR(label)) |
| return; |
| |
| clk_enable(nmk_chip->clk); |
| is_out = !!(readl(nmk_chip->addr + NMK_GPIO_DIR) & BIT(offset)); |
| pull = !(readl(nmk_chip->addr + NMK_GPIO_PDIS) & BIT(offset)); |
| data_out = !!(readl(nmk_chip->addr + NMK_GPIO_DAT) & BIT(offset)); |
| mode = nmk_gpio_get_mode(nmk_chip, offset); |
| #ifdef CONFIG_PINCTRL_NOMADIK |
| if (mode == NMK_GPIO_ALT_C && pctldev) |
| mode = nmk_prcm_gpiocr_get_mode(pctldev, gpio); |
| #endif |
| |
| if (is_out) { |
| seq_printf(s, " gpio-%-3d (%-20.20s) out %s %s", |
| gpio, |
| label ?: "(none)", |
| data_out ? "hi" : "lo", |
| (mode < 0) ? "unknown" : modes[mode]); |
| } else { |
| int irq = chip->to_irq(chip, offset); |
| const int pullidx = pull ? 1 : 0; |
| int val; |
| static const char * const pulls[] = { |
| "none ", |
| "pull enabled", |
| }; |
| |
| seq_printf(s, " gpio-%-3d (%-20.20s) in %s %s", |
| gpio, |
| label ?: "(none)", |
| pulls[pullidx], |
| (mode < 0) ? "unknown" : modes[mode]); |
| |
| val = nmk_gpio_get_input(chip, offset); |
| seq_printf(s, " VAL %d", val); |
| |
| /* |
| * This races with request_irq(), set_irq_type(), |
| * and set_irq_wake() ... but those are "rare". |
| */ |
| if (irq > 0 && irq_has_action(irq)) { |
| char *trigger; |
| bool wake; |
| |
| if (nmk_chip->edge_rising & BIT(offset)) |
| trigger = "edge-rising"; |
| else if (nmk_chip->edge_falling & BIT(offset)) |
| trigger = "edge-falling"; |
| else |
| trigger = "edge-undefined"; |
| |
| wake = !!(nmk_chip->real_wake & BIT(offset)); |
| |
| seq_printf(s, " irq-%d %s%s", |
| irq, trigger, wake ? " wakeup" : ""); |
| } |
| } |
| clk_disable(nmk_chip->clk); |
| } |
| |
| static void nmk_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) |
| { |
| unsigned int i, gpio = chip->base; |
| |
| for (i = 0; i < chip->ngpio; i++, gpio++) { |
| nmk_gpio_dbg_show_one(s, NULL, chip, i, gpio); |
| seq_puts(s, "\n"); |
| } |
| } |
| |
| #else |
| |
| #define nmk_gpio_dbg_show NULL |
| |
| #endif |
| |
| /* |
| * We will allocate memory for the state container using devm* allocators |
| * binding to the first device reaching this point, it doesn't matter if |
| * it is the pin controller or GPIO driver. However we need to use the right |
| * platform device when looking up resources so pay attention to pdev. |
| */ |
| struct nmk_gpio_chip *nmk_gpio_populate_chip(struct fwnode_handle *fwnode, |
| struct platform_device *pdev) |
| { |
| struct nmk_gpio_chip *nmk_chip; |
| struct platform_device *gpio_pdev; |
| struct device *dev = &pdev->dev; |
| struct reset_control *reset; |
| struct device *gpio_dev; |
| struct gpio_chip *chip; |
| struct resource *res; |
| struct clk *clk; |
| void __iomem *base; |
| u32 id, ngpio; |
| int ret; |
| |
| gpio_dev = bus_find_device_by_fwnode(&platform_bus_type, fwnode); |
| if (!gpio_dev) { |
| dev_err(dev, "populate \"%pfwP\": device not found\n", fwnode); |
| return ERR_PTR(-ENODEV); |
| } |
| gpio_pdev = to_platform_device(gpio_dev); |
| |
| if (device_property_read_u32(gpio_dev, "gpio-bank", &id)) { |
| dev_err(dev, "populate: gpio-bank property not found\n"); |
| platform_device_put(gpio_pdev); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| #ifdef CONFIG_PINCTRL_NOMADIK |
| if (id >= ARRAY_SIZE(nmk_gpio_chips)) { |
| dev_err(dev, "populate: invalid id: %u\n", id); |
| platform_device_put(gpio_pdev); |
| return ERR_PTR(-EINVAL); |
| } |
| /* Already populated? */ |
| nmk_chip = nmk_gpio_chips[id]; |
| if (nmk_chip) { |
| platform_device_put(gpio_pdev); |
| return nmk_chip; |
| } |
| #endif |
| |
| nmk_chip = devm_kzalloc(dev, sizeof(*nmk_chip), GFP_KERNEL); |
| if (!nmk_chip) { |
| platform_device_put(gpio_pdev); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| if (device_property_read_u32(gpio_dev, "ngpios", &ngpio)) { |
| ngpio = NMK_GPIO_PER_CHIP; |
| dev_dbg(dev, "populate: using default ngpio (%u)\n", ngpio); |
| } |
| |
| nmk_chip->is_mobileye_soc = device_is_compatible(gpio_dev, |
| "mobileye,eyeq5-gpio"); |
| nmk_chip->bank = id; |
| chip = &nmk_chip->chip; |
| chip->base = -1; |
| chip->ngpio = ngpio; |
| chip->label = dev_name(gpio_dev); |
| chip->parent = gpio_dev; |
| |
| /* NOTE: different devices! No devm_platform_ioremap_resource() here! */ |
| res = platform_get_resource(gpio_pdev, IORESOURCE_MEM, 0); |
| base = devm_ioremap_resource(dev, res); |
| if (IS_ERR(base)) { |
| platform_device_put(gpio_pdev); |
| return ERR_CAST(base); |
| } |
| nmk_chip->addr = base; |
| |
| /* NOTE: do not use devm_ here! */ |
| clk = clk_get_optional(gpio_dev, NULL); |
| if (IS_ERR(clk)) { |
| platform_device_put(gpio_pdev); |
| return ERR_CAST(clk); |
| } |
| clk_prepare(clk); |
| nmk_chip->clk = clk; |
| |
| /* NOTE: do not use devm_ here! */ |
| reset = reset_control_get_optional_shared(gpio_dev, NULL); |
| if (IS_ERR(reset)) { |
| clk_unprepare(clk); |
| clk_put(clk); |
| platform_device_put(gpio_pdev); |
| dev_err(dev, "failed getting reset control: %pe\n", |
| reset); |
| return ERR_CAST(reset); |
| } |
| |
| /* |
| * Reset might be shared and asserts/deasserts calls are unbalanced. We |
| * only support sharing this reset with other gpio-nomadik devices that |
| * use this reset to ensure deassertion at probe. |
| */ |
| ret = reset_control_deassert(reset); |
| if (ret) { |
| reset_control_put(reset); |
| clk_unprepare(clk); |
| clk_put(clk); |
| platform_device_put(gpio_pdev); |
| dev_err(dev, "failed reset deassert: %d\n", ret); |
| return ERR_PTR(ret); |
| } |
| |
| #ifdef CONFIG_PINCTRL_NOMADIK |
| nmk_gpio_chips[id] = nmk_chip; |
| #endif |
| return nmk_chip; |
| } |
| |
| static void nmk_gpio_irq_print_chip(struct irq_data *d, struct seq_file *p) |
| { |
| struct gpio_chip *gc = irq_data_get_irq_chip_data(d); |
| struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(gc); |
| |
| seq_printf(p, "nmk%u-%u-%u", nmk_chip->bank, |
| gc->base, gc->base + gc->ngpio - 1); |
| } |
| |
| static const struct irq_chip nmk_irq_chip = { |
| .irq_ack = nmk_gpio_irq_ack, |
| .irq_mask = nmk_gpio_irq_mask, |
| .irq_unmask = nmk_gpio_irq_unmask, |
| .irq_set_type = nmk_gpio_irq_set_type, |
| .irq_set_wake = nmk_gpio_irq_set_wake, |
| .irq_startup = nmk_gpio_irq_startup, |
| .irq_shutdown = nmk_gpio_irq_shutdown, |
| .irq_print_chip = nmk_gpio_irq_print_chip, |
| .flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE, |
| GPIOCHIP_IRQ_RESOURCE_HELPERS, |
| }; |
| |
| static int nmk_gpio_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct nmk_gpio_chip *nmk_chip; |
| struct gpio_irq_chip *girq; |
| bool supports_sleepmode; |
| struct gpio_chip *chip; |
| int irq; |
| int ret; |
| |
| nmk_chip = nmk_gpio_populate_chip(dev_fwnode(dev), pdev); |
| if (IS_ERR(nmk_chip)) { |
| dev_err(dev, "could not populate nmk chip struct\n"); |
| return PTR_ERR(nmk_chip); |
| } |
| |
| supports_sleepmode = |
| device_property_read_bool(dev, "st,supports-sleepmode"); |
| |
| /* Correct platform device ID */ |
| pdev->id = nmk_chip->bank; |
| |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) |
| return irq; |
| |
| /* |
| * The virt address in nmk_chip->addr is in the nomadik register space, |
| * so we can simply convert the resource address, without remapping |
| */ |
| nmk_chip->sleepmode = supports_sleepmode; |
| spin_lock_init(&nmk_chip->lock); |
| |
| chip = &nmk_chip->chip; |
| chip->parent = dev; |
| chip->request = gpiochip_generic_request; |
| chip->free = gpiochip_generic_free; |
| chip->get_direction = nmk_gpio_get_dir; |
| chip->direction_input = nmk_gpio_make_input; |
| chip->get = nmk_gpio_get_input; |
| chip->direction_output = nmk_gpio_make_output; |
| chip->set = nmk_gpio_set_output; |
| chip->dbg_show = nmk_gpio_dbg_show; |
| chip->can_sleep = false; |
| chip->owner = THIS_MODULE; |
| |
| girq = &chip->irq; |
| gpio_irq_chip_set_chip(girq, &nmk_irq_chip); |
| girq->parent_handler = NULL; |
| girq->num_parents = 0; |
| girq->parents = NULL; |
| girq->default_type = IRQ_TYPE_NONE; |
| girq->handler = handle_edge_irq; |
| |
| ret = devm_request_irq(dev, irq, nmk_gpio_irq_handler, IRQF_SHARED, |
| dev_name(dev), nmk_chip); |
| if (ret) { |
| dev_err(dev, "failed requesting IRQ\n"); |
| return ret; |
| } |
| |
| if (!nmk_chip->is_mobileye_soc) { |
| clk_enable(nmk_chip->clk); |
| nmk_chip->lowemi = readl_relaxed(nmk_chip->addr + NMK_GPIO_LOWEMI); |
| clk_disable(nmk_chip->clk); |
| } |
| |
| ret = gpiochip_add_data(chip, nmk_chip); |
| if (ret) |
| return ret; |
| |
| platform_set_drvdata(pdev, nmk_chip); |
| |
| dev_info(dev, "chip registered\n"); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id nmk_gpio_match[] = { |
| { .compatible = "st,nomadik-gpio", }, |
| { .compatible = "mobileye,eyeq5-gpio", }, |
| {} |
| }; |
| |
| static struct platform_driver nmk_gpio_driver = { |
| .driver = { |
| .name = "nomadik-gpio", |
| .of_match_table = nmk_gpio_match, |
| .suppress_bind_attrs = true, |
| }, |
| .probe = nmk_gpio_probe, |
| }; |
| |
| static int __init nmk_gpio_init(void) |
| { |
| return platform_driver_register(&nmk_gpio_driver); |
| } |
| subsys_initcall(nmk_gpio_init); |