diff options
author | Théo Lebrun <theo.lebrun@bootlin.com> | 2024-02-28 12:28:04 +0100 |
---|---|---|
committer | Linus Walleij <linus.walleij@linaro.org> | 2024-02-29 10:22:33 +0100 |
commit | 966942ae493650210b9514f3d4bfc95f78ef0129 (patch) | |
tree | 49165f05e6c0deb06377766942d72824b4aafdc6 /drivers/gpio/gpio-nomadik.c | |
parent | 53cf6b72e074864b94ade97dcb6f30b5ac1a82dc (diff) |
gpio: nomadik: extract GPIO platform driver from drivers/pinctrl/nomadik/
Previously, drivers/pinctrl/nomadik/pinctrl-nomadik.c registered two
platform drivers: pinctrl & GPIO. Move the GPIO aspect to the
drivers/gpio/ folder, as would be expected.
Both drivers are intertwined for a reason; pinctrl requires access to
GPIO registers for pinmuxing, pull-disable, disabling interrupts while
setting the muxing and wakeup control. Information sharing is done
through a shared array containing GPIO chips and a few helper
functions. That shared array is not touched from gpio-nomadik when
CONFIG_PINCTRL_NOMADIK is not defined.
Make no change to the code that moved into gpio-nomadik; there should be
no behavior change following. A few functions are shared and header
comments are added. Checkpatch warnings are addressed. NUM_BANKS is
renamed to NMK_MAX_BANKS.
It is supported to compile gpio-nomadik without pinctrl-nomadik. The
opposite is not true.
Signed-off-by: Théo Lebrun <theo.lebrun@bootlin.com>
Link: https://lore.kernel.org/r/20240228-mbly-gpio-v2-6-3ba757474006@bootlin.com
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Diffstat (limited to 'drivers/gpio/gpio-nomadik.c')
-rw-r--r-- | drivers/gpio/gpio-nomadik.c | 660 |
1 files changed, 660 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-nomadik.c b/drivers/gpio/gpio-nomadik.c new file mode 100644 index 000000000000..e39477e1a58f --- /dev/null +++ b/drivers/gpio/gpio-nomadik.c @@ -0,0 +1,660 @@ +// 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. + * + * 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/of_device.h> +#include <linux/of_platform.h> +#include <linux/pinctrl/pinctrl.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <linux/seq_file.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; + + 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 { + 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) +{ + /* + * 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->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; + + 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 (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 (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 void nmk_gpio_irq_handler(struct irq_desc *desc) +{ + struct irq_chip *host_chip = irq_desc_get_chip(desc); + struct gpio_chip *chip = irq_desc_get_handler_data(desc); + struct nmk_gpio_chip *nmk_chip = gpiochip_get_data(chip); + u32 status; + + chained_irq_enter(host_chip, desc); + + clk_enable(nmk_chip->clk); + status = readl(nmk_chip->addr + NMK_GPIO_IS); + clk_disable(nmk_chip->clk); + + while (status) { + int bit = __ffs(status); + + generic_handle_domain_irq(chip->irq.domain, bit); + status &= ~BIT(bit); + } + + chained_irq_exit(host_chip, desc); +} + +/* 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; + + 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 + +static inline void nmk_gpio_dbg_show_one(struct seq_file *s, + struct pinctrl_dev *pctldev, + struct gpio_chip *chip, + unsigned int offset, + unsigned int gpio) +{ +} + +#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 device_node *np, + struct platform_device *pdev) +{ + struct nmk_gpio_chip *nmk_chip; + struct platform_device *gpio_pdev; + struct gpio_chip *chip; + struct resource *res; + struct clk *clk; + void __iomem *base; + u32 id; + + gpio_pdev = of_find_device_by_node(np); + if (!gpio_pdev) { + pr_err("populate \"%pOFn\": device not found\n", np); + return ERR_PTR(-ENODEV); + } + if (of_property_read_u32(np, "gpio-bank", &id)) { + dev_err(&pdev->dev, "populate: gpio-bank property not found\n"); + platform_device_put(gpio_pdev); + return ERR_PTR(-EINVAL); + } + +#ifdef CONFIG_PINCTRL_NOMADIK + /* Already populated? */ + nmk_chip = nmk_gpio_chips[id]; + if (nmk_chip) { + platform_device_put(gpio_pdev); + return nmk_chip; + } +#endif + + nmk_chip = devm_kzalloc(&pdev->dev, sizeof(*nmk_chip), GFP_KERNEL); + if (!nmk_chip) { + platform_device_put(gpio_pdev); + return ERR_PTR(-ENOMEM); + } + + nmk_chip->bank = id; + chip = &nmk_chip->chip; + chip->base = id * NMK_GPIO_PER_CHIP; + chip->ngpio = NMK_GPIO_PER_CHIP; + chip->label = dev_name(&gpio_pdev->dev); + chip->parent = &gpio_pdev->dev; + + res = platform_get_resource(gpio_pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) { + platform_device_put(gpio_pdev); + return ERR_CAST(base); + } + nmk_chip->addr = base; + + clk = clk_get(&gpio_pdev->dev, NULL); + if (IS_ERR(clk)) { + platform_device_put(gpio_pdev); + return (void *)clk; + } + clk_prepare(clk); + nmk_chip->clk = clk; + +#ifdef CONFIG_PINCTRL_NOMADIK + BUG_ON(nmk_chip->bank >= ARRAY_SIZE(nmk_gpio_chips)); + 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 *dev) +{ + struct device_node *np = dev->dev.of_node; + struct nmk_gpio_chip *nmk_chip; + struct gpio_chip *chip; + struct gpio_irq_chip *girq; + bool supports_sleepmode; + int irq; + int ret; + + nmk_chip = nmk_gpio_populate_chip(np, dev); + if (IS_ERR(nmk_chip)) { + dev_err(&dev->dev, "could not populate nmk chip struct\n"); + return PTR_ERR(nmk_chip); + } + + supports_sleepmode = + of_property_read_bool(np, "st,supports-sleepmode"); + + /* Correct platform device ID */ + dev->id = nmk_chip->bank; + + irq = platform_get_irq(dev, 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->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 = nmk_gpio_irq_handler; + girq->num_parents = 1; + girq->parents = devm_kcalloc(&dev->dev, 1, + sizeof(*girq->parents), + GFP_KERNEL); + if (!girq->parents) + return -ENOMEM; + girq->parents[0] = irq; + girq->default_type = IRQ_TYPE_NONE; + girq->handler = handle_edge_irq; + + 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(dev, nmk_chip); + + dev_info(&dev->dev, "chip registered\n"); + + return 0; +} + +static const struct of_device_id nmk_gpio_match[] = { + { .compatible = "st,nomadik-gpio", }, + {} +}; + +static struct platform_driver nmk_gpio_driver = { + .driver = { + .name = "gpio", + .of_match_table = nmk_gpio_match, + }, + .probe = nmk_gpio_probe, +}; + +static int __init nmk_gpio_init(void) +{ + return platform_driver_register(&nmk_gpio_driver); +} +subsys_initcall(nmk_gpio_init); |