X-Git-Url: http://git.linex4red.de/pub/spi-gpio-pp.git/blobdiff_plain/e539cfa0af8f19cb21fca37079e4805935814ba5..813458776ee097275c377f19853773b39dab193a:/gpio_parport.c?ds=inline diff --git a/gpio_parport.c b/gpio_parport.c new file mode 100644 index 0000000..73a16b1 --- /dev/null +++ b/gpio_parport.c @@ -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 +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_GPIO_PARPORT_MUTEX +#include +#define sCONFIG_GPIO_PARPORT_MUTEX "CONFIG_GPIO_PARPORT_MUTEX" +#else +#include +#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);