diff options
| author | Grant Likely <grant.likely@secretlab.ca> | 2011-06-04 18:38:28 -0600 | 
|---|---|---|
| committer | Grant Likely <grant.likely@secretlab.ca> | 2011-06-06 10:10:11 -0600 | 
| commit | c103de240439dfee24ac50eb99c8be3a30d13323 (patch) | |
| tree | 014eeda779510d7d3dfabd1183ce7f1a288d367b /drivers/gpio/gpio-cs5535.c | |
| parent | 8c31b1635b91e48f867e010cd7bcd06393e5858a (diff) | |
| download | olio-linux-3.10-c103de240439dfee24ac50eb99c8be3a30d13323.tar.xz olio-linux-3.10-c103de240439dfee24ac50eb99c8be3a30d13323.zip  | |
gpio: reorganize drivers
Sort the gpio makefile and enforce the naming convention gpio-*.c for
gpio drivers.
v2: cleaned up filenames in Kconfig and comment blocks
v3: fixup use of BASIC_MMIO to GENERIC_GPIO for mxc
Signed-off-by: Grant Likely <grant.likely@secretlab.ca>
Diffstat (limited to 'drivers/gpio/gpio-cs5535.c')
| -rw-r--r-- | drivers/gpio/gpio-cs5535.c | 401 | 
1 files changed, 401 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-cs5535.c b/drivers/gpio/gpio-cs5535.c new file mode 100644 index 00000000000..6e16cba56ad --- /dev/null +++ b/drivers/gpio/gpio-cs5535.c @@ -0,0 +1,401 @@ +/* + * AMD CS5535/CS5536 GPIO driver + * Copyright (C) 2006  Advanced Micro Devices, Inc. + * Copyright (C) 2007-2009  Andres Salomon <dilinger@collabora.co.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/io.h> +#include <linux/cs5535.h> +#include <asm/msr.h> + +#define DRV_NAME "cs5535-gpio" + +/* + * Some GPIO pins + *  31-29,23 : reserved (always mask out) + *  28       : Power Button + *  26       : PME# + *  22-16    : LPC + *  14,15    : SMBus + *  9,8      : UART1 + *  7        : PCI INTB + *  3,4      : UART2/DDC + *  2        : IDE_IRQ0 + *  1        : AC_BEEP + *  0        : PCI INTA + * + * If a mask was not specified, allow all except + * reserved and Power Button + */ +#define GPIO_DEFAULT_MASK 0x0F7FFFFF + +static ulong mask = GPIO_DEFAULT_MASK; +module_param_named(mask, mask, ulong, 0444); +MODULE_PARM_DESC(mask, "GPIO channel mask."); + +static struct cs5535_gpio_chip { +	struct gpio_chip chip; +	resource_size_t base; + +	struct platform_device *pdev; +	spinlock_t lock; +} cs5535_gpio_chip; + +/* + * The CS5535/CS5536 GPIOs support a number of extra features not defined + * by the gpio_chip API, so these are exported.  For a full list of the + * registers, see include/linux/cs5535.h. + */ + +static void errata_outl(struct cs5535_gpio_chip *chip, u32 val, +		unsigned int reg) +{ +	unsigned long addr = chip->base + 0x80 + reg; + +	/* +	 * According to the CS5536 errata (#36), after suspend +	 * a write to the high bank GPIO register will clear all +	 * non-selected bits; the recommended workaround is a +	 * read-modify-write operation. +	 * +	 * Don't apply this errata to the edge status GPIOs, as writing +	 * to their lower bits will clear them. +	 */ +	if (reg != GPIO_POSITIVE_EDGE_STS && reg != GPIO_NEGATIVE_EDGE_STS) { +		if (val & 0xffff) +			val |= (inl(addr) & 0xffff); /* ignore the high bits */ +		else +			val |= (inl(addr) ^ (val >> 16)); +	} +	outl(val, addr); +} + +static void __cs5535_gpio_set(struct cs5535_gpio_chip *chip, unsigned offset, +		unsigned int reg) +{ +	if (offset < 16) +		/* low bank register */ +		outl(1 << offset, chip->base + reg); +	else +		/* high bank register */ +		errata_outl(chip, 1 << (offset - 16), reg); +} + +void cs5535_gpio_set(unsigned offset, unsigned int reg) +{ +	struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; +	unsigned long flags; + +	spin_lock_irqsave(&chip->lock, flags); +	__cs5535_gpio_set(chip, offset, reg); +	spin_unlock_irqrestore(&chip->lock, flags); +} +EXPORT_SYMBOL_GPL(cs5535_gpio_set); + +static void __cs5535_gpio_clear(struct cs5535_gpio_chip *chip, unsigned offset, +		unsigned int reg) +{ +	if (offset < 16) +		/* low bank register */ +		outl(1 << (offset + 16), chip->base + reg); +	else +		/* high bank register */ +		errata_outl(chip, 1 << offset, reg); +} + +void cs5535_gpio_clear(unsigned offset, unsigned int reg) +{ +	struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; +	unsigned long flags; + +	spin_lock_irqsave(&chip->lock, flags); +	__cs5535_gpio_clear(chip, offset, reg); +	spin_unlock_irqrestore(&chip->lock, flags); +} +EXPORT_SYMBOL_GPL(cs5535_gpio_clear); + +int cs5535_gpio_isset(unsigned offset, unsigned int reg) +{ +	struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; +	unsigned long flags; +	long val; + +	spin_lock_irqsave(&chip->lock, flags); +	if (offset < 16) +		/* low bank register */ +		val = inl(chip->base + reg); +	else { +		/* high bank register */ +		val = inl(chip->base + 0x80 + reg); +		offset -= 16; +	} +	spin_unlock_irqrestore(&chip->lock, flags); + +	return (val & (1 << offset)) ? 1 : 0; +} +EXPORT_SYMBOL_GPL(cs5535_gpio_isset); + +int cs5535_gpio_set_irq(unsigned group, unsigned irq) +{ +	uint32_t lo, hi; + +	if (group > 7 || irq > 15) +		return -EINVAL; + +	rdmsr(MSR_PIC_ZSEL_HIGH, lo, hi); + +	lo &= ~(0xF << (group * 4)); +	lo |= (irq & 0xF) << (group * 4); + +	wrmsr(MSR_PIC_ZSEL_HIGH, lo, hi); +	return 0; +} +EXPORT_SYMBOL_GPL(cs5535_gpio_set_irq); + +void cs5535_gpio_setup_event(unsigned offset, int pair, int pme) +{ +	struct cs5535_gpio_chip *chip = &cs5535_gpio_chip; +	uint32_t shift = (offset % 8) * 4; +	unsigned long flags; +	uint32_t val; + +	if (offset >= 24) +		offset = GPIO_MAP_W; +	else if (offset >= 16) +		offset = GPIO_MAP_Z; +	else if (offset >= 8) +		offset = GPIO_MAP_Y; +	else +		offset = GPIO_MAP_X; + +	spin_lock_irqsave(&chip->lock, flags); +	val = inl(chip->base + offset); + +	/* Clear whatever was there before */ +	val &= ~(0xF << shift); + +	/* Set the new value */ +	val |= ((pair & 7) << shift); + +	/* Set the PME bit if this is a PME event */ +	if (pme) +		val |= (1 << (shift + 3)); + +	outl(val, chip->base + offset); +	spin_unlock_irqrestore(&chip->lock, flags); +} +EXPORT_SYMBOL_GPL(cs5535_gpio_setup_event); + +/* + * Generic gpio_chip API support. + */ + +static int chip_gpio_request(struct gpio_chip *c, unsigned offset) +{ +	struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c; +	unsigned long flags; + +	spin_lock_irqsave(&chip->lock, flags); + +	/* check if this pin is available */ +	if ((mask & (1 << offset)) == 0) { +		dev_info(&chip->pdev->dev, +			"pin %u is not available (check mask)\n", offset); +		spin_unlock_irqrestore(&chip->lock, flags); +		return -EINVAL; +	} + +	/* disable output aux 1 & 2 on this pin */ +	__cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_AUX1); +	__cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_AUX2); + +	/* disable input aux 1 on this pin */ +	__cs5535_gpio_clear(chip, offset, GPIO_INPUT_AUX1); + +	spin_unlock_irqrestore(&chip->lock, flags); + +	return 0; +} + +static int chip_gpio_get(struct gpio_chip *chip, unsigned offset) +{ +	return cs5535_gpio_isset(offset, GPIO_READ_BACK); +} + +static void chip_gpio_set(struct gpio_chip *chip, unsigned offset, int val) +{ +	if (val) +		cs5535_gpio_set(offset, GPIO_OUTPUT_VAL); +	else +		cs5535_gpio_clear(offset, GPIO_OUTPUT_VAL); +} + +static int chip_direction_input(struct gpio_chip *c, unsigned offset) +{ +	struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c; +	unsigned long flags; + +	spin_lock_irqsave(&chip->lock, flags); +	__cs5535_gpio_set(chip, offset, GPIO_INPUT_ENABLE); +	__cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_ENABLE); +	spin_unlock_irqrestore(&chip->lock, flags); + +	return 0; +} + +static int chip_direction_output(struct gpio_chip *c, unsigned offset, int val) +{ +	struct cs5535_gpio_chip *chip = (struct cs5535_gpio_chip *) c; +	unsigned long flags; + +	spin_lock_irqsave(&chip->lock, flags); + +	__cs5535_gpio_set(chip, offset, GPIO_INPUT_ENABLE); +	__cs5535_gpio_set(chip, offset, GPIO_OUTPUT_ENABLE); +	if (val) +		__cs5535_gpio_set(chip, offset, GPIO_OUTPUT_VAL); +	else +		__cs5535_gpio_clear(chip, offset, GPIO_OUTPUT_VAL); + +	spin_unlock_irqrestore(&chip->lock, flags); + +	return 0; +} + +static const char * const cs5535_gpio_names[] = { +	"GPIO0", "GPIO1", "GPIO2", "GPIO3", +	"GPIO4", "GPIO5", "GPIO6", "GPIO7", +	"GPIO8", "GPIO9", "GPIO10", "GPIO11", +	"GPIO12", "GPIO13", "GPIO14", "GPIO15", +	"GPIO16", "GPIO17", "GPIO18", "GPIO19", +	"GPIO20", "GPIO21", "GPIO22", NULL, +	"GPIO24", "GPIO25", "GPIO26", "GPIO27", +	"GPIO28", NULL, NULL, NULL, +}; + +static struct cs5535_gpio_chip cs5535_gpio_chip = { +	.chip = { +		.owner = THIS_MODULE, +		.label = DRV_NAME, + +		.base = 0, +		.ngpio = 32, +		.names = cs5535_gpio_names, +		.request = chip_gpio_request, + +		.get = chip_gpio_get, +		.set = chip_gpio_set, + +		.direction_input = chip_direction_input, +		.direction_output = chip_direction_output, +	}, +}; + +static int __devinit cs5535_gpio_probe(struct platform_device *pdev) +{ +	struct resource *res; +	int err = -EIO; +	ulong mask_orig = mask; + +	/* There are two ways to get the GPIO base address; one is by +	 * fetching it from MSR_LBAR_GPIO, the other is by reading the +	 * PCI BAR info.  The latter method is easier (especially across +	 * different architectures), so we'll stick with that for now.  If +	 * it turns out to be unreliable in the face of crappy BIOSes, we +	 * can always go back to using MSRs.. */ + +	res = platform_get_resource(pdev, IORESOURCE_IO, 0); +	if (!res) { +		dev_err(&pdev->dev, "can't fetch device resource info\n"); +		goto done; +	} + +	if (!request_region(res->start, resource_size(res), pdev->name)) { +		dev_err(&pdev->dev, "can't request region\n"); +		goto done; +	} + +	/* set up the driver-specific struct */ +	cs5535_gpio_chip.base = res->start; +	cs5535_gpio_chip.pdev = pdev; +	spin_lock_init(&cs5535_gpio_chip.lock); + +	dev_info(&pdev->dev, "reserved resource region %pR\n", res); + +	/* mask out reserved pins */ +	mask &= 0x1F7FFFFF; + +	/* do not allow pin 28, Power Button, as there's special handling +	 * in the PMC needed. (note 12, p. 48) */ +	mask &= ~(1 << 28); + +	if (mask_orig != mask) +		dev_info(&pdev->dev, "mask changed from 0x%08lX to 0x%08lX\n", +				mask_orig, mask); + +	/* finally, register with the generic GPIO API */ +	err = gpiochip_add(&cs5535_gpio_chip.chip); +	if (err) +		goto release_region; + +	dev_info(&pdev->dev, "GPIO support successfully loaded.\n"); +	return 0; + +release_region: +	release_region(res->start, resource_size(res)); +done: +	return err; +} + +static int __devexit cs5535_gpio_remove(struct platform_device *pdev) +{ +	struct resource *r; +	int err; + +	err = gpiochip_remove(&cs5535_gpio_chip.chip); +	if (err) { +		/* uhh? */ +		dev_err(&pdev->dev, "unable to remove gpio_chip?\n"); +		return err; +	} + +	r = platform_get_resource(pdev, IORESOURCE_IO, 0); +	release_region(r->start, resource_size(r)); +	return 0; +} + +static struct platform_driver cs5535_gpio_driver = { +	.driver = { +		.name = DRV_NAME, +		.owner = THIS_MODULE, +	}, +	.probe = cs5535_gpio_probe, +	.remove = __devexit_p(cs5535_gpio_remove), +}; + +static int __init cs5535_gpio_init(void) +{ +	return platform_driver_register(&cs5535_gpio_driver); +} + +static void __exit cs5535_gpio_exit(void) +{ +	platform_driver_unregister(&cs5535_gpio_driver); +} + +module_init(cs5535_gpio_init); +module_exit(cs5535_gpio_exit); + +MODULE_AUTHOR("Andres Salomon <dilinger@queued.net>"); +MODULE_DESCRIPTION("AMD CS5535/CS5536 GPIO driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME);  |