diff options
author | Russell King <rmk+kernel@arm.linux.org.uk> | 2011-12-20 02:00:22 +0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2012-01-26 23:57:24 +0400 |
commit | d9dc878769f521f494d1617d7cd0c92073df75d8 (patch) | |
tree | 79e79c50c211af91e48f38a7beed61e7425495bc /drivers/pcmcia/soc_common.c | |
parent | e0d21178ceb06f5bfa81a5697f68384f74af054a (diff) | |
download | linux-d9dc878769f521f494d1617d7cd0c92073df75d8.tar.xz |
PCMCIA: soc_common: add GPIO support for card status signals
Add GPIO support for reading the card status (card detect, ready,
battery voltage detect) signals into soc_common code. As we want
interrupts from these GPIOs, this takes over the old irq handling
infrastructure for card status signals, which will now be managed
entirely by the soc_common code.
Acked-by: Dominik Brodowski <linux@dominikbrodowski.net>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'drivers/pcmcia/soc_common.c')
-rw-r--r-- | drivers/pcmcia/soc_common.c | 120 |
1 files changed, 117 insertions, 3 deletions
diff --git a/drivers/pcmcia/soc_common.c b/drivers/pcmcia/soc_common.c index 84d90d5220ce..09a9a52ad650 100644 --- a/drivers/pcmcia/soc_common.c +++ b/drivers/pcmcia/soc_common.c @@ -32,6 +32,7 @@ #include <linux/cpufreq.h> +#include <linux/gpio.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/io.h> @@ -49,6 +50,8 @@ #include "soc_common.h" +static irqreturn_t soc_common_pcmcia_interrupt(int irq, void *dev); + #ifdef CONFIG_PCMCIA_DEBUG static int pc_debug; @@ -104,6 +107,93 @@ void soc_common_pcmcia_get_timing(struct soc_pcmcia_socket *skt, } EXPORT_SYMBOL(soc_common_pcmcia_get_timing); +static void __soc_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt, + unsigned int nr) +{ + unsigned int i; + + for (i = 0; i < nr; i++) { + if (skt->stat[i].irq) + free_irq(skt->stat[i].irq, skt); + if (gpio_is_valid(skt->stat[i].gpio)) + gpio_free(skt->stat[i].gpio); + } + + if (skt->ops->hw_shutdown) + skt->ops->hw_shutdown(skt); +} + +static void soc_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) +{ + __soc_pcmcia_hw_shutdown(skt, ARRAY_SIZE(skt->stat)); +} + +static int soc_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + int ret = 0, i; + + if (skt->ops->hw_init) { + ret = skt->ops->hw_init(skt); + if (ret) + return ret; + } + + for (i = 0; i < ARRAY_SIZE(skt->stat); i++) { + if (gpio_is_valid(skt->stat[i].gpio)) { + int irq; + + ret = gpio_request_one(skt->stat[i].gpio, GPIOF_IN, + skt->stat[i].name); + if (ret) { + __soc_pcmcia_hw_shutdown(skt, i); + return ret; + } + + irq = gpio_to_irq(skt->stat[i].gpio); + + if (i == SOC_STAT_RDY) + skt->socket.pci_irq = irq; + else + skt->stat[i].irq = irq; + } + + if (skt->stat[i].irq) { + ret = request_irq(skt->stat[i].irq, + soc_common_pcmcia_interrupt, + IRQF_TRIGGER_NONE, + skt->stat[i].name, skt); + if (ret) { + if (gpio_is_valid(skt->stat[i].gpio)) + gpio_free(skt->stat[i].gpio); + __soc_pcmcia_hw_shutdown(skt, i); + return ret; + } + } + } + + return ret; +} + +static void soc_pcmcia_hw_enable(struct soc_pcmcia_socket *skt) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(skt->stat); i++) + if (skt->stat[i].irq) { + irq_set_irq_type(skt->stat[i].irq, IRQ_TYPE_EDGE_RISING); + irq_set_irq_type(skt->stat[i].irq, IRQ_TYPE_EDGE_BOTH); + } +} + +static void soc_pcmcia_hw_disable(struct soc_pcmcia_socket *skt) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(skt->stat); i++) + if (skt->stat[i].irq) + irq_set_irq_type(skt->stat[i].irq, IRQ_TYPE_NONE); +} + static unsigned int soc_common_pcmcia_skt_state(struct soc_pcmcia_socket *skt) { struct pcmcia_state state; @@ -111,6 +201,22 @@ static unsigned int soc_common_pcmcia_skt_state(struct soc_pcmcia_socket *skt) memset(&state, 0, sizeof(struct pcmcia_state)); + /* Make battery voltage state report 'good' */ + state.bvd1 = 1; + state.bvd2 = 1; + + /* CD is active low by default */ + if (gpio_is_valid(skt->stat[SOC_STAT_CD].gpio)) + state.detect = !gpio_get_value(skt->stat[SOC_STAT_CD].gpio); + + /* RDY and BVD are active high by default */ + if (gpio_is_valid(skt->stat[SOC_STAT_RDY].gpio)) + state.ready = !!gpio_get_value(skt->stat[SOC_STAT_RDY].gpio); + if (gpio_is_valid(skt->stat[SOC_STAT_BVD1].gpio)) + state.bvd1 = !!gpio_get_value(skt->stat[SOC_STAT_BVD1].gpio); + if (gpio_is_valid(skt->stat[SOC_STAT_BVD2].gpio)) + state.bvd2 = !!gpio_get_value(skt->stat[SOC_STAT_BVD2].gpio); + skt->ops->socket_state(skt, &state); stat = state.detect ? SS_DETECT : 0; @@ -188,6 +294,7 @@ static int soc_common_pcmcia_sock_init(struct pcmcia_socket *sock) debug(skt, 2, "initializing socket\n"); if (skt->ops->socket_init) skt->ops->socket_init(skt); + soc_pcmcia_hw_enable(skt); return 0; } @@ -207,6 +314,7 @@ static int soc_common_pcmcia_suspend(struct pcmcia_socket *sock) debug(skt, 2, "suspending socket\n"); + soc_pcmcia_hw_disable(skt); if (skt->ops->socket_suspend) skt->ops->socket_suspend(skt); @@ -638,10 +746,15 @@ module_exit(soc_pcmcia_cpufreq_unregister); void soc_pcmcia_init_one(struct soc_pcmcia_socket *skt, struct pcmcia_low_level *ops, struct device *dev) { + int i; + skt->ops = ops; skt->socket.owner = ops->owner; skt->socket.dev.parent = dev; skt->socket.pci_irq = NO_IRQ; + + for (i = 0; i < ARRAY_SIZE(skt->stat); i++) + skt->stat[i].gpio = -EINVAL; } EXPORT_SYMBOL(soc_pcmcia_init_one); @@ -652,8 +765,9 @@ void soc_pcmcia_remove_one(struct soc_pcmcia_socket *skt) pcmcia_unregister_socket(&skt->socket); - skt->ops->hw_shutdown(skt); + soc_pcmcia_hw_shutdown(skt); + /* should not be required; violates some lowlevel drivers */ soc_common_pcmcia_config_skt(skt, &dead_socket); list_del(&skt->node); @@ -710,7 +824,7 @@ int soc_pcmcia_add_one(struct soc_pcmcia_socket *skt) */ skt->ops->set_timing(skt); - ret = skt->ops->hw_init(skt); + ret = soc_pcmcia_hw_init(skt); if (ret) goto out_err_6; @@ -743,7 +857,7 @@ int soc_pcmcia_add_one(struct soc_pcmcia_socket *skt) pcmcia_unregister_socket(&skt->socket); out_err_7: - skt->ops->hw_shutdown(skt); + soc_pcmcia_hw_shutdown(skt); out_err_6: list_del(&skt->node); mutex_unlock(&soc_pcmcia_sockets_lock); |