/* Supports:
* Moorestown platform Langwell chip.
* Medfield platform Penwell chip.
+ * Whitney point.
*/
#include <linux/module.h>
#include <linux/pci.h>
+#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/stddef.h>
#include <linux/io.h>
#include <linux/gpio.h>
#include <linux/slab.h>
+#include <linux/pm_runtime.h>
/*
* Langwell chip has 64 pins and thus there are 2 32bit registers to control
void *reg_base;
spinlock_t lock;
unsigned irq_base;
+ struct pci_dev *pdev;
};
static void __iomem *gpio_reg(struct gpio_chip *chip, unsigned offset,
u32 value;
unsigned long flags;
+ if (lnw->pdev)
+ pm_runtime_get(&lnw->pdev->dev);
+
spin_lock_irqsave(&lnw->lock, flags);
value = readl(gpdr);
value &= ~BIT(offset % 32);
writel(value, gpdr);
spin_unlock_irqrestore(&lnw->lock, flags);
+
+ if (lnw->pdev)
+ pm_runtime_put(&lnw->pdev->dev);
+
return 0;
}
unsigned long flags;
lnw_gpio_set(chip, offset, value);
+
+ if (lnw->pdev)
+ pm_runtime_get(&lnw->pdev->dev);
+
spin_lock_irqsave(&lnw->lock, flags);
value = readl(gpdr);
- value |= BIT(offset % 32);;
+ value |= BIT(offset % 32);
writel(value, gpdr);
spin_unlock_irqrestore(&lnw->lock, flags);
+
+ if (lnw->pdev)
+ pm_runtime_put(&lnw->pdev->dev);
+
return 0;
}
return lnw->irq_base + offset;
}
-static int lnw_irq_type(unsigned irq, unsigned type)
+static int lnw_irq_type(struct irq_data *d, unsigned type)
{
- struct lnw_gpio *lnw = get_irq_chip_data(irq);
- u32 gpio = irq - lnw->irq_base;
+ struct lnw_gpio *lnw = irq_data_get_irq_chip_data(d);
+ u32 gpio = d->irq - lnw->irq_base;
unsigned long flags;
u32 value;
void __iomem *grer = gpio_reg(&lnw->chip, gpio, GRER);
if (gpio >= lnw->chip.ngpio)
return -EINVAL;
+
+ if (lnw->pdev)
+ pm_runtime_get(&lnw->pdev->dev);
+
spin_lock_irqsave(&lnw->lock, flags);
if (type & IRQ_TYPE_EDGE_RISING)
value = readl(grer) | BIT(gpio % 32);
writel(value, gfer);
spin_unlock_irqrestore(&lnw->lock, flags);
+ if (lnw->pdev)
+ pm_runtime_put(&lnw->pdev->dev);
+
return 0;
-};
+}
-static void lnw_irq_unmask(unsigned irq)
+static void lnw_irq_unmask(struct irq_data *d)
{
-};
+}
-static void lnw_irq_mask(unsigned irq)
+static void lnw_irq_mask(struct irq_data *d)
{
-};
+}
static struct irq_chip lnw_irqchip = {
.name = "LNW-GPIO",
- .mask = lnw_irq_mask,
- .unmask = lnw_irq_unmask,
- .set_type = lnw_irq_type,
+ .irq_mask = lnw_irq_mask,
+ .irq_unmask = lnw_irq_unmask,
+ .irq_set_type = lnw_irq_type,
};
static DEFINE_PCI_DEVICE_TABLE(lnw_gpio_ids) = { /* pin number */
static void lnw_irq_handler(unsigned irq, struct irq_desc *desc)
{
- struct lnw_gpio *lnw = (struct lnw_gpio *)get_irq_data(irq);
- u32 base, gpio;
+ struct irq_data *data = irq_desc_get_irq_data(desc);
+ struct lnw_gpio *lnw = irq_data_get_irq_handler_data(data);
+ struct irq_chip *chip = irq_data_get_irq_chip(data);
+ u32 base, gpio, mask;
+ unsigned long pending;
void __iomem *gedr;
- u32 gedr_v;
/* check GPIO controller to check which pin triggered the interrupt */
for (base = 0; base < lnw->chip.ngpio; base += 32) {
gedr = gpio_reg(&lnw->chip, base, GEDR);
- gedr_v = readl(gedr);
- if (!gedr_v)
- continue;
- for (gpio = base; gpio < base + 32; gpio++)
- if (gedr_v & BIT(gpio % 32)) {
- pr_debug("pin %d triggered\n", gpio);
- generic_handle_irq(lnw->irq_base + gpio);
- }
- /* clear the edge detect status bit */
- writel(gedr_v, gedr);
+ pending = readl(gedr);
+ while (pending) {
+ gpio = __ffs(pending);
+ mask = BIT(gpio);
+ pending &= ~mask;
+ /* Clear before handling so we can't lose an edge */
+ writel(mask, gedr);
+ generic_handle_irq(lnw->irq_base + base + gpio);
+ }
}
- desc->chip->eoi(irq);
+
+ chip->irq_eoi(data);
+}
+
+#ifdef CONFIG_PM
+static int lnw_gpio_runtime_resume(struct device *dev)
+{
+ return 0;
}
+static int lnw_gpio_runtime_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int lnw_gpio_runtime_idle(struct device *dev)
+{
+ int err = pm_schedule_suspend(dev, 500);
+
+ if (!err)
+ return 0;
+
+ return -EBUSY;
+}
+
+#else
+#define lnw_gpio_runtime_suspend NULL
+#define lnw_gpio_runtime_resume NULL
+#define lnw_gpio_runtime_idle NULL
+#endif
+
+static const struct dev_pm_ops lnw_gpio_pm_ops = {
+ .runtime_suspend = lnw_gpio_runtime_suspend,
+ .runtime_resume = lnw_gpio_runtime_resume,
+ .runtime_idle = lnw_gpio_runtime_idle,
+};
+
static int __devinit lnw_gpio_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
lnw->chip.base = gpio_base;
lnw->chip.ngpio = id->driver_data;
lnw->chip.can_sleep = 0;
+ lnw->pdev = pdev;
pci_set_drvdata(pdev, lnw);
retval = gpiochip_add(&lnw->chip);
if (retval) {
dev_err(&pdev->dev, "langwell gpiochip_add error %d\n", retval);
goto err5;
}
- set_irq_data(pdev->irq, lnw);
- set_irq_chained_handler(pdev->irq, lnw_irq_handler);
+ irq_set_handler_data(pdev->irq, lnw);
+ irq_set_chained_handler(pdev->irq, lnw_irq_handler);
for (i = 0; i < lnw->chip.ngpio; i++) {
- set_irq_chip_and_handler_name(i + lnw->irq_base, &lnw_irqchip,
- handle_simple_irq, "demux");
- set_irq_chip_data(i + lnw->irq_base, lnw);
+ irq_set_chip_and_handler_name(i + lnw->irq_base, &lnw_irqchip,
+ handle_simple_irq, "demux");
+ irq_set_chip_data(i + lnw->irq_base, lnw);
}
spin_lock_init(&lnw->lock);
+
+ pm_runtime_put_noidle(&pdev->dev);
+ pm_runtime_allow(&pdev->dev);
+
goto done;
err5:
kfree(lnw);
.name = "langwell_gpio",
.id_table = lnw_gpio_ids,
.probe = lnw_gpio_probe,
+ .driver = {
+ .pm = &lnw_gpio_pm_ops,
+ },
+};
+
+
+static int __devinit wp_gpio_probe(struct platform_device *pdev)
+{
+ struct lnw_gpio *lnw;
+ struct gpio_chip *gc;
+ struct resource *rc;
+ int retval = 0;
+
+ rc = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!rc)
+ return -EINVAL;
+
+ lnw = kzalloc(sizeof(struct lnw_gpio), GFP_KERNEL);
+ if (!lnw) {
+ dev_err(&pdev->dev,
+ "can't allocate whitneypoint_gpio chip data\n");
+ return -ENOMEM;
+ }
+ lnw->reg_base = ioremap_nocache(rc->start, resource_size(rc));
+ if (lnw->reg_base == NULL) {
+ retval = -EINVAL;
+ goto err_kmalloc;
+ }
+ spin_lock_init(&lnw->lock);
+ gc = &lnw->chip;
+ gc->label = dev_name(&pdev->dev);
+ gc->owner = THIS_MODULE;
+ gc->direction_input = lnw_gpio_direction_input;
+ gc->direction_output = lnw_gpio_direction_output;
+ gc->get = lnw_gpio_get;
+ gc->set = lnw_gpio_set;
+ gc->to_irq = NULL;
+ gc->base = 0;
+ gc->ngpio = 64;
+ gc->can_sleep = 0;
+ retval = gpiochip_add(gc);
+ if (retval) {
+ dev_err(&pdev->dev, "whitneypoint gpiochip_add error %d\n",
+ retval);
+ goto err_ioremap;
+ }
+ platform_set_drvdata(pdev, lnw);
+ return 0;
+err_ioremap:
+ iounmap(lnw->reg_base);
+err_kmalloc:
+ kfree(lnw);
+ return retval;
+}
+
+static int __devexit wp_gpio_remove(struct platform_device *pdev)
+{
+ struct lnw_gpio *lnw = platform_get_drvdata(pdev);
+ int err;
+ err = gpiochip_remove(&lnw->chip);
+ if (err)
+ dev_err(&pdev->dev, "failed to remove gpio_chip.\n");
+ iounmap(lnw->reg_base);
+ kfree(lnw);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static struct platform_driver wp_gpio_driver = {
+ .probe = wp_gpio_probe,
+ .remove = __devexit_p(wp_gpio_remove),
+ .driver = {
+ .name = "wp_gpio",
+ .owner = THIS_MODULE,
+ },
};
static int __init lnw_gpio_init(void)
{
- return pci_register_driver(&lnw_gpio_driver);
+ int ret;
+ ret = pci_register_driver(&lnw_gpio_driver);
+ if (ret < 0)
+ return ret;
+ ret = platform_driver_register(&wp_gpio_driver);
+ if (ret < 0)
+ pci_unregister_driver(&lnw_gpio_driver);
+ return ret;
}
device_initcall(lnw_gpio_init);