| /* |
| * CLPS711X GPIO driver |
| * |
| * Copyright (C) 2012 Alexander Shiyan <shc_work@mail.ru> |
| * |
| * 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. |
| */ |
| |
| #include <linux/io.h> |
| #include <linux/slab.h> |
| #include <linux/gpio.h> |
| #include <linux/module.h> |
| #include <linux/spinlock.h> |
| #include <linux/platform_device.h> |
| |
| #include <mach/hardware.h> |
| |
| #define CLPS711X_GPIO_PORTS 5 |
| #define CLPS711X_GPIO_NAME "gpio-clps711x" |
| |
| struct clps711x_gpio { |
| struct gpio_chip chip[CLPS711X_GPIO_PORTS]; |
| spinlock_t lock; |
| }; |
| |
| static void __iomem *clps711x_ports[] = { |
| CLPS711X_VIRT_BASE + PADR, |
| CLPS711X_VIRT_BASE + PBDR, |
| CLPS711X_VIRT_BASE + PCDR, |
| CLPS711X_VIRT_BASE + PDDR, |
| CLPS711X_VIRT_BASE + PEDR, |
| }; |
| |
| static void __iomem *clps711x_pdirs[] = { |
| CLPS711X_VIRT_BASE + PADDR, |
| CLPS711X_VIRT_BASE + PBDDR, |
| CLPS711X_VIRT_BASE + PCDDR, |
| CLPS711X_VIRT_BASE + PDDDR, |
| CLPS711X_VIRT_BASE + PEDDR, |
| }; |
| |
| #define clps711x_port(x) clps711x_ports[x->base / 8] |
| #define clps711x_pdir(x) clps711x_pdirs[x->base / 8] |
| |
| static int gpio_clps711x_get(struct gpio_chip *chip, unsigned offset) |
| { |
| return !!(readb(clps711x_port(chip)) & (1 << offset)); |
| } |
| |
| static void gpio_clps711x_set(struct gpio_chip *chip, unsigned offset, |
| int value) |
| { |
| int tmp; |
| unsigned long flags; |
| struct clps711x_gpio *gpio = dev_get_drvdata(chip->dev); |
| |
| spin_lock_irqsave(&gpio->lock, flags); |
| tmp = readb(clps711x_port(chip)) & ~(1 << offset); |
| if (value) |
| tmp |= 1 << offset; |
| writeb(tmp, clps711x_port(chip)); |
| spin_unlock_irqrestore(&gpio->lock, flags); |
| } |
| |
| static int gpio_clps711x_dir_in(struct gpio_chip *chip, unsigned offset) |
| { |
| int tmp; |
| unsigned long flags; |
| struct clps711x_gpio *gpio = dev_get_drvdata(chip->dev); |
| |
| spin_lock_irqsave(&gpio->lock, flags); |
| tmp = readb(clps711x_pdir(chip)) & ~(1 << offset); |
| writeb(tmp, clps711x_pdir(chip)); |
| spin_unlock_irqrestore(&gpio->lock, flags); |
| |
| return 0; |
| } |
| |
| static int gpio_clps711x_dir_out(struct gpio_chip *chip, unsigned offset, |
| int value) |
| { |
| int tmp; |
| unsigned long flags; |
| struct clps711x_gpio *gpio = dev_get_drvdata(chip->dev); |
| |
| spin_lock_irqsave(&gpio->lock, flags); |
| tmp = readb(clps711x_pdir(chip)) | (1 << offset); |
| writeb(tmp, clps711x_pdir(chip)); |
| tmp = readb(clps711x_port(chip)) & ~(1 << offset); |
| if (value) |
| tmp |= 1 << offset; |
| writeb(tmp, clps711x_port(chip)); |
| spin_unlock_irqrestore(&gpio->lock, flags); |
| |
| return 0; |
| } |
| |
| static int gpio_clps711x_dir_in_inv(struct gpio_chip *chip, unsigned offset) |
| { |
| int tmp; |
| unsigned long flags; |
| struct clps711x_gpio *gpio = dev_get_drvdata(chip->dev); |
| |
| spin_lock_irqsave(&gpio->lock, flags); |
| tmp = readb(clps711x_pdir(chip)) | (1 << offset); |
| writeb(tmp, clps711x_pdir(chip)); |
| spin_unlock_irqrestore(&gpio->lock, flags); |
| |
| return 0; |
| } |
| |
| static int gpio_clps711x_dir_out_inv(struct gpio_chip *chip, unsigned offset, |
| int value) |
| { |
| int tmp; |
| unsigned long flags; |
| struct clps711x_gpio *gpio = dev_get_drvdata(chip->dev); |
| |
| spin_lock_irqsave(&gpio->lock, flags); |
| tmp = readb(clps711x_pdir(chip)) & ~(1 << offset); |
| writeb(tmp, clps711x_pdir(chip)); |
| tmp = readb(clps711x_port(chip)) & ~(1 << offset); |
| if (value) |
| tmp |= 1 << offset; |
| writeb(tmp, clps711x_port(chip)); |
| spin_unlock_irqrestore(&gpio->lock, flags); |
| |
| return 0; |
| } |
| |
| static struct { |
| char *name; |
| int nr; |
| int inv_dir; |
| } clps711x_gpio_ports[] __initconst = { |
| { "PORTA", 8, 0, }, |
| { "PORTB", 8, 0, }, |
| { "PORTC", 8, 0, }, |
| { "PORTD", 8, 1, }, |
| { "PORTE", 3, 0, }, |
| }; |
| |
| static int __init gpio_clps711x_init(void) |
| { |
| int i; |
| struct platform_device *pdev; |
| struct clps711x_gpio *gpio; |
| |
| pdev = platform_device_alloc(CLPS711X_GPIO_NAME, 0); |
| if (!pdev) { |
| pr_err("Cannot create platform device: %s\n", |
| CLPS711X_GPIO_NAME); |
| return -ENOMEM; |
| } |
| |
| platform_device_add(pdev); |
| |
| gpio = devm_kzalloc(&pdev->dev, sizeof(struct clps711x_gpio), |
| GFP_KERNEL); |
| if (!gpio) { |
| dev_err(&pdev->dev, "GPIO allocating memory error\n"); |
| platform_device_del(pdev); |
| platform_device_put(pdev); |
| return -ENOMEM; |
| } |
| |
| platform_set_drvdata(pdev, gpio); |
| |
| spin_lock_init(&gpio->lock); |
| |
| for (i = 0; i < CLPS711X_GPIO_PORTS; i++) { |
| gpio->chip[i].owner = THIS_MODULE; |
| gpio->chip[i].dev = &pdev->dev; |
| gpio->chip[i].label = clps711x_gpio_ports[i].name; |
| gpio->chip[i].base = i * 8; |
| gpio->chip[i].ngpio = clps711x_gpio_ports[i].nr; |
| gpio->chip[i].get = gpio_clps711x_get; |
| gpio->chip[i].set = gpio_clps711x_set; |
| if (!clps711x_gpio_ports[i].inv_dir) { |
| gpio->chip[i].direction_input = gpio_clps711x_dir_in; |
| gpio->chip[i].direction_output = gpio_clps711x_dir_out; |
| } else { |
| gpio->chip[i].direction_input = gpio_clps711x_dir_in_inv; |
| gpio->chip[i].direction_output = gpio_clps711x_dir_out_inv; |
| } |
| WARN_ON(gpiochip_add(&gpio->chip[i])); |
| } |
| |
| dev_info(&pdev->dev, "GPIO driver initialized\n"); |
| |
| return 0; |
| } |
| arch_initcall(gpio_clps711x_init); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); |
| MODULE_DESCRIPTION("CLPS711X GPIO driver"); |