Add gpio_parport and gpio_test
[pub/spi-gpio-pp.git] / gpio_parport.c
diff --git a/gpio_parport.c b/gpio_parport.c
new file mode 100644 (file)
index 0000000..73a16b1
--- /dev/null
@@ -0,0 +1,512 @@
+/*
+ * gpio_parport.c - GPIO master controller driver based on the parallel port adapter
+ *
+ * Copyright (C) 2008 Peter Henn
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * The gpio_parport driver uses the gpio framwork started with kernel 2.6.25.
+ * It is designed as a PC reference GPIO driver just for testing on Intel based
+ * PC architectures. Therefore the whole functionality of a parport used as 
+ * GPIO was never in focus.  We initial need only one dedicated output and one 
+ * dedicated input, but with interrupt support.
+ *
+ * The driver uses caches, which saves the output status, because the IO output
+ * need long time on x86 architecture. The caches will be setup with the initial
+ * values read from those ports during driver startup. If you are using the parport
+ * in shared operation mode, the caches has to be updated too.
+ */
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/parport.h>
+#include <asm/gpio.h>
+
+#ifdef CONFIG_GPIO_PARPORT_MUTEX
+#include <linux/mutex.h>
+#define sCONFIG_GPIO_PARPORT_MUTEX "CONFIG_GPIO_PARPORT_MUTEX"
+#else
+#include <linux/spinlock.h>
+#define sCONFIG_GPIO_PARPORT_MUTEX ""
+#endif
+
+
+/* ToDo:
+ * - proper handling error flags for gpio_to_irq ...
+ * - using pp->dev-port should be equal to pp->port ...
+ * - Sharing parport with other devices controled by module load
+ *   paramter with using mutex mode ... but check with releasing parport IRQ handler  
+ * - Verifing Parport modes
+ * - Add sysfs Debug functionality
+ * - Support PM functions like resume, suspend
+ *
+ *   Parallel                
+ *     Port      Direction  Register Name                   Signal
+ *  -----------  ---------  -------- ---------------------- ------
+ *   nStrobe  1     -->     nC0      PARPORT_CONTROL_STROBE nGPO08
+ *      D0    2     -->      D0      (1 << 0)                GPO00
+ *      D1    3     -->      D1      (1 << 1)                GPO01
+ *      D2    4     -->      D2      (1 << 2)                GPO02
+ *      D3    5     -->      D3      (1 << 3)                GPO03
+ *      D4    6     -->      D4      (1 << 4)                GPO04
+ *      D5    7     -->      D5      (1 << 5)                GPO05
+ *      D6    8     -->      D6      (1 << 6)                GPO06
+ *      D7    9     -->      D7      (1 << 7)                GPO07
+ *   nAckn   10     <--      S6      PARPORT_STATUS_ACK      GPI15*
+ *    Busy   11     <--     nS7      PARPORT_STATUS_BUSY    nGPI16
+ *    Paper  12     <--      S5      PARPORT_STATUS_PAPEROUT GPI14
+ *    Sel    13     <--      S4      PARPORT_STATUS_SELECT   GPI13
+ *   nFeed   14     -->     nC1      PARPORT_CONTROL_AUTOFD nGPO09
+ *   nError  15     <--      S3      PARPORT_STATUS_ERROR    GPI12
+ *   nInit   16     -->      C2      PARPORT_CONTROL_INIT    GPO10
+ *   nSelIn  17     -->     nC3      PARPORT_CONTROL_SELECT nGPO11 
+ *     GND   25     --       --                              GND
+ *
+ *  Signal:
+ *  - GPOx  : general purpose output only
+ *  - GPIx  : general purpose input only
+ *  - GPIx* : interrupt in
+ *  - nGPXx : signal is inverted
+ */
+
+//*****************************************************************************
+// MODULE PARAMETERS 
+//*****************************************************************************
+#define DRVNAME                 "gpio_parport"   /* name of the driver */
+#define DRIVER_VERSION            "0.2"   /* helps identifing different versions of that driver */
+
+#define GPIO_PARPORT_CTRL_PORT_BITS  8    /* starting with bit 8 use the parport control port */
+#define GPIO_PARPORT_STAT_PORT_BITS 12    /* starting with bit 12 use the parport status port */
+#define GPIO_PARPORT_STAT_PORT_OFF   3    /* parport status port needs internally 3 bit offset */
+#define GPIO_PARPORT_INTERRUPT      15    /* gpio offset for parport pin Acknolge, which support interrupt */
+#define GPIO_PARPORT_NR_GPIOS       17    /* parport support up to 17 general purpose (dedicated) Inputs and Outputs */
+
+
+static int modparam_gpiobase = -1         /* dynamic assingment */;
+
+typedef struct {
+       struct pardevice        *dev;     /* parport device entry */
+        struct parport         *port;    /* parport port entry */
+        u8                     dcache;   /* parport data port mirror bits */
+        u8                     ccache;   /* parport control port mirror bits */
+        struct gpio_chip        gpio;     /* parport GPIO seen as a chip */
+        int                     irq;      /* parport interrupt */
+        #ifdef CONFIG_GPIO_PARPORT_MUTEX
+            struct mutex        lock;     /* prevent double accessing bits */
+        #else
+            spinlock_t          lock;     /* prevent double accessing bits */
+        #endif
+        struct list_head        list;     /* gpio_parport host controller list */
+} gpio_pp_struct;
+
+
+/*----------------------------------------------------------------------*/
+/*
+** use the lock and unlock macros to get rid of the bad #ifdef
+** stuff in the rest of the code.
+*/
+#ifdef CONFIG_GPIO_PARPORT_MUTEX
+#define gpio_parport_lock(lock, flags)           \
+        do {                                     \
+           mutex_lock(lock);                    \
+        } while (0)
+#else
+#define gpio_parport_lock(lock, flags)           \
+        do {                                     \
+           spin_lock_irqsave(lock, flags);      \
+        } while (0)
+#endif
+
+#ifdef CONFIG_GPIO_PARPORT_MUTEX
+#define gpio_parport_unlock(lock, flags)         \
+        do {                                     \
+           mutex_unlock(lock);                  \
+        } while (0)
+#else
+#define gpio_parport_unlock(lock, flags)         \
+        do {                                     \
+           spin_unlock_irqrestore(lock, flags); \
+        } while (0)
+#endif
+
+
+/*----------------------------------------------------------------------*/
+/*
+** parport data bit input function
+*/
+static inline int gpio_parport_data_get(gpio_pp_struct *pp, unsigned mask)
+{
+        // good idea to update also the cache
+       pp->dcache = parport_read_data(pp->port);
+       return ((pp->dcache & mask) != 0);
+}
+
+/*
+** parport control bit input function
+*/
+static inline int gpio_parport_ctrl_get(gpio_pp_struct *pp, unsigned mask)
+{
+        // good idea to update also the cache
+       pp->ccache = parport_read_control(pp->port);
+       return ((pp->ccache & mask) != 0);
+}
+
+/*
+** parport status bit input function
+*/
+static inline int gpio_parport_status_get(gpio_pp_struct *pp, unsigned mask)
+{
+        unsigned int cache = parport_read_status(pp->port);
+       return ((cache & mask) != 0);
+}
+
+
+/*
+** general parport read bit function
+*/ 
+static int gpio_parport_get(struct gpio_chip *gpio, unsigned offset)
+{
+        gpio_pp_struct *pp = container_of(gpio, gpio_pp_struct, gpio);
+       int            status = 0; /* inititalization ensures returm zero for unhandled bits */
+       unsigned       long flags;
+
+       if (offset < GPIO_PARPORT_CTRL_PORT_BITS) {
+               u8 mask = 1 << offset;
+               gpio_parport_lock(&pp->lock, flags);
+               status = gpio_parport_data_get(pp, mask);
+                gpio_parport_unlock(&pp->lock, flags);
+       } else if (offset < GPIO_PARPORT_STAT_PORT_BITS) {
+               u8 mask = 1 << (offset - GPIO_PARPORT_CTRL_PORT_BITS);
+               gpio_parport_lock(&pp->lock, flags);
+               status = gpio_parport_ctrl_get(pp, mask);
+                gpio_parport_unlock(&pp->lock, flags);
+       } else if (offset < GPIO_PARPORT_NR_GPIOS) {
+               u8 mask = 1 << (offset - GPIO_PARPORT_STAT_PORT_BITS + GPIO_PARPORT_STAT_PORT_OFF);
+               gpio_parport_lock(&pp->lock, flags);
+               status = gpio_parport_status_get(pp, mask);
+                gpio_parport_unlock(&pp->lock, flags);
+       }
+       return status;
+}
+
+
+/*----------------------------------------------------------------------*/
+/*
+** parport data bit output function
+*/
+static inline void gpio_parport_data_set(gpio_pp_struct *pp, unsigned mask, int value)
+{
+        u8 byte = pp->dcache; // use old value from cache
+       if (value)
+               byte |= mask;
+       else
+               byte &= ~mask;
+       pp->dcache = byte;   // restore cache
+       return parport_write_data(pp->port, byte);
+}
+
+/*
+** parport control bit output function
+** most PARPORT_CONTROL_* bits are negated, but they are currently _not_ corrected 
+** to keep it as simple as possible
+*/
+static inline void gpio_parport_ctrl_set(gpio_pp_struct *pp, unsigned mask, int value)
+{
+        u8 byte = pp->ccache; // use old value from cache
+       if (value)
+               byte |= mask;
+       else
+               byte &= ~mask;
+       pp->ccache = byte;   // restore cache
+       return parport_write_control(pp->port, byte);
+}
+
+/*
+** general parport set bit output function
+*/
+static void gpio_parport_set(struct gpio_chip *gpio, unsigned offset, int value)
+{
+        gpio_pp_struct *pp = container_of(gpio, gpio_pp_struct, gpio);
+       unsigned        long flags;
+
+       if (offset < GPIO_PARPORT_CTRL_PORT_BITS) {
+               u8 mask = 1 << offset;
+               gpio_parport_lock(&pp->lock, flags);
+               gpio_parport_data_set(pp, mask, value);
+                gpio_parport_unlock(&pp->lock, flags);
+       } else if (offset < GPIO_PARPORT_STAT_PORT_BITS) {
+               u8 mask = 1 << (offset - GPIO_PARPORT_CTRL_PORT_BITS);
+               gpio_parport_lock(&pp->lock, flags);
+               gpio_parport_ctrl_set(pp, mask, value);
+                gpio_parport_unlock(&pp->lock, flags);
+       }
+}
+
+/*----------------------------------------------------------------------*/
+/*
+** general parport direction input funtion
+** we support only the parport control bits as dedicated input bits
+*/
+static int gpio_parport_direction_input(struct gpio_chip * gpio, unsigned offset)
+{
+       if (offset < GPIO_PARPORT_STAT_PORT_BITS) {
+                return -ENOSYS;
+       } else if (offset < GPIO_PARPORT_NR_GPIOS) {
+                gpio_pp_struct *pp = container_of(gpio, gpio_pp_struct, gpio);
+               unsigned long flags;
+               gpio_parport_lock(&pp->lock, flags);
+               if ((GPIO_PARPORT_INTERRUPT == offset) && (pp->irq >= 0)) {
+                       parport_enable_irq(pp->port);
+               }
+                gpio_parport_unlock(&pp->lock, flags);
+               return 0;
+       }
+       return -ENODEV;
+}
+
+/*
+** general parport direction output function
+** we support only data bits and control bits as dedicated output bits
+*/
+static int gpio_parport_direction_output(struct gpio_chip * gpio, unsigned offset, int value) 
+{
+        gpio_pp_struct *pp = container_of(gpio, gpio_pp_struct, gpio);
+       unsigned        long flags;
+
+       if (offset < GPIO_PARPORT_CTRL_PORT_BITS) {
+               u8 mask = 1 << offset;
+               gpio_parport_lock(&pp->lock, flags);
+               gpio_parport_data_set(pp, mask, value);
+                gpio_parport_unlock(&pp->lock, flags);
+               return 0;
+       } else if (offset < GPIO_PARPORT_STAT_PORT_BITS) {
+               u8 mask = 1 << (offset - GPIO_PARPORT_CTRL_PORT_BITS);
+               gpio_parport_lock(&pp->lock, flags);
+               gpio_parport_ctrl_set(pp, mask, value);
+                gpio_parport_unlock(&pp->lock, flags);
+               return 0;
+       } else if (offset < GPIO_PARPORT_NR_GPIOS) {
+               return -ENOSYS;
+       }
+        return -ENODEV;
+}
+
+/*----------------------------------------------------------------------*/
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 27))
+/*
+** parport interrupt support only for acknoledge on GPI15 interrupt
+*/
+static int gpio_parport_gpio_to_irq(struct gpio_chip * gpio, unsigned offset)
+{
+        gpio_pp_struct *pp = container_of(gpio, gpio_pp_struct, gpio);
+
+        if (GPIO_PARPORT_INTERRUPT == offset) {
+               return pp->irq;
+       } else {
+               return -ENOSYS;
+       }
+} 
+#endif
+
+/*----------------------------------------------------------------------*/
+/*
+** gpio_parport setup function
+** add all entries into the GPIO framework
+*/
+static void gpio_parport_gpio_setup(gpio_pp_struct *pp)
+{
+       struct gpio_chip *c = &pp->gpio;
+       c->label = (char *)pp->port->name;
+       c->owner = THIS_MODULE;
+       c->direction_input = gpio_parport_direction_input;
+       c->get = gpio_parport_get;
+       c->direction_output = gpio_parport_direction_output;
+       c->set = gpio_parport_set;
+       c->dbg_show = NULL;
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 27))
+       c->request = NULL;
+       c->free = NULL;
+       c->to_irq = gpio_parport_gpio_to_irq;
+#endif
+       c->base = modparam_gpiobase;
+       c->ngpio = GPIO_PARPORT_NR_GPIOS;
+        #ifdef CONFIG_GPIO_PARPORT_MUTEX
+           c->can_sleep = 1;
+           c->dev = pp->dev;
+        #else
+           c->can_sleep = 0;
+        #endif
+}
+
+/***************************************************************************
+ *         Parallel port attaching and detaching  routines                 *
+ ***************************************************************************/
+
+static LIST_HEAD(gpio_parport_hosts);
+
+/*
+** attach driver to parport is currently done with exclusive access. Unfortunately 
+** the driver is still loaded, although the attach might not be done successful.
+** If you have  already registered e.g. the lp driver, please unload this drivers
+** first as long as no shared supported is here implemented.
+*/
+static int __gpio_parport_attach(struct parport *pb)
+{
+        gpio_pp_struct *pp;
+       int err;
+
+       /* we need a zero initialized structure here */
+       pp = kzalloc(sizeof(*pp), GFP_KERNEL);
+       if (!pp)
+               return -ENOMEM;
+
+        #ifdef CONFIG_GPIO_PARPORT_MUTEX
+           mutex_init(&pp->lock);
+        #else
+           spin_lock_init(&pp->lock);
+        #endif
+
+        /* ----------------- ParPort Hooks ------------------- */
+       /* REVISIT:  no probe, just think it is there */
+       pp->port = pb;
+
+       //ToDo: share parport with other device, but may need a mutex lock!
+       //ToDo: check supported modes are ok
+       //ToDo: PM support, e.g. Suspend and Resume
+       /* interrupt handler will not be here registered. 
+           But an interrupt handler can be later registered to IRQ pin */
+       pp->dev = parport_register_device(pb, /* port */
+                                         DRVNAME, /* driver name */
+                                         NULL, /* preemption fkt */ 
+                                         NULL, /* wake-up fkt */
+                                         NULL, /* interrupt handler */
+                                         PARPORT_FLAG_EXCL, /* flags */
+                                         pp /* handle */ );
+       if (pp->dev == NULL) {
+               pr_err("%s: unable to register with parport\n", DRVNAME);
+               err = -ENOMEM;
+               goto err_release_mem;
+       }
+
+       err = parport_claim(pp->dev);
+       if (err < 0) {
+               pr_err("%s: unable to claim parport\n", DRVNAME);
+               err = -ENODEV;
+               goto err_unregister_parport;
+       }
+
+        /* ------------- GPIO PARPORT init --------------- */  
+       /* cache initialisation just preventing changing unused bits */
+       pp->dcache = parport_read_data(pp->port);
+       pp->ccache = parport_read_control(pp->port);
+
+       /* interrrut initialidation */
+        /* works for x86 arch, because we have nothing special to handle here */
+       if(pp->dev->port->irq != PARPORT_IRQ_NONE) {
+               pp->irq = pp->dev->port->irq;
+               /* we free the standard parport handler, because we want to use our own later */
+               free_irq(pp->irq, pp->dev->port);
+               pr_info("%s: IRQ %d is available\n", DRVNAME, pp->irq);
+       } else {
+               pp->irq = -ENXIO;
+       }
+
+        /* ----------------- GPIO init ------------------- */
+       gpio_parport_gpio_setup(pp);
+       err = gpiochip_add(&pp->gpio);
+       if (err != 0) {
+               pr_err("%s: Failed to register GPIOs\n", DRVNAME);
+               goto err_release_parport;
+       }
+
+       pr_info("%s: Abusing Parport for GPIO  %d to %d, labled as %s\n",
+               DRVNAME, pp->gpio.base, pp->gpio.base + GPIO_PARPORT_NR_GPIOS - 1, pp->gpio.label);
+
+       list_add_tail(&pp->list, &gpio_parport_hosts);
+       return 0;
+
+ err_release_parport:
+       parport_release(pp->dev);
+ err_unregister_parport:
+       parport_unregister_device(pp->dev);
+ err_release_mem:
+       kfree(pp);
+       return err;
+}
+
+
+
+static void gpio_parport_attach(struct parport *pb)
+{
+        (void)__gpio_parport_attach(pb);
+}
+
+
+static int gpio_parport_remove(gpio_pp_struct *pp)
+{
+        int err;
+        if (pp->irq >= 0) {
+               parport_disable_irq(pp->port);
+        }
+       err = gpiochip_remove(&pp->gpio);
+       if (err < 0)
+               pr_err("%s: gpio_remove error: %d\n", DRVNAME, err);
+       parport_release(pp->dev);
+        parport_unregister_device(pp->dev);
+       //ToDo: Check the unregister return value
+       return err;
+}
+
+static void gpio_parport_detach(struct parport *pb)
+{
+       gpio_pp_struct *pp;
+
+       list_for_each_entry(pp, &gpio_parport_hosts, list) {
+               if (pp->dev->port == pb) {
+                       (void)gpio_parport_remove(pp);   
+                       list_del_init(&pp->list);
+                       kfree(pp);
+                       break;
+               }
+       }
+}
+
+static struct parport_driver gpio_parport_driver = {
+       .name =         DRVNAME,
+       .attach =       gpio_parport_attach,
+       .detach =       gpio_parport_detach,
+};
+
+
+static int __init gpio_parport_init(void)
+{
+       return parport_register_driver(&gpio_parport_driver);
+}
+device_initcall(gpio_parport_init);
+
+static void __exit gpio_parport_exit(void)
+{
+       parport_unregister_driver(&gpio_parport_driver);
+}
+module_exit(gpio_parport_exit);
+
+MODULE_AUTHOR("Option Wireless");
+MODULE_DESCRIPTION("GPIO master controller driver for Parport Adapter");
+MODULE_LICENSE("GPL");
+
+MODULE_INFO(Flags, sCONFIG_GPIO_PARPORT_MUTEX);
+MODULE_INFO(Version, DRIVER_VERSION);