| /* |
| * drivers/pinctrl/ambarella/pinctrl-amb.c |
| * |
| * History: |
| * 2013/12/18 - [Cao Rongrong] created file |
| * |
| * Copyright (C) 2012-2016, Ambarella, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/gpio.h> |
| #include <linux/io.h> |
| #include <linux/slab.h> |
| #include <linux/syscore_ops.h> |
| #include <linux/irqdomain.h> |
| #include <linux/of_address.h> |
| #include <linux/of_irq.h> |
| #include <linux/irqchip/chained_irq.h> |
| #include <plat/iav_helper.h> |
| #include <plat/pinctrl.h> |
| |
| #if defined(CONFIG_PM) |
| struct amb_gpio_regs { |
| u32 irq_wake_mask; |
| u32 data; |
| u32 dir; |
| u32 is; |
| u32 ibe; |
| u32 iev; |
| u32 ie; |
| u32 afsel; |
| u32 mask; |
| }; |
| |
| struct amb_gpio_regs amb_gpio_pm[GPIO_INSTANCES]; |
| #endif |
| |
| struct amb_gpio_chip { |
| int irq[GPIO_INSTANCES]; |
| void __iomem *regbase[GPIO_INSTANCES]; |
| void __iomem *iomux_base; |
| struct gpio_chip *gc; |
| struct irq_domain *domain; |
| struct ambarella_service gpio_service; |
| }; |
| |
| static int ambarella_gpio_service(void *arg, void *result); |
| |
| /* gpiolib gpio_request callback function */ |
| static int amb_gpio_request(struct gpio_chip *gc, unsigned pin) |
| { |
| return pinctrl_request_gpio(gc->base + pin); |
| } |
| |
| /* gpiolib gpio_set callback function */ |
| static void amb_gpio_free(struct gpio_chip *gc, unsigned pin) |
| { |
| pinctrl_free_gpio(gc->base + pin); |
| } |
| |
| /* gpiolib gpio_free callback function */ |
| static void amb_gpio_set(struct gpio_chip *gc, unsigned pin, int value) |
| { |
| struct amb_gpio_chip *amb_gpio = dev_get_drvdata(gc->dev); |
| void __iomem *regbase; |
| u32 bank, offset, mask; |
| |
| bank = PINID_TO_BANK(pin); |
| offset = PINID_TO_OFFSET(pin); |
| regbase = amb_gpio->regbase[bank]; |
| mask = (0x1 << offset); |
| |
| amba_writel(regbase + GPIO_MASK_OFFSET, mask); |
| if (value == GPIO_LOW) |
| mask = 0; |
| amba_writel(regbase + GPIO_DATA_OFFSET, mask); |
| } |
| |
| /* gpiolib gpio_get callback function */ |
| static int amb_gpio_get(struct gpio_chip *gc, unsigned pin) |
| { |
| struct amb_gpio_chip *amb_gpio = dev_get_drvdata(gc->dev); |
| void __iomem *regbase; |
| u32 bank, offset, mask, data; |
| |
| bank = PINID_TO_BANK(pin); |
| offset = PINID_TO_OFFSET(pin); |
| regbase = amb_gpio->regbase[bank]; |
| mask = (0x1 << offset); |
| |
| amba_writel(regbase + GPIO_MASK_OFFSET, mask); |
| data = amba_readl(regbase + GPIO_DATA_OFFSET); |
| data = (data >> offset) & 0x1; |
| |
| return (data ? GPIO_HIGH : GPIO_LOW); |
| } |
| |
| /* gpiolib gpio_get_direction callback function */ |
| static int amb_gpio_get_direction(struct gpio_chip *gc, unsigned pin) |
| { |
| struct amb_gpio_chip *amb_gpio = dev_get_drvdata(gc->dev); |
| void __iomem *regbase; |
| u32 bank, offset, mask, data; |
| |
| bank = PINID_TO_BANK(pin); |
| offset = PINID_TO_OFFSET(pin); |
| regbase = amb_gpio->regbase[bank]; |
| mask = (0x1 << offset); |
| |
| amba_writel(regbase + GPIO_MASK_OFFSET, mask); |
| data = amba_readl(regbase + GPIO_DIR_OFFSET); |
| data = (data >> offset) & 0x1; |
| |
| return (data ? GPIOF_DIR_OUT : GPIOF_DIR_IN); |
| } |
| |
| /* gpiolib gpio_direction_input callback function */ |
| static int amb_gpio_direction_input(struct gpio_chip *gc, unsigned pin) |
| { |
| return pinctrl_gpio_direction_input(gc->base + pin); |
| } |
| |
| /* gpiolib gpio_direction_output callback function */ |
| static int amb_gpio_direction_output(struct gpio_chip *gc, |
| unsigned pin, int value) |
| { |
| int rval; |
| |
| rval = pinctrl_gpio_direction_output(gc->base + pin); |
| if (rval < 0) |
| return rval; |
| |
| amb_gpio_set(gc, pin, value); |
| |
| return 0; |
| } |
| |
| /* gpiolib gpio_to_irq callback function */ |
| static int amb_gpio_to_irq(struct gpio_chip *gc, unsigned pin) |
| { |
| struct amb_gpio_chip *amb_gpio = dev_get_drvdata(gc->dev); |
| |
| return irq_create_mapping(amb_gpio->domain, pin); |
| } |
| |
| static void amb_gpio_dbg_show(struct seq_file *s, struct gpio_chip *gc) |
| { |
| struct amb_gpio_chip *amb_gpio = dev_get_drvdata(gc->dev); |
| void __iomem *iomux_base = amb_gpio->iomux_base; |
| void __iomem *regbase; |
| u32 afsel = 0, data = 0, dir = 0, mask = 0; |
| u32 iomux0 = 0, iomux1 = 0, iomux2 = 0, alt = 0; |
| u32 i, bank, offset; |
| |
| for (i = 0; i < gc->ngpio; i++) { |
| offset = PINID_TO_OFFSET(i); |
| if (offset == 0) { |
| bank = PINID_TO_BANK(i); |
| regbase = amb_gpio->regbase[bank]; |
| |
| afsel = amba_readl(regbase + GPIO_AFSEL_OFFSET); |
| dir = amba_readl(regbase + GPIO_DIR_OFFSET); |
| mask = amba_readl(regbase + GPIO_MASK_OFFSET); |
| amba_writel(regbase + GPIO_MASK_OFFSET, ~afsel); |
| data = amba_readl(regbase + GPIO_DATA_OFFSET); |
| amba_writel(regbase + GPIO_MASK_OFFSET, mask); |
| |
| seq_printf(s, "\nGPIO[%d]:\t[%d - %d]\n", |
| bank, i, i + GPIO_BANK_SIZE - 1); |
| seq_printf(s, "GPIO_BASE:\t0x%08X\n", (u32)regbase); |
| seq_printf(s, "GPIO_AFSEL:\t0x%08X\n", afsel); |
| seq_printf(s, "GPIO_DIR:\t0x%08X\n", dir); |
| seq_printf(s, "GPIO_MASK:\t0x%08X:0x%08X\n", mask, ~afsel); |
| seq_printf(s, "GPIO_DATA:\t0x%08X\n", data); |
| |
| if (iomux_base != NULL) { |
| iomux0 = amba_readl(iomux_base + bank * 12); |
| iomux1 = amba_readl(iomux_base + bank * 12 + 4); |
| iomux2 = amba_readl(iomux_base + bank * 12 + 8); |
| seq_printf(s, "IOMUX_REG%d_0:\t0x%08X\n", bank, iomux0); |
| seq_printf(s, "IOMUX_REG%d_1:\t0x%08X\n", bank, iomux1); |
| seq_printf(s, "IOMUX_REG%d_2:\t0x%08X\n", bank, iomux2); |
| } |
| } |
| |
| seq_printf(s, " gpio-%-3d", gc->base + i); |
| if (iomux_base != NULL) { |
| alt = ((iomux2 >> offset) & 1) << 2; |
| alt |= ((iomux1 >> offset) & 1) << 1; |
| alt |= ((iomux0 >> offset) & 1) << 0; |
| if (alt != 0) |
| seq_printf(s, " [HW ] (alt%d)\n", alt); |
| else { |
| const char *label = gpiochip_is_requested(gc, i); |
| label = label ? : ""; |
| seq_printf(s, " [GPIO] (%-20.20s) %s %s\n", label, |
| (dir & (1 << offset)) ? "out" : "in ", |
| (data & (1 << offset)) ? "hi" : "lo"); |
| } |
| } else { |
| if (afsel & (1 << offset)) { |
| seq_printf(s, " [HW ]\n"); |
| } else { |
| const char *label = gpiochip_is_requested(gc, i); |
| label = label ? : ""; |
| seq_printf(s, " [GPIO] (%-20.20s) %s %s\n", label, |
| (dir & (1 << offset)) ? "out" : "in ", |
| (data & (1 << offset)) ? "hi" : "lo"); |
| } |
| } |
| } |
| } |
| |
| static struct gpio_chip amb_gc = { |
| .label = "ambarella-gpio", |
| .base = 0, |
| .ngpio = AMBGPIO_SIZE, |
| .request = amb_gpio_request, |
| .free = amb_gpio_free, |
| .direction_input = amb_gpio_direction_input, |
| .direction_output = amb_gpio_direction_output, |
| .get = amb_gpio_get, |
| .set = amb_gpio_set, |
| .get_direction = amb_gpio_get_direction, |
| .to_irq = amb_gpio_to_irq, |
| .dbg_show = amb_gpio_dbg_show, |
| .owner = THIS_MODULE, |
| }; |
| |
| static void amb_gpio_irq_enable(struct irq_data *data) |
| { |
| struct amb_gpio_chip *amb_gpio = dev_get_drvdata(amb_gc.dev); |
| void __iomem *regbase = irq_data_get_irq_chip_data(data); |
| void __iomem *iomux_base = amb_gpio->iomux_base; |
| u32 i, bank, offset, val; |
| |
| bank = PINID_TO_BANK(data->hwirq); |
| offset = PINID_TO_OFFSET(data->hwirq); |
| |
| /* make sure the pin is in gpio input mode */ |
| if (!gpiochip_is_requested(&amb_gc, data->hwirq)) { |
| amba_clrbitsl(regbase + GPIO_AFSEL_OFFSET, 0x1 << offset); |
| amba_clrbitsl(regbase + GPIO_DIR_OFFSET, 0x1 << offset); |
| |
| if (iomux_base) { |
| for (i = 0; i < 3; i++) { |
| val = amba_readl(iomux_base + IOMUX_REG_OFFSET(bank, i)); |
| val &= (~(0x1 << offset)); |
| amba_writel(iomux_base + IOMUX_REG_OFFSET(bank, i), val); |
| } |
| amba_writel(iomux_base + IOMUX_CTRL_SET_OFFSET, 0x1); |
| amba_writel(iomux_base + IOMUX_CTRL_SET_OFFSET, 0x0); |
| } |
| } |
| |
| amba_writel(regbase + GPIO_IC_OFFSET, 0x1 << offset); |
| amba_setbitsl(regbase + GPIO_IE_OFFSET, 0x1 << offset); |
| } |
| |
| static void amb_gpio_irq_disable(struct irq_data *data) |
| { |
| void __iomem *regbase = irq_data_get_irq_chip_data(data); |
| u32 offset = PINID_TO_OFFSET(data->hwirq); |
| |
| amba_clrbitsl(regbase + GPIO_IE_OFFSET, 0x1 << offset); |
| amba_writel(regbase + GPIO_IC_OFFSET, 0x1 << offset); |
| } |
| |
| static void amb_gpio_irq_ack(struct irq_data *data) |
| { |
| void __iomem *regbase = irq_data_get_irq_chip_data(data); |
| u32 offset = PINID_TO_OFFSET(data->hwirq); |
| |
| amba_writel(regbase + GPIO_IC_OFFSET, 0x1 << offset); |
| } |
| |
| static void amb_gpio_irq_mask(struct irq_data *data) |
| { |
| void __iomem *regbase = irq_data_get_irq_chip_data(data); |
| u32 offset = PINID_TO_OFFSET(data->hwirq); |
| |
| amba_clrbitsl(regbase + GPIO_IE_OFFSET, 0x1 << offset); |
| } |
| |
| static void amb_gpio_irq_mask_ack(struct irq_data *data) |
| { |
| void __iomem *regbase = irq_data_get_irq_chip_data(data); |
| u32 offset = PINID_TO_OFFSET(data->hwirq); |
| |
| amba_clrbitsl(regbase + GPIO_IE_OFFSET, 0x1 << offset); |
| amba_writel(regbase + GPIO_IC_OFFSET, 0x1 << offset); |
| } |
| |
| static void amb_gpio_irq_unmask(struct irq_data *data) |
| { |
| void __iomem *regbase = irq_data_get_irq_chip_data(data); |
| u32 offset = PINID_TO_OFFSET(data->hwirq); |
| |
| amba_setbitsl(regbase + GPIO_IE_OFFSET, 0x1 << offset); |
| } |
| |
| static int amb_gpio_irq_set_type(struct irq_data *data, unsigned int type) |
| { |
| void __iomem *regbase = irq_data_get_irq_chip_data(data); |
| struct irq_desc *desc = irq_to_desc(data->irq); |
| u32 offset = PINID_TO_OFFSET(data->hwirq); |
| u32 mask, bit, sense, bothedges, event; |
| |
| mask = ~(0x1 << offset); |
| bit = (0x1 << offset); |
| sense = amba_readl(regbase + GPIO_IS_OFFSET); |
| bothedges = amba_readl(regbase + GPIO_IBE_OFFSET); |
| event = amba_readl(regbase + GPIO_IEV_OFFSET); |
| |
| switch (type) { |
| case IRQ_TYPE_EDGE_RISING: |
| sense &= mask; |
| bothedges &= mask; |
| event |= bit; |
| desc->handle_irq = handle_edge_irq; |
| break; |
| case IRQ_TYPE_EDGE_FALLING: |
| sense &= mask; |
| bothedges &= mask; |
| event &= mask; |
| desc->handle_irq = handle_edge_irq; |
| break; |
| case IRQ_TYPE_EDGE_BOTH: |
| sense &= mask; |
| bothedges |= bit; |
| event &= mask; |
| desc->handle_irq = handle_edge_irq; |
| break; |
| case IRQ_TYPE_LEVEL_HIGH: |
| sense |= bit; |
| bothedges &= mask; |
| event |= bit; |
| desc->handle_irq = handle_level_irq; |
| break; |
| case IRQ_TYPE_LEVEL_LOW: |
| sense |= bit; |
| bothedges &= mask; |
| event &= mask; |
| desc->handle_irq = handle_level_irq; |
| break; |
| default: |
| pr_err("%s: irq[%d] type[%d] fail!\n", |
| __func__, data->irq, type); |
| return -EINVAL; |
| } |
| |
| amba_writel(regbase + GPIO_IS_OFFSET, sense); |
| amba_writel(regbase + GPIO_IBE_OFFSET, bothedges); |
| amba_writel(regbase + GPIO_IEV_OFFSET, event); |
| /* clear obsolete irq */ |
| amba_writel(regbase + GPIO_IC_OFFSET, 0x1 << offset); |
| |
| return 0; |
| } |
| |
| static int amb_gpio_irq_set_wake(struct irq_data *data, unsigned int on) |
| { |
| #if defined(CONFIG_PM) |
| u32 bank = PINID_TO_BANK(data->hwirq); |
| u32 offset = PINID_TO_OFFSET(data->hwirq); |
| |
| if (on) { |
| amb_gpio_pm[bank].irq_wake_mask |= (1 << offset); |
| } else { |
| amb_gpio_pm[bank].irq_wake_mask &= ~(1 << offset); |
| } |
| #endif |
| return 0; |
| } |
| |
| static struct irq_chip amb_gpio_irqchip = { |
| .name = "GPIO", |
| .irq_enable = amb_gpio_irq_enable, |
| .irq_disable = amb_gpio_irq_disable, |
| .irq_ack = amb_gpio_irq_ack, |
| .irq_mask = amb_gpio_irq_mask, |
| .irq_mask_ack = amb_gpio_irq_mask_ack, |
| .irq_unmask = amb_gpio_irq_unmask, |
| .irq_set_type = amb_gpio_irq_set_type, |
| .irq_set_wake = amb_gpio_irq_set_wake, |
| .flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND, |
| }; |
| |
| static int amb_gpio_irqdomain_map(struct irq_domain *d, |
| unsigned int irq, irq_hw_number_t hwirq) |
| { |
| struct amb_gpio_chip *amb_gpio; |
| |
| amb_gpio = (struct amb_gpio_chip *)d->host_data; |
| |
| irq_set_chip_and_handler(irq, &amb_gpio_irqchip, handle_level_irq); |
| irq_set_chip_data(irq, amb_gpio->regbase[PINID_TO_BANK(hwirq)]); |
| set_irq_flags(irq, IRQF_VALID | IRQF_PROBE); |
| |
| return 0; |
| } |
| |
| const struct irq_domain_ops amb_gpio_irq_domain_ops = { |
| .map = amb_gpio_irqdomain_map, |
| .xlate = irq_domain_xlate_twocell, |
| }; |
| |
| static void amb_gpio_handle_irq(unsigned int irq, struct irq_desc *desc) |
| { |
| struct irq_chip *irqchip; |
| struct amb_gpio_chip *amb_gpio; |
| u32 i, gpio_mis, gpio_hwirq, gpio_irq; |
| |
| irqchip = irq_desc_get_chip(desc); |
| chained_irq_enter(irqchip, desc); |
| |
| amb_gpio = irq_get_handler_data(irq); |
| |
| /* find the GPIO bank generating this irq */ |
| for (i = 0; i < GPIO_INSTANCES; i++) { |
| if (amb_gpio->irq[i] == irq) |
| break; |
| } |
| |
| if (i == GPIO_INSTANCES) |
| return; |
| |
| gpio_mis = amba_readl(amb_gpio->regbase[i] + GPIO_MIS_OFFSET); |
| if (gpio_mis) { |
| gpio_hwirq = i * GPIO_BANK_SIZE + ffs(gpio_mis) - 1; |
| gpio_irq = irq_find_mapping(amb_gpio->domain, gpio_hwirq); |
| generic_handle_irq(gpio_irq); |
| } |
| |
| chained_irq_exit(irqchip, desc); |
| } |
| |
| static int amb_gpio_probe(struct platform_device *pdev) |
| { |
| struct device_node *np = pdev->dev.of_node; |
| struct device_node *parent; |
| struct amb_gpio_chip *amb_gpio; |
| int i, rval; |
| |
| amb_gpio = devm_kzalloc(&pdev->dev, sizeof(*amb_gpio), GFP_KERNEL); |
| if (!amb_gpio) { |
| dev_err(&pdev->dev, "failed to allocate memory for private data\n"); |
| return -ENOMEM; |
| } |
| |
| parent = of_get_parent(np); |
| |
| for (i = 0; i < GPIO_INSTANCES; i++) { |
| amb_gpio->regbase[i] = of_iomap(parent, i); |
| if (amb_gpio->regbase[i] == NULL) { |
| dev_err(&pdev->dev, "devm_ioremap() failed\n"); |
| return -ENOMEM; |
| } |
| |
| amba_writel(amb_gpio->regbase[i] + GPIO_ENABLE_OFFSET, 0xffffffff); |
| |
| amb_gpio->irq[i] = irq_of_parse_and_map(np, i); |
| if (amb_gpio->irq[i] == 0) { |
| dev_err(&pdev->dev, "no irq for gpio[%d]!\n", i); |
| return -ENXIO; |
| } |
| } |
| |
| /* iomux_base will get NULL if not existed */ |
| amb_gpio->iomux_base = of_iomap(parent, i); |
| |
| of_node_put(parent); |
| |
| amb_gpio->gc = &amb_gc; |
| amb_gpio->gc->dev = &pdev->dev; |
| rval = gpiochip_add(amb_gpio->gc); |
| if (rval) { |
| dev_err(&pdev->dev, |
| "failed to register gpio_chip %s\n", amb_gpio->gc->label); |
| return rval; |
| } |
| |
| /* Initialize GPIO irq */ |
| amb_gpio->domain = irq_domain_add_linear(np, amb_gpio->gc->ngpio, |
| &amb_gpio_irq_domain_ops, amb_gpio); |
| if (!amb_gpio->domain) { |
| pr_err("%s: Failed to create irqdomain\n", np->full_name); |
| return -ENOSYS; |
| } |
| |
| for (i = 0; i < GPIO_INSTANCES; i++) { |
| irq_set_irq_type(amb_gpio->irq[i], IRQ_TYPE_LEVEL_HIGH); |
| irq_set_handler_data(amb_gpio->irq[i], amb_gpio); |
| irq_set_chained_handler(amb_gpio->irq[i], amb_gpio_handle_irq); |
| } |
| |
| platform_set_drvdata(pdev, amb_gpio); |
| |
| dev_info(&pdev->dev, "Ambarella GPIO driver registered\n"); |
| |
| /* register ambarella gpio service for private operation */ |
| amb_gpio->gpio_service.service = AMBARELLA_SERVICE_GPIO; |
| amb_gpio->gpio_service.func = ambarella_gpio_service; |
| ambarella_register_service(&amb_gpio->gpio_service); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PM |
| static int amb_gpio_irq_suspend(void) |
| { |
| struct amb_gpio_chip *amb_gpio; |
| struct amb_gpio_regs *pm; |
| void __iomem *regbase; |
| int i; |
| |
| amb_gpio = dev_get_drvdata(amb_gc.dev); |
| if (amb_gpio == NULL) { |
| pr_err("No device for ambarella gpio irq\n"); |
| return -ENODEV; |
| } |
| |
| for (i = 0; i < GPIO_INSTANCES; i++) { |
| regbase = amb_gpio->regbase[i]; |
| pm = &amb_gpio_pm[i]; |
| pm->afsel = amba_readl(regbase + GPIO_AFSEL_OFFSET); |
| pm->dir = amba_readl(regbase + GPIO_DIR_OFFSET); |
| pm->is = amba_readl(regbase + GPIO_IS_OFFSET); |
| pm->ibe = amba_readl(regbase + GPIO_IBE_OFFSET); |
| pm->iev = amba_readl(regbase + GPIO_IEV_OFFSET); |
| pm->ie = amba_readl(regbase + GPIO_IE_OFFSET); |
| pm->mask = ~pm->afsel; |
| amba_writel(regbase + GPIO_MASK_OFFSET, pm->mask); |
| pm->data = amba_readl(regbase + GPIO_DATA_OFFSET); |
| |
| if (pm->irq_wake_mask) { |
| amba_writel(regbase + GPIO_IE_OFFSET, pm->irq_wake_mask); |
| pr_info("gpio_irq[%p]: irq_wake[0x%08X]\n", |
| regbase, pm->irq_wake_mask); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void amb_gpio_irq_resume(void) |
| { |
| struct amb_gpio_chip *amb_gpio; |
| struct amb_gpio_regs *pm; |
| void __iomem *regbase; |
| int i; |
| |
| amb_gpio = dev_get_drvdata(amb_gc.dev); |
| if (amb_gpio == NULL) { |
| pr_err("No device for ambarella gpio irq\n"); |
| return; |
| } |
| |
| for (i = 0; i < GPIO_INSTANCES; i++) { |
| regbase = amb_gpio->regbase[i]; |
| pm = &amb_gpio_pm[i]; |
| amba_writel(regbase + GPIO_AFSEL_OFFSET, pm->afsel); |
| amba_writel(regbase + GPIO_DIR_OFFSET, pm->dir); |
| amba_writel(regbase + GPIO_MASK_OFFSET, pm->mask); |
| amba_writel(regbase + GPIO_DATA_OFFSET, pm->data); |
| amba_writel(regbase + GPIO_IS_OFFSET, pm->is); |
| amba_writel(regbase + GPIO_IBE_OFFSET, pm->ibe); |
| amba_writel(regbase + GPIO_IEV_OFFSET, pm->iev); |
| amba_writel(regbase + GPIO_IE_OFFSET, pm->ie); |
| amba_writel(regbase + GPIO_ENABLE_OFFSET, 0xffffffff); |
| } |
| } |
| |
| struct syscore_ops amb_gpio_irq_syscore_ops = { |
| .suspend = amb_gpio_irq_suspend, |
| .resume = amb_gpio_irq_resume, |
| }; |
| |
| #endif |
| |
| static const struct of_device_id amb_gpio_dt_match[] = { |
| { .compatible = "ambarella,gpio" }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, amb_gpio_dt_match); |
| |
| static struct platform_driver amb_gpio_driver = { |
| .probe = amb_gpio_probe, |
| .driver = { |
| .name = "ambarella-gpio", |
| .owner = THIS_MODULE, |
| .of_match_table = of_match_ptr(amb_gpio_dt_match), |
| }, |
| }; |
| |
| static int __init amb_gpio_drv_register(void) |
| { |
| #ifdef CONFIG_PM |
| register_syscore_ops(&amb_gpio_irq_syscore_ops); |
| #endif |
| |
| return platform_driver_register(&amb_gpio_driver); |
| } |
| postcore_initcall(amb_gpio_drv_register); |
| |
| MODULE_AUTHOR("Cao Rongrong <rrcao@ambarella.com>"); |
| MODULE_DESCRIPTION("Ambarella SoC GPIO driver"); |
| MODULE_LICENSE("GPL v2"); |
| |
| static int ambarella_gpio_service(void *arg, void *result) |
| { |
| struct ambsvc_gpio *gpio_svc = arg; |
| u32 *value = result; |
| int rval = 0; |
| |
| BUG_ON(!gpio_svc); |
| |
| switch (gpio_svc->svc_id) { |
| case AMBSVC_GPIO_REQUEST: |
| { |
| rval = gpio_request(gpio_svc->gpio, "gpio"); |
| break; |
| } |
| case AMBSVC_GPIO_OUTPUT: |
| rval = gpio_direction_output(gpio_svc->gpio, gpio_svc->value); |
| break; |
| |
| case AMBSVC_GPIO_INPUT: |
| rval = gpio_direction_input(gpio_svc->gpio); |
| if (rval >= 0 && value) |
| *value = gpio_get_value_cansleep(gpio_svc->gpio); |
| break; |
| |
| case AMBSVC_GPIO_FREE: |
| gpio_free(gpio_svc->gpio); |
| break; |
| |
| case AMBSVC_GPIO_TO_IRQ: |
| *value = gpio_to_irq(gpio_svc->gpio); |
| break; |
| |
| default: |
| pr_err("%s: Invalid gpio service (%d)\n", __func__, gpio_svc->svc_id); |
| rval = -EINVAL; |
| break; |
| } |
| |
| return rval; |
| } |
| |