diff options
Diffstat (limited to 'drivers/gpio/gpio-sodaville.c')
| -rw-r--r-- | drivers/gpio/gpio-sodaville.c | 302 | 
1 files changed, 302 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-sodaville.c b/drivers/gpio/gpio-sodaville.c new file mode 100644 index 00000000000..9ba15d31d24 --- /dev/null +++ b/drivers/gpio/gpio-sodaville.c @@ -0,0 +1,302 @@ +/* + *  GPIO interface for Intel Sodaville SoCs. + * + *  Copyright (c) 2010, 2011 Intel Corporation + * + *  This program is free software; you can redistribute it and/or modify + *  it under the terms of the GNU General Public License 2 as published + *  by the Free Software Foundation. + * + */ + +#include <linux/errno.h> +#include <linux/gpio.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/of_irq.h> +#include <linux/basic_mmio_gpio.h> + +#define DRV_NAME		"sdv_gpio" +#define SDV_NUM_PUB_GPIOS	12 +#define PCI_DEVICE_ID_SDV_GPIO	0x2e67 +#define GPIO_BAR		0 + +#define GPOUTR		0x00 +#define GPOER		0x04 +#define GPINR		0x08 + +#define GPSTR		0x0c +#define GPIT1R0		0x10 +#define GPIO_INT	0x14 +#define GPIT1R1		0x18 + +#define GPMUXCTL	0x1c + +struct sdv_gpio_chip_data { +	int irq_base; +	void __iomem *gpio_pub_base; +	struct irq_domain id; +	struct irq_chip_generic *gc; +	struct bgpio_chip bgpio; +}; + +static int sdv_gpio_pub_set_type(struct irq_data *d, unsigned int type) +{ +	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); +	struct sdv_gpio_chip_data *sd = gc->private; +	void __iomem *type_reg; +	u32 irq_offs = d->irq - sd->irq_base; +	u32 reg; + +	if (irq_offs < 8) +		type_reg = sd->gpio_pub_base + GPIT1R0; +	else +		type_reg = sd->gpio_pub_base + GPIT1R1; + +	reg = readl(type_reg); + +	switch (type) { +	case IRQ_TYPE_LEVEL_HIGH: +		reg &= ~BIT(4 * (irq_offs % 8)); +		break; + +	case IRQ_TYPE_LEVEL_LOW: +		reg |= BIT(4 * (irq_offs % 8)); +		break; + +	default: +		return -EINVAL; +	} + +	writel(reg, type_reg); +	return 0; +} + +static irqreturn_t sdv_gpio_pub_irq_handler(int irq, void *data) +{ +	struct sdv_gpio_chip_data *sd = data; +	u32 irq_stat = readl(sd->gpio_pub_base + GPSTR); + +	irq_stat &= readl(sd->gpio_pub_base + GPIO_INT); +	if (!irq_stat) +		return IRQ_NONE; + +	while (irq_stat) { +		u32 irq_bit = __fls(irq_stat); + +		irq_stat &= ~BIT(irq_bit); +		generic_handle_irq(sd->irq_base + irq_bit); +	} + +	return IRQ_HANDLED; +} + +static int sdv_xlate(struct irq_domain *h, struct device_node *node, +		const u32 *intspec, u32 intsize, irq_hw_number_t *out_hwirq, +		u32 *out_type) +{ +	u32 line, type; + +	if (node != h->of_node) +		return -EINVAL; + +	if (intsize < 2) +		return -EINVAL; + +	line = *intspec; +	*out_hwirq = line; + +	intspec++; +	type = *intspec; + +	switch (type) { +	case IRQ_TYPE_LEVEL_LOW: +	case IRQ_TYPE_LEVEL_HIGH: +		*out_type = type; +		break; +	default: +		return -EINVAL; +	} +	return 0; +} + +static struct irq_domain_ops irq_domain_sdv_ops = { +	.dt_translate	= sdv_xlate, +}; + +static __devinit int sdv_register_irqsupport(struct sdv_gpio_chip_data *sd, +		struct pci_dev *pdev) +{ +	struct irq_chip_type *ct; +	int ret; + +	sd->irq_base = irq_alloc_descs(-1, 0, SDV_NUM_PUB_GPIOS, -1); +	if (sd->irq_base < 0) +		return sd->irq_base; + +	/* mask + ACK all interrupt sources */ +	writel(0, sd->gpio_pub_base + GPIO_INT); +	writel((1 << 11) - 1, sd->gpio_pub_base + GPSTR); + +	ret = request_irq(pdev->irq, sdv_gpio_pub_irq_handler, IRQF_SHARED, +			"sdv_gpio", sd); +	if (ret) +		goto out_free_desc; + +	sd->id.irq_base = sd->irq_base; +	sd->id.of_node = of_node_get(pdev->dev.of_node); +	sd->id.ops = &irq_domain_sdv_ops; + +	/* +	 * This gpio irq controller latches level irqs. Testing shows that if +	 * we unmask & ACK the IRQ before the source of the interrupt is gone +	 * then the interrupt is active again. +	 */ +	sd->gc = irq_alloc_generic_chip("sdv-gpio", 1, sd->irq_base, +			sd->gpio_pub_base, handle_fasteoi_irq); +	if (!sd->gc) { +		ret = -ENOMEM; +		goto out_free_irq; +	} + +	sd->gc->private = sd; +	ct = sd->gc->chip_types; +	ct->type = IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW; +	ct->regs.eoi = GPSTR; +	ct->regs.mask = GPIO_INT; +	ct->chip.irq_mask = irq_gc_mask_clr_bit; +	ct->chip.irq_unmask = irq_gc_mask_set_bit; +	ct->chip.irq_eoi = irq_gc_eoi; +	ct->chip.irq_set_type = sdv_gpio_pub_set_type; + +	irq_setup_generic_chip(sd->gc, IRQ_MSK(SDV_NUM_PUB_GPIOS), +			IRQ_GC_INIT_MASK_CACHE, IRQ_NOREQUEST, +			IRQ_LEVEL | IRQ_NOPROBE); + +	irq_domain_add(&sd->id); +	return 0; +out_free_irq: +	free_irq(pdev->irq, sd); +out_free_desc: +	irq_free_descs(sd->irq_base, SDV_NUM_PUB_GPIOS); +	return ret; +} + +static int __devinit sdv_gpio_probe(struct pci_dev *pdev, +					const struct pci_device_id *pci_id) +{ +	struct sdv_gpio_chip_data *sd; +	unsigned long addr; +	const void *prop; +	int len; +	int ret; +	u32 mux_val; + +	sd = kzalloc(sizeof(struct sdv_gpio_chip_data), GFP_KERNEL); +	if (!sd) +		return -ENOMEM; +	ret = pci_enable_device(pdev); +	if (ret) { +		dev_err(&pdev->dev, "can't enable device.\n"); +		goto done; +	} + +	ret = pci_request_region(pdev, GPIO_BAR, DRV_NAME); +	if (ret) { +		dev_err(&pdev->dev, "can't alloc PCI BAR #%d\n", GPIO_BAR); +		goto disable_pci; +	} + +	addr = pci_resource_start(pdev, GPIO_BAR); +	if (!addr) +		goto release_reg; +	sd->gpio_pub_base = ioremap(addr, pci_resource_len(pdev, GPIO_BAR)); + +	prop = of_get_property(pdev->dev.of_node, "intel,muxctl", &len); +	if (prop && len == 4) { +		mux_val = of_read_number(prop, 1); +		writel(mux_val, sd->gpio_pub_base + GPMUXCTL); +	} + +	ret = bgpio_init(&sd->bgpio, &pdev->dev, 4, +			sd->gpio_pub_base + GPINR, sd->gpio_pub_base + GPOUTR, +			NULL, sd->gpio_pub_base + GPOER, NULL, false); +	if (ret) +		goto unmap; +	sd->bgpio.gc.ngpio = SDV_NUM_PUB_GPIOS; + +	ret = gpiochip_add(&sd->bgpio.gc); +	if (ret < 0) { +		dev_err(&pdev->dev, "gpiochip_add() failed.\n"); +		goto unmap; +	} + +	ret = sdv_register_irqsupport(sd, pdev); +	if (ret) +		goto unmap; + +	pci_set_drvdata(pdev, sd); +	dev_info(&pdev->dev, "Sodaville GPIO driver registered.\n"); +	return 0; + +unmap: +	iounmap(sd->gpio_pub_base); +release_reg: +	pci_release_region(pdev, GPIO_BAR); +disable_pci: +	pci_disable_device(pdev); +done: +	kfree(sd); +	return ret; +} + +static void sdv_gpio_remove(struct pci_dev *pdev) +{ +	struct sdv_gpio_chip_data *sd = pci_get_drvdata(pdev); + +	irq_domain_del(&sd->id); +	free_irq(pdev->irq, sd); +	irq_free_descs(sd->irq_base, SDV_NUM_PUB_GPIOS); + +	if (gpiochip_remove(&sd->bgpio.gc)) +		dev_err(&pdev->dev, "gpiochip_remove() failed.\n"); + +	pci_release_region(pdev, GPIO_BAR); +	iounmap(sd->gpio_pub_base); +	pci_disable_device(pdev); +	kfree(sd); +} + +static struct pci_device_id sdv_gpio_pci_ids[] __devinitdata = { +	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_SDV_GPIO) }, +	{ 0, }, +}; + +static struct pci_driver sdv_gpio_driver = { +	.name = DRV_NAME, +	.id_table = sdv_gpio_pci_ids, +	.probe = sdv_gpio_probe, +	.remove = sdv_gpio_remove, +}; + +static int __init sdv_gpio_init(void) +{ +	return pci_register_driver(&sdv_gpio_driver); +} +module_init(sdv_gpio_init); + +static void __exit sdv_gpio_exit(void) +{ +	pci_unregister_driver(&sdv_gpio_driver); +} +module_exit(sdv_gpio_exit); + +MODULE_AUTHOR("Hans J. Koch <hjk@linutronix.de>"); +MODULE_DESCRIPTION("GPIO interface for Intel Sodaville SoCs"); +MODULE_LICENSE("GPL v2");  |