| /* |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 as published |
| * by the Free Software Foundation. |
| * |
| * Copyright (C) 2010 John Crispin <blogic@openwrt.org> |
| */ |
| |
| #include <linux/slab.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/gpio.h> |
| #include <linux/ioport.h> |
| #include <linux/io.h> |
| |
| #include <lantiq_soc.h> |
| |
| #define LTQ_GPIO_OUT 0x00 |
| #define LTQ_GPIO_IN 0x04 |
| #define LTQ_GPIO_DIR 0x08 |
| #define LTQ_GPIO_ALTSEL0 0x0C |
| #define LTQ_GPIO_ALTSEL1 0x10 |
| #define LTQ_GPIO_OD 0x14 |
| |
| #define PINS_PER_PORT 16 |
| #define MAX_PORTS 3 |
| |
| #define ltq_gpio_getbit(m, r, p) (!!(ltq_r32(m + r) & (1 << p))) |
| #define ltq_gpio_setbit(m, r, p) ltq_w32_mask(0, (1 << p), m + r) |
| #define ltq_gpio_clearbit(m, r, p) ltq_w32_mask((1 << p), 0, m + r) |
| |
| struct ltq_gpio { |
| void __iomem *membase; |
| struct gpio_chip chip; |
| }; |
| |
| static struct ltq_gpio ltq_gpio_port[MAX_PORTS]; |
| |
| int gpio_to_irq(unsigned int gpio) |
| { |
| return -EINVAL; |
| } |
| EXPORT_SYMBOL(gpio_to_irq); |
| |
| int irq_to_gpio(unsigned int gpio) |
| { |
| return -EINVAL; |
| } |
| EXPORT_SYMBOL(irq_to_gpio); |
| |
| int ltq_gpio_request(unsigned int pin, unsigned int alt0, |
| unsigned int alt1, unsigned int dir, const char *name) |
| { |
| int id = 0; |
| |
| if (pin >= (MAX_PORTS * PINS_PER_PORT)) |
| return -EINVAL; |
| if (gpio_request(pin, name)) { |
| pr_err("failed to setup lantiq gpio: %s\n", name); |
| return -EBUSY; |
| } |
| if (dir) |
| gpio_direction_output(pin, 1); |
| else |
| gpio_direction_input(pin); |
| while (pin >= PINS_PER_PORT) { |
| pin -= PINS_PER_PORT; |
| id++; |
| } |
| if (alt0) |
| ltq_gpio_setbit(ltq_gpio_port[id].membase, |
| LTQ_GPIO_ALTSEL0, pin); |
| else |
| ltq_gpio_clearbit(ltq_gpio_port[id].membase, |
| LTQ_GPIO_ALTSEL0, pin); |
| if (alt1) |
| ltq_gpio_setbit(ltq_gpio_port[id].membase, |
| LTQ_GPIO_ALTSEL1, pin); |
| else |
| ltq_gpio_clearbit(ltq_gpio_port[id].membase, |
| LTQ_GPIO_ALTSEL1, pin); |
| return 0; |
| } |
| EXPORT_SYMBOL(ltq_gpio_request); |
| |
| static void ltq_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) |
| { |
| struct ltq_gpio *ltq_gpio = container_of(chip, struct ltq_gpio, chip); |
| |
| if (value) |
| ltq_gpio_setbit(ltq_gpio->membase, LTQ_GPIO_OUT, offset); |
| else |
| ltq_gpio_clearbit(ltq_gpio->membase, LTQ_GPIO_OUT, offset); |
| } |
| |
| static int ltq_gpio_get(struct gpio_chip *chip, unsigned int offset) |
| { |
| struct ltq_gpio *ltq_gpio = container_of(chip, struct ltq_gpio, chip); |
| |
| return ltq_gpio_getbit(ltq_gpio->membase, LTQ_GPIO_IN, offset); |
| } |
| |
| static int ltq_gpio_direction_input(struct gpio_chip *chip, unsigned int offset) |
| { |
| struct ltq_gpio *ltq_gpio = container_of(chip, struct ltq_gpio, chip); |
| |
| ltq_gpio_clearbit(ltq_gpio->membase, LTQ_GPIO_OD, offset); |
| ltq_gpio_clearbit(ltq_gpio->membase, LTQ_GPIO_DIR, offset); |
| |
| return 0; |
| } |
| |
| static int ltq_gpio_direction_output(struct gpio_chip *chip, |
| unsigned int offset, int value) |
| { |
| struct ltq_gpio *ltq_gpio = container_of(chip, struct ltq_gpio, chip); |
| |
| ltq_gpio_setbit(ltq_gpio->membase, LTQ_GPIO_OD, offset); |
| ltq_gpio_setbit(ltq_gpio->membase, LTQ_GPIO_DIR, offset); |
| ltq_gpio_set(chip, offset, value); |
| |
| return 0; |
| } |
| |
| static int ltq_gpio_req(struct gpio_chip *chip, unsigned offset) |
| { |
| struct ltq_gpio *ltq_gpio = container_of(chip, struct ltq_gpio, chip); |
| |
| ltq_gpio_clearbit(ltq_gpio->membase, LTQ_GPIO_ALTSEL0, offset); |
| ltq_gpio_clearbit(ltq_gpio->membase, LTQ_GPIO_ALTSEL1, offset); |
| return 0; |
| } |
| |
| static int ltq_gpio_probe(struct platform_device *pdev) |
| { |
| struct resource *res; |
| |
| if (pdev->id >= MAX_PORTS) { |
| dev_err(&pdev->dev, "invalid gpio port %d\n", |
| pdev->id); |
| return -EINVAL; |
| } |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!res) { |
| dev_err(&pdev->dev, "failed to get memory for gpio port %d\n", |
| pdev->id); |
| return -ENOENT; |
| } |
| res = devm_request_mem_region(&pdev->dev, res->start, |
| resource_size(res), dev_name(&pdev->dev)); |
| if (!res) { |
| dev_err(&pdev->dev, |
| "failed to request memory for gpio port %d\n", |
| pdev->id); |
| return -EBUSY; |
| } |
| ltq_gpio_port[pdev->id].membase = devm_ioremap_nocache(&pdev->dev, |
| res->start, resource_size(res)); |
| if (!ltq_gpio_port[pdev->id].membase) { |
| dev_err(&pdev->dev, "failed to remap memory for gpio port %d\n", |
| pdev->id); |
| return -ENOMEM; |
| } |
| ltq_gpio_port[pdev->id].chip.label = "ltq_gpio"; |
| ltq_gpio_port[pdev->id].chip.direction_input = ltq_gpio_direction_input; |
| ltq_gpio_port[pdev->id].chip.direction_output = |
| ltq_gpio_direction_output; |
| ltq_gpio_port[pdev->id].chip.get = ltq_gpio_get; |
| ltq_gpio_port[pdev->id].chip.set = ltq_gpio_set; |
| ltq_gpio_port[pdev->id].chip.request = ltq_gpio_req; |
| ltq_gpio_port[pdev->id].chip.base = PINS_PER_PORT * pdev->id; |
| ltq_gpio_port[pdev->id].chip.ngpio = PINS_PER_PORT; |
| platform_set_drvdata(pdev, <q_gpio_port[pdev->id]); |
| return gpiochip_add(<q_gpio_port[pdev->id].chip); |
| } |
| |
| static struct platform_driver |
| ltq_gpio_driver = { |
| .probe = ltq_gpio_probe, |
| .driver = { |
| .name = "ltq_gpio", |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| int __init ltq_gpio_init(void) |
| { |
| int ret = platform_driver_register(<q_gpio_driver); |
| |
| if (ret) |
| pr_info("ltq_gpio : Error registering platfom driver!"); |
| return ret; |
| } |
| |
| postcore_initcall(ltq_gpio_init); |