rendered paste bodycommit e608c684f66576b6db53cefa4d99fa007cff124eAuthor: Philipp Zabel <philipp.zabel@gmail.com>Date: Thu Nov 22 10:01:54 2007 +0100 htc-egpio, a driver for GPIO/IRQ expanders with fixed input/output pins implemented in CPLD chips on several HTC devices. The original driver was written by Kevin O'Connor, I have adapted it to use gpiolib and made the bus/register widths configurable. Signed-off-by: Philipp Zabel <philipp.zabel@gmail.com>diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfigindex 0c886c8..c682366 100644--- a/drivers/mfd/Kconfig+++ b/drivers/mfd/Kconfig@@ -22,6 +22,14 @@ config MFD_ASIC3 This driver supports the ASIC3 multifunction chip found on many PDAs (mainly iPAQ and HTC based ones) +config HTC_EGPIO+ bool "HTC EGPIO support"+ depends on GENERIC_HARDIRQS && ARM+ help+ This driver supports the CPLD egpio chip present on+ several HTC phones. It provides basic support for input+ pins, output pins, and irqs.+ endmenu menu "Multimedia Capabilities Port drivers"diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefileindex 521cd5c..f81f8d2 100644--- a/drivers/mfd/Makefile+++ b/drivers/mfd/Makefile@@ -5,6 +5,8 @@ obj-$(CONFIG_MFD_SM501) += sm501.o obj-$(CONFIG_MFD_ASIC3) += asic3.o +obj-$(CONFIG_HTC_EGPIO) += htc-egpio.o+ obj-$(CONFIG_MCP) += mcp-core.o obj-$(CONFIG_MCP_SA11X0) += mcp-sa11x0.o obj-$(CONFIG_MCP_UCB1200) += ucb1x00-core.odiff --git a/drivers/mfd/htc-egpio.c b/drivers/mfd/htc-egpio.cnew file mode 100644index 0000000..feea5eb--- /dev/null+++ b/drivers/mfd/htc-egpio.c@@ -0,0 +1,432 @@+/*+ * Support for the GPIO/IRQ expander chips present on several HTC phones.+ * These are implemented in CPLD chips present on the board.+ *+ * Copyright (c) 2007 Kevin O'Connor <kevin@koconnor.net>+ * Copyright (c) 2007 Philipp Zabel <philipp.zabel@gmail.com>+ *+ * This file may be distributed under the terms of the GNU GPL license.+ */++#include <linux/kernel.h>+#include <linux/errno.h> /* ENODEV */+#include <linux/interrupt.h> /* enable_irq_wake */+#include <linux/spinlock.h> /* spinlock_t */+#include <linux/platform_device.h> /* platform_device */+#include <linux/module.h>+#include <linux/mfd/htc-egpio.h>++#include <asm/irq.h> /* IRQT_RISING */+#include <asm/io.h> /* ioremap_nocache */+#include <asm/mach/irq.h> /* struct irq_chip */++struct egpio_chip {+ int reg_start;+ int cached_values;+ int is_out;+ struct device *dev;+ struct gpio_chip chip;+};++struct egpio_info {+ spinlock_t lock;++ /* iomem info */+ void __iomem *base_addr;+ int bus_shift; /* byte shift */+ int reg_shift; /* bit shift */+ int reg_mask;++ /* irq info */+ int ack_register;+ int ack_write;+ u16 irqs_enabled;+ uint irq_start;+ int nirqs;+ uint chained_irq;++ /* egpio info */+ struct egpio_chip *chip;+ int nchips;+};++static inline void egpio_writew(u16 value, struct egpio_info *ei, int reg)+{+ writew(value, ei->base_addr + (reg << ei->bus_shift));+}++static inline u16 egpio_readw(struct egpio_info *ei, int reg)+{+ return readw(ei->base_addr + (reg << ei->bus_shift));+}++/*+ * IRQs+ */++static inline void ack_irqs(struct egpio_info *ei)+{+ egpio_writew(ei->ack_write, ei, ei->ack_register);+ pr_debug("EGPIO ack - write %x to base+%x\n",+ ei->ack_write, ei->ack_register << ei->bus_shift);+}++static void egpio_ack(unsigned int irq)+{+}++/* There does not appear to be a way to proactively mask interrupts+ * on the egpio chip itself. So, we simply ignore interrupts that+ * aren't desired. */+static void egpio_mask(unsigned int irq)+{+ struct egpio_info *ei = get_irq_chip_data(irq);+ ei->irqs_enabled &= ~(1 << (irq - ei->irq_start));+ pr_debug("EGPIO mask %d %04x\n", irq, ei->irqs_enabled);+}+static void egpio_unmask(unsigned int irq)+{+ struct egpio_info *ei = get_irq_chip_data(irq);+ ei->irqs_enabled |= 1 << (irq - ei->irq_start);+ pr_debug("EGPIO unmask %d %04x\n", irq, ei->irqs_enabled);+}++static struct irq_chip egpio_muxed_chip = {+ .name = "htc-egpio",+ .ack = egpio_ack,+ .mask = egpio_mask,+ .unmask = egpio_unmask,+};++static void egpio_handler(unsigned int irq, struct irq_desc *desc)+{+ struct egpio_info *ei = get_irq_data(irq);+ int irqpin;++ /* Read current pins. */+ unsigned long readval = egpio_readw(ei, ei->ack_register);+ pr_debug("IRQ reg: %x\n", (unsigned int)readval);+ /* Ack/unmask interrupts. */+ ack_irqs(ei);+ /* Process all set pins. */+ readval &= ei->irqs_enabled;+ for_each_bit(irqpin, &readval, ei->nirqs) {+ /* Run irq handler */+ pr_debug("got IRQ %d\n", irqpin);+ irq = ei->irq_start + irqpin;+ desc = &irq_desc[irq];+ desc->handle_irq(irq, desc);+ }+}++int htc_egpio_get_wakeup_irq(struct device *dev)+{+ struct egpio_info *ei = dev_get_drvdata(dev);++ /* Read current pins. */+ u16 readval = egpio_readw(ei, ei->ack_register);+ /* Ack/unmask interrupts. */+ ack_irqs(ei);+ /* Return first set pin. */+ readval &= ei->irqs_enabled;+ return ei->irq_start + ffs(readval) - 1;+}+EXPORT_SYMBOL(htc_egpio_get_wakeup_irq);++static inline int egpio_pos(struct egpio_info *ei, int bit)+{+ return bit >> ei->reg_shift;+}++static inline int egpio_bit(struct egpio_info *ei, int bit)+{+ return 1 << (bit & ((1 << ei->reg_shift)-1));+}++/*+ * Input pins+ */++static int egpio_get(struct gpio_chip *chip, unsigned offset)+{+ struct egpio_chip *egpio;+ struct egpio_info *ei;+ unsigned bit;+ int reg;+ int value;++ pr_debug("egpio_get_value(%d)\n", chip->base + offset);++ egpio = container_of(chip, struct egpio_chip, chip);+ ei = dev_get_drvdata(egpio->dev);+ bit = egpio_bit(ei, offset);+ reg = egpio->reg_start + egpio_pos(ei, offset);++ value = egpio_readw(ei, reg);+ pr_debug("readw(%p + %x) = %x\n",+ ei->base_addr, reg << ei->bus_shift, value);+ return value & bit;+}++static int egpio_direction_input(struct gpio_chip *chip, unsigned offset)+{+ struct egpio_chip *egpio;++ egpio = container_of(chip, struct egpio_chip, chip);+ return test_bit(offset, &egpio->is_out) ? -EINVAL : 0;+}+++/*+ * Output pins+ */++static void egpio_set(struct gpio_chip *chip, unsigned offset, int value)+{+ unsigned long flag;+ struct egpio_chip *egpio;+ struct egpio_info *ei;+ unsigned bit;+ int pos;+ int reg;+ int shift;++ pr_debug("egpio_set(%s, %d(%d), %d)\n",+ chip->label, offset, offset+chip->base, value);++ egpio = container_of(chip, struct egpio_chip, chip);+ ei = dev_get_drvdata(egpio->dev);+ bit = egpio_bit(ei, offset);+ pos = egpio_pos(ei, offset);+ reg = egpio->reg_start + pos;+ shift = pos << ei->reg_shift;++ pr_debug("egpio %s: reg %d = 0x%04x\n", value ? "set" : "clear",+ reg, (egpio->cached_values >> shift) & ei->reg_mask);++ spin_lock_irqsave(&ei->lock, flag);+ if (value)+ egpio->cached_values |= (1 << offset);+ else+ egpio->cached_values &= ~(1 << offset);+ egpio_writew((egpio->cached_values >> shift) & ei->reg_mask, ei, reg);+ spin_unlock_irqrestore(&ei->lock, flag);+}++static int egpio_direction_output(struct gpio_chip *chip,+ unsigned offset, int value)+{+ struct egpio_chip *egpio;++ egpio = container_of(chip, struct egpio_chip, chip);+ if (test_bit(offset, &egpio->is_out)) {+ egpio_set(chip, offset, value);+ return 0;+ } else {+ return -EINVAL;+ }+}++static void egpio_write_cache(struct egpio_info *ei)+{+ int i;+ struct egpio_chip *egpio;+ int shift;++ for (i = 0; i < ei->nchips; i++) {+ egpio = &(ei->chip[i]);+ for (shift = 0; shift < egpio->chip.ngpio;+ shift <<= ei->reg_shift) {+ int reg = egpio->reg_start + egpio_pos(ei, shift);+ if ((egpio->is_out >> shift) & ei->reg_mask)+ egpio_writew((egpio->cached_values >> shift)+ & ei->reg_mask, ei, reg);+ }+ }+}+++/*+ * Setup+ */++static int __init egpio_probe(struct platform_device *pdev)+{+ struct htc_egpio_platform_data *pdata = pdev->dev.platform_data;+ struct resource *res;+ struct egpio_info *ei;+ struct gpio_chip *chip;+ unsigned int irq, irq_end;+ int i;+ int ret;++ /* Initialize ei data structure. */+ ei = kzalloc(sizeof(*ei), GFP_KERNEL);+ if (!ei)+ return -ENOMEM;++ spin_lock_init(&ei->lock);++ /* Find chained irq */+ ret = -EINVAL;+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);+ if (res)+ ei->chained_irq = res->start;++ /* Map egpio chip into virtual address space. */+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);+ if (!res)+ goto fail;+ ei->base_addr = ioremap_nocache(res->start, res->end - res->start);+ if (!ei->base_addr)+ goto fail;+ printk(KERN_NOTICE "EGPIO phys=%08x virt=%p\n",+ res->start, ei->base_addr);++ if ((pdata->bus_width != 16) && (pdata->bus_width != 32))+ goto fail;+ ei->bus_shift = fls(pdata->bus_width - 1) - 3;+ pr_debug("bus_shift = %d\n", ei->bus_shift);++ if ((pdata->reg_width != 8) && (pdata->reg_width != 16))+ goto fail;+ ei->reg_shift = fls(pdata->reg_width - 1);+ pr_debug("reg_shift = %d\n", ei->reg_shift);++ ei->reg_mask = (1 << pdata->reg_width) - 1;++ platform_set_drvdata(pdev, ei);++ ei->nchips = pdata->num_chips;+ ei->chip = kzalloc(sizeof(struct egpio_chip) * ei->nchips, GFP_KERNEL);+ if (!ei) {+ ret = -ENOMEM;+ goto fail;+ }+ for (i = 0; i < ei->nchips; i++) {+ ei->chip[i].reg_start = pdata->chip[i].reg_start;+ ei->chip[i].cached_values = pdata->chip[i].initial_values;+ ei->chip[i].is_out = pdata->chip[i].direction;+ ei->chip[i].dev = &(pdev->dev);+ chip = &(ei->chip[i].chip);+ chip->label = "htc-egpio";+ chip->get = egpio_get;+ chip->set = egpio_set;+ chip->direction_input = egpio_direction_input;+ chip->direction_output = egpio_direction_output;+ chip->base = pdata->chip[i].gpio_base;+ chip->ngpio = pdata->chip[i].num_gpios;++ gpiochip_add(chip);+ }++ /* Set initial pin values */+ egpio_write_cache(ei);++ ei->irq_start = pdata->irq_base;+ ei->nirqs = pdata->num_irqs;+ ei->ack_register = pdata->ack_register;++ if (ei->chained_irq) {+ /* Setup irq handlers */+ ei->ack_write = 0xFFFF;+ if (pdata->invert_acks)+ ei->ack_write = 0;+ irq_end = ei->irq_start + ei->nirqs;+ for (irq = ei->irq_start; irq < irq_end; irq++) {+ set_irq_chip(irq, &egpio_muxed_chip);+ set_irq_chip_data(irq, ei);+ set_irq_handler(irq, handle_simple_irq);+ set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);+ }+ set_irq_type(ei->chained_irq, IRQT_RISING);+ set_irq_data(ei->chained_irq, ei);+ set_irq_chained_handler(ei->chained_irq, egpio_handler);+ ack_irqs(ei);++ device_init_wakeup(&pdev->dev, 1);+ }++ return 0;++fail:+ printk(KERN_ERR "EGPIO failed to setup\n");+ kfree(ei);+ return ret;+}++static int egpio_remove(struct platform_device *pdev)+{+ struct egpio_info *ei = platform_get_drvdata(pdev);+ unsigned int irq, irq_end;++ if (ei->chained_irq) {+ irq_end = ei->irq_start + ei->nirqs;+ for (irq = ei->irq_start; irq < irq_end; irq++) {+ set_irq_chip(irq, NULL);+ set_irq_handler(irq, NULL);+ set_irq_flags(irq, 0);+ }+ set_irq_chained_handler(ei->chained_irq, NULL);+ device_init_wakeup(&pdev->dev, 0);+ }+ iounmap(ei->base_addr);+ kfree(ei->chip);+ kfree(ei);++ return 0;+}++#ifdef CONFIG_PM+static int egpio_suspend(struct platform_device *pdev, pm_message_t state)+{+ struct egpio_info *ei = platform_get_drvdata(pdev);++ if (ei->chained_irq && device_may_wakeup(&pdev->dev))+ enable_irq_wake(ei->chained_irq);+ return 0;+}++static int egpio_resume(struct platform_device *pdev)+{+ struct egpio_info *ei = platform_get_drvdata(pdev);++ if (ei->chained_irq && device_may_wakeup(&pdev->dev))+ disable_irq_wake(ei->chained_irq);++ /* Update registers from the cache, in case+ the CPLD was powered off during suspend */+ egpio_write_cache(ei);+ return 0;+}+#else+#define egpio_suspend NULL+#define egpio_resume NULL+#endif+++static struct platform_driver egpio_driver = {+ .driver = {+ .name = "htc-egpio",+ },+ .remove = egpio_remove,+ .suspend = egpio_suspend,+ .resume = egpio_resume,+};++static int __init egpio_init(void)+{+ return platform_driver_probe(&egpio_driver, egpio_probe);+}++static void __exit egpio_exit(void)+{+ platform_driver_unregister(&egpio_driver);+}++/* start early for dependencies */+subsys_initcall(egpio_init);+module_exit(egpio_exit)++MODULE_LICENSE("GPL");+MODULE_AUTHOR("Kevin O'Connor <kevin@koconnor.net>");diff --git a/include/linux/mfd/htc-egpio.h b/include/linux/mfd/htc-egpio.hnew file mode 100644index 0000000..e13098f--- /dev/null+++ b/include/linux/mfd/htc-egpio.h@@ -0,0 +1,57 @@+/*+ * HTC simple EGPIO irq and gpio extender+ */++#ifndef __HTC_EGPIO_H__+#define __HTC_EGPIO_H__++#include <asm/gpio.h>++/* Descriptive values for all-in or all-out htc_egpio_chip descriptors. */+#define HTC_EGPIO_OUTPUT (~0)+#define HTC_EGPIO_INPUT 0++/**+ * struct htc_egpio_chip - descriptor to create gpio_chip for register range+ * @reg_start: index of first register+ * @gpio_base: gpio number of first pin in this register range+ * @num_gpios: number of gpios in this register range, max BITS_PER_LONG+ * (number of registers = DIV_ROUND_UP(num_gpios, reg_width))+ * @direction: bitfield, '0' = input, '1' = output,+ */+struct htc_egpio_chip {+ int reg_start;+ int gpio_base;+ int num_gpios;+ unsigned long direction;+ unsigned long initial_values;+};++/**+ * struct htc_egpio_platform_data - description provided by the arch+ * @irq_base: beginning of available IRQs (eg, IRQ_BOARD_START)+ * @num_irqs: number of irqs+ * @reg_width: number of bits per register, either 8 or 16 bit+ * @bus_width: alignment of the registers, either 16 or 32 bit+ * @invert_acks: set if chip requires writing '0' to ack an irq, instead of '1'+ * @ack_register: location of the irq/ack register+ * @chip: pointer to array of htc_egpio_chip descriptors+ * @num_chips: number of egpio chip descriptors+ */+struct htc_egpio_platform_data {+ int bus_width;+ int reg_width;++ int irq_base;+ int num_irqs;+ int invert_acks;+ int ack_register;++ struct htc_egpio_chip *chip;+ int num_chips;+};++/* Determine the wakeup irq, to be called during early resume */+extern int htc_egpio_get_wakeup_irq(struct device *dev);++#endif