/* * 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_unregister(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");