+/*
+ * 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);