[RFC PATCH 2/2] MXC: add pcmcia support
Martin Fuzzey
mfuzzey at gmail.com
Tue Oct 27 13:27:02 EDT 2009
Signed-off-by: Martin Fuzzey <mfuzzey at gmail.com>
---
arch/arm/mach-mx2/clock_imx21.c | 1
arch/arm/mach-mx2/clock_imx27.c | 1
arch/arm/mach-mx2/devices.c | 21 +
arch/arm/mach-mx2/devices.h | 1
arch/arm/mach-mx3/clock.c | 1
drivers/pcmcia/Kconfig | 6
drivers/pcmcia/Makefile | 3
drivers/pcmcia/mxc_generic.c | 711 +++++++++++++++++++++++++++++++++++++++
8 files changed, 745 insertions(+), 0 deletions(-)
create mode 100644 drivers/pcmcia/mxc_generic.c
diff --git a/arch/arm/mach-mx2/clock_imx21.c b/arch/arm/mach-mx2/clock_imx21.c
index 801e9d2..e131312 100644
--- a/arch/arm/mach-mx2/clock_imx21.c
+++ b/arch/arm/mach-mx2/clock_imx21.c
@@ -960,6 +960,7 @@ static struct clk_lookup lookups[] = {
_REGISTER_CLOCK("mxc-keypad", NULL, kpp_clk)
_REGISTER_CLOCK(NULL, "owire", owire_clk)
_REGISTER_CLOCK(NULL, "rtc", rtc_clk)
+ _REGISTER_CLOCK("mxc-pcmcia", NULL, hclk_clk)
};
/*
diff --git a/arch/arm/mach-mx2/clock_imx27.c b/arch/arm/mach-mx2/clock_imx27.c
index a3a76cf..ed2fb96 100644
--- a/arch/arm/mach-mx2/clock_imx27.c
+++ b/arch/arm/mach-mx2/clock_imx27.c
@@ -674,6 +674,7 @@ static struct clk_lookup lookups[] = {
_REGISTER_CLOCK("mxc_w1.0", NULL, owire_clk)
_REGISTER_CLOCK(NULL, "rtc", rtc_clk)
_REGISTER_CLOCK(NULL, "scc", scc_clk)
+ _REGISTER_CLOCK("mxc-pcmcia", NULL, ahb_clk)
};
/* Adjust the clock path for TO2 and later */
diff --git a/arch/arm/mach-mx2/devices.c b/arch/arm/mach-mx2/devices.c
index 50199af..906ccb2 100644
--- a/arch/arm/mach-mx2/devices.c
+++ b/arch/arm/mach-mx2/devices.c
@@ -564,3 +564,24 @@ int __init mxc_register_gpios(void)
{
return mxc_gpio_init(imx_gpio_ports, ARRAY_SIZE(imx_gpio_ports));
}
+
+static struct resource mxc_resource_pcmcia[] = {
+ [0] = {
+ .start = PCMCIA_CTL_BASE_ADDR,
+ .end = PCMCIA_CTL_BASE_ADDR + 0x67,
+ .flags = IORESOURCE_MEM,
+ },
+ [1] = {
+ .start = MXC_INT_PCMCIA,
+ .end = MXC_INT_PCMCIA,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+struct platform_device mxc_device_pcmcia = {
+ .name = "mxc-pcmcia",
+ .id = -1,
+ .resource = mxc_resource_pcmcia,
+ .num_resources = ARRAY_SIZE(mxc_resource_pcmcia),
+};
+
diff --git a/arch/arm/mach-mx2/devices.h b/arch/arm/mach-mx2/devices.h
index d315406..9eff953 100644
--- a/arch/arm/mach-mx2/devices.h
+++ b/arch/arm/mach-mx2/devices.h
@@ -26,4 +26,5 @@ extern struct platform_device mxc_usbh2;
extern struct platform_device mxc_spi_device0;
extern struct platform_device mxc_spi_device1;
extern struct platform_device mxc_spi_device2;
+extern struct platform_device mxc_device_pcmcia;
diff --git a/arch/arm/mach-mx3/clock.c b/arch/arm/mach-mx3/clock.c
index da4a59f..7663010 100644
--- a/arch/arm/mach-mx3/clock.c
+++ b/arch/arm/mach-mx3/clock.c
@@ -573,6 +573,7 @@ static struct clk_lookup lookups[] = {
_REGISTER_CLOCK(NULL, "mpeg4", mpeg4_clk)
_REGISTER_CLOCK(NULL, "mbx", mbx_clk)
_REGISTER_CLOCK("mxc_rtc", NULL, ckil_clk)
+ _REGISTER_CLOCK("mxc-pcmcia", NULL, ahb_clk)
};
int __init mx31_clocks_init(unsigned long fref)
diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig
index 17f38a7..9bddf2f 100644
--- a/drivers/pcmcia/Kconfig
+++ b/drivers/pcmcia/Kconfig
@@ -292,4 +292,10 @@ config PCCARD_NONSTATIC
config PCCARD_IODYN
bool
+config PCMCIA_MXC
+ tristate "Freescale MXC PCMCIA Driver"
+ depends on PCMCIA && ARCH_MXC
+ help
+ Say Y here to support the integrated PCMCIA in the freescale MXC SoCs.
+
endif # PCCARD
diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile
index a03a38a..024fb9c 100644
--- a/drivers/pcmcia/Makefile
+++ b/drivers/pcmcia/Makefile
@@ -34,6 +34,8 @@ obj-$(CONFIG_OMAP_CF) += omap_cf.o
obj-$(CONFIG_BFIN_CFPCMCIA) += bfin_cf_pcmcia.o
obj-$(CONFIG_AT91_CF) += at91_cf.o
obj-$(CONFIG_ELECTRA_CF) += electra_cf.o
+obj-$(CONFIG_PCMCIA_MXC) += mxc_pcmcia.o
+mxc_pcmcia-objs += soc_common.o mxc_generic.o
sa11xx_core-y += soc_common.o sa11xx_base.o
pxa2xx_core-y += soc_common.o pxa2xx_base.o
@@ -78,3 +80,4 @@ pxa2xx-obj-$(CONFIG_MACH_E740) += pxa2xx_e740.o
pxa2xx-obj-$(CONFIG_MACH_STARGATE2) += pxa2xx_stargate2.o
obj-$(CONFIG_PCMCIA_PXA2XX) += pxa2xx_core.o $(pxa2xx-obj-y)
+
diff --git a/drivers/pcmcia/mxc_generic.c b/drivers/pcmcia/mxc_generic.c
new file mode 100644
index 0000000..4151ae4
--- /dev/null
+++ b/drivers/pcmcia/mxc_generic.c
@@ -0,0 +1,711 @@
+/*
+ * drivers/pcmcia/mxc_pcmica.c
+ *
+ * Driver for the PCMCIA control functionality of i.MX SoC family
+ *
+ * Copyright (C) 1999 John G. Dorsey. All Rights Reserved.
+ * Copyright (C) 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright (C) 2009 Martin Fuzzey <mfuzzey at gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+
+#include <mach/hardware.h>
+#include "soc_common.h"
+
+
+#define PCMCIA_WINDOWS 5 /* # windows supported by hardware */
+#define ATTRIBUTE_MEMORY_WINDOW 0
+#define COMMON_MEMORY_WINDOW 1
+#define IO_WINDOW 2
+
+/* Registers */
+#define PCMCIA_PIPR 0x00 /* Input pins register */
+#define PIPR_POWERON (1 << 8) /* card indicates "power on" */
+#define PIPR_RDY (1 << 7) /* card is ready */
+#define PIPR_BVD2 (1 << 6) /* battery voltage 2/SPKR in */
+#define PIPR_BVD1 (1 << 5) /* battery voltage 1/STSCHG */
+#define PIPR_CD (3 << 3) /* card detect 1 and 2 */
+#define PIPR_WP (1 << 2) /* write protect switch enabled */
+#define PIPR_VS (3 << 0) /* voltage sense bits */
+#define PIPR_VS_5V (1 << 0) /* 5v */
+
+#define PCMCIA_PSCR 0x04 /* Status Changed Register */
+#define PSCR_POWC (1 << 11) /* power on changed */
+#define PSCR_RDYR (1 << 10) /* ready rising changed */
+#define PSCR_RDYF (1 << 9) /* ready falling changed */
+#define PSCR_RDYH (1 << 8) /* ready state */
+#define PSCR_RDYL (1 << 7) /* inverted ready state */
+#define PSCR_BVDC2 (1 << 6) /* battery voltage 2 changed */
+#define PSCR_BVDC1 (1 << 5) /* battery voltage 1 changed */
+#define PSCR_CDC2 (1 << 4) /* card detect 2 changed */
+#define PSCR_CDC1 (1 << 3) /* card detect 1 changed */
+#define PSCR_WPC (1 << 2) /* write protect changed */
+#define PSCR_VSC2 (1 << 1) /* voltage sense 2 changed */
+#define PSCR_VSC1 (1 << 0) /* voltage sense 1 changed */
+
+#define PCMCIA_PER 0x08 /* Interrupt Enable Register */
+#define PER_ERRINTEN (1 << 12) /* error interrupt enable */
+#define PER_POWERONEN (1 << 11) /* power on interrupt enable */
+#define PER_RDYRE (1 << 10) /* RDY/nIREQ pin rising edge */
+#define PER_RDYFE (1 << 9) /* RDY/nIREQ pin falling edge */
+#define PER_RDYHE (1 << 8) /* RDY/nIREQ pin high */
+#define PER_RDYLE (1 << 7) /* RDY/nIREQ pin low */
+#define PER_BVDE2 (1 << 6) /* battery voltage 2/SPKR in */
+#define PER_BVDE1 (1 << 5) /* battery voltage 1/STSCHG */
+#define PER_CDE2 (1 << 4) /* card detect 2 */
+#define PER_CDE1 (1 << 3) /* card detect 1 */
+#define PER_WPE (1 << 2) /* write protect */
+#define PER_VSE2 (1 << 1) /* voltage sense 2 */
+#define PER_VSE1 (1 << 0) /* voltage sense 1 */
+
+/* win: 0-4 */
+#define PCMCIA_PBR(win) (0x0C + 4 * (win)) /* Base Register x */
+
+#define PCMCIA_POR(win) (0x28 + 4 * (win)) /* Option Register x */
+#define POR_PV (1 << 29) /* set iff bank is valid */
+#define POR_WPEN (1 << 28) /* write protect input enable */
+#define POR_WP (1 << 27) /* write protected */
+
+#define POR_PRS_SHIFT (25)
+#define POR_PRS(x) (((x) & 0x3) << POR_PRS_SHIFT)
+#define POR_PRS_MASK POR_PRS(3) /* PCMCIA region select */
+#define POR_PRS_COMMON (0) /* values of POR_PRS field */
+#define POR_PRS_TRUE_IDE (1)
+#define POR_PRS_ATTRIBUTE (2)
+#define POR_PRS_IO (3)
+
+#define POR_PPS_8 (1 << 24) /* PCMCIA Port size = 8bits */
+#define POR_PPS_16 (0 << 24) /* PCMCIA Port size = 16bits */
+
+#define POR_PSL_SHIFT (17) /* strobe length */
+#define POR_PSL(x) (((x) & 0x7F) << POR_PSL_SHIFT)
+#define POR_PSL_MASK POR_PSL(0x7f)
+
+#define POR_PSST_SHIFT (11) /* strobe setup time */
+#define POR_PSST(x) (((x) & 0x3F) << POR_PSST_SHIFT)
+#define POR_PSST_MASK POR_PSST(0x3f)
+
+#define POR_PSHT_SHIFT (5) /* strobe hold time */
+#define POR_PSHT(x) (((x) & 0x3F) << POR_PSHT_SHIFT)
+#define POR_PSHT_MASK POR_PSHT(0x3f)
+
+#define POR_BSIZE_SHIFT (0) /* bank size */
+#define POR_BSIZE(x) (((x) & 0x1F) << POR_BSIZE_SHIFT)
+#define POR_BSIZE_MASK POR_BSIZE(0x1F)
+
+#define PCMCIA_POFR(win) (0x44 + 4 * (win)) /* Offset Register x */
+
+#define PCMCIA_PGCR 0x60 /* General Control Register */
+#define PGCR_LPMEN (1 << 3) /* Low power Mode Enable */
+#define PGCR_SPKREN (1 << 2) /* SPKROUT routing enable */
+#define PGCR_POE (1 << 1) /* Controller out enable */
+#define PGCR_RESET (1 << 0) /* Card reset */
+
+#define PCMCIA_PGSR 0x64 /* General Status Register */
+#define PGSR_NWINE (1 << 4) /* No Window error */
+#define PGSR_LPE (1 << 3) /* Low Power error */
+#define PGSR_SE (1 << 2) /* Size error */
+#define PGSR_CDE (1 << 1) /* Card Detect error */
+#define PGSR_WPE (1 << 0) /* Write Protect error */
+
+
+static const char skt_name[] = "pcmcia0";
+
+struct mxc_pcmcia_window {
+ unsigned size;
+ unsigned bsize; /* BSZIE value - see datasheet */
+};
+
+struct mxc_pcmcia_soc {
+ unsigned long base; /* physical base address of card memory */
+ unsigned long addr_size; /* addressable range by pcmcia */
+ struct mxc_pcmcia_window windows[PCMCIA_WINDOWS];
+};
+
+struct mxc_pcmcia {
+ void __iomem *regs;
+ struct pcmcia_irqs irqs;
+ struct clk *clk;
+ struct mxc_pcmcia_soc *soc;
+};
+
+/* Hardcoded base address rather than PCMCIA_MEM_BASE_ADDR below
+to allow driver to work on multiple SoC types which I understand is a
+longterm objective for MXC */
+static struct mxc_pcmcia_soc mx21_soc = {
+ .base = 0xD4000000,
+ .addr_size = SZ_32K,
+ .windows = {
+ {.size = SZ_8K, .bsize = 0x0D},
+ {.size = SZ_8K, .bsize = 0x0D},
+ {.size = SZ_2K, .bsize = 0x04},
+ },
+};
+
+static struct mxc_pcmcia_soc mx27_soc = {
+ .base = 0xDC000000,
+ .addr_size = SZ_64M,
+ .windows = {
+ {.size = SZ_16M, .bsize = 0x14},
+ {.size = SZ_16M, .bsize = 0x14},
+ {.size = SZ_64K, .bsize = 0x05},
+ },
+};
+
+static struct mxc_pcmcia_soc mx3x_soc = {
+ .base = 0xBC000000,
+ .addr_size = SZ_64M,
+ .windows = {
+ {.size = SZ_16M, .bsize = 0x14},
+ {.size = SZ_16M, .bsize = 0x14},
+ {.size = SZ_64K, .bsize = 0x05},
+ },
+};
+
+static inline struct mxc_pcmcia *driver_data(struct soc_pcmcia_socket *skt)
+{
+ return skt->socket.driver_data;
+}
+
+static inline void set_register_bits(struct mxc_pcmcia *mxc_pcmcia,
+ u32 offset, u32 mask)
+{
+ void __iomem *reg = mxc_pcmcia->regs + offset;
+ writel(readl(reg) | mask, reg);
+}
+
+static inline void clear_register_bits(struct mxc_pcmcia *mxc_pcmcia,
+ u32 offset, u32 mask)
+{
+ void __iomem *reg = mxc_pcmcia->regs + offset;
+ writel(readl(reg) & ~mask, reg);
+}
+
+
+/*
+ * Set address and profile to window registers POR, POFR
+ */
+static int mxc_pcmcia_set_window(struct soc_pcmcia_socket *skt,
+ u_int card_addr, u_int window, u_int active, u32 *offset)
+{
+ u32 reg;
+ struct mxc_pcmcia *mxc_pcmcia = driver_data(skt);
+ u32 offset_mask = mxc_pcmcia->soc->windows[window].size - 1;
+ u32 bank_mask = ~offset_mask;
+
+ /* Disable the window */
+ clear_register_bits(mxc_pcmcia, PCMCIA_POR(window), POR_PV);
+
+ if ((card_addr & bank_mask) >= mxc_pcmcia->soc->addr_size) {
+ dev_err(skt->dev, "card address not supported %08X\n",
+ card_addr);
+ return -EINVAL;
+ }
+
+ /* Set POR */
+ reg = readl(mxc_pcmcia->regs + PCMCIA_POR(window));
+ reg &= ~(POR_PRS_MASK | POR_WPEN | POR_WP | POR_PPS_8);
+ reg |= POR_PPS_16;
+
+ switch (window) {
+ case IO_WINDOW:
+ reg |= POR_PRS(POR_PRS_IO);
+ break;
+
+ case ATTRIBUTE_MEMORY_WINDOW:
+ reg |= POR_PRS(POR_PRS_ATTRIBUTE);
+ break;
+
+ case COMMON_MEMORY_WINDOW:
+ reg |= POR_PRS(POR_PRS_COMMON);
+ break;
+
+ default:
+ return -EINVAL;
+
+ }
+ writel(reg, mxc_pcmcia->regs + PCMCIA_POR(window));
+
+ writel(card_addr & bank_mask, mxc_pcmcia->regs + PCMCIA_POFR(window));
+
+ /* Enable the window */
+ if (active)
+ set_register_bits(mxc_pcmcia, PCMCIA_POR(window), POR_PV);
+
+ if (offset)
+ *offset = card_addr & offset_mask;
+
+ return 0;
+}
+
+
+static int mxc_set_io_map(struct soc_pcmcia_socket *skt,
+ struct pccard_io_map *map)
+{
+ return mxc_pcmcia_set_window(
+ skt,
+ 0, IO_WINDOW,
+ map->flags & MAP_ACTIVE, NULL);
+}
+
+
+static int mxc_set_mem_map(struct soc_pcmcia_socket *skt,
+ struct pccard_mem_map *map)
+{
+ int window;
+ int ret;
+ struct resource *res;
+ u32 offset;
+
+ if (map->flags & MAP_ATTRIB) {
+ window = ATTRIBUTE_MEMORY_WINDOW;
+ res = &skt->res_attr;
+ } else {
+ window = COMMON_MEMORY_WINDOW;
+ res = &skt->res_mem;
+ }
+
+ ret = mxc_pcmcia_set_window(
+ skt,
+ map->card_start,
+ window,
+ map->flags & MAP_ACTIVE,
+ &offset);
+
+ if (!ret)
+ map->static_start = res->start + offset;
+
+ return ret;
+}
+
+
+static int mxc_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ struct mxc_pcmcia *mxc_pcmcia = driver_data(skt);
+ int ret;
+ int i;
+ u32 base, bsize, offset = 0;
+
+ ret = soc_pcmcia_request_irqs(skt, &mxc_pcmcia->irqs, 1);
+ if (ret)
+ return ret;
+
+ /* configure but don't enable windows */
+ for (i = 0; i < PCMCIA_WINDOWS; i++) {
+ if (mxc_pcmcia->soc->windows[i].size) {
+ base = offset;
+ bsize = mxc_pcmcia->soc->windows[i].bsize;
+ } else {
+ base = 0;
+ bsize = 0;
+ }
+ writel(base, mxc_pcmcia->regs + PCMCIA_PBR(i));
+ writel(bsize, mxc_pcmcia->regs + PCMCIA_POR(i));
+ writel(0, mxc_pcmcia->regs + PCMCIA_POFR(i));
+ offset += mxc_pcmcia->soc->windows[i].size;
+ }
+
+ return 0;
+}
+
+
+static void mxc_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
+{
+ struct mxc_pcmcia *mxc_pcmcia = driver_data(skt);
+ int i;
+
+ soc_pcmcia_free_irqs(skt, &mxc_pcmcia->irqs, 1);
+
+ for (i = 0; i < PCMCIA_WINDOWS; i++) {
+ writel(0, mxc_pcmcia->regs + PCMCIA_PBR(i));
+ writel(0, mxc_pcmcia->regs + PCMCIA_POR(i));
+ writel(0, mxc_pcmcia->regs + PCMCIA_POFR(i));
+ }
+}
+
+static void
+mxc_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ unsigned long pins;
+
+ pins = readl(driver_data(skt)->regs + PCMCIA_PIPR);
+ dev_dbg(skt->dev, "PIPR = 0x%08lx\n", pins);
+
+ state->ready = (pins & PIPR_RDY) ? 1 : 0;
+ state->bvd2 = (pins & PIPR_BVD2) ? 1 : 0;
+ state->bvd1 = (pins & PIPR_BVD1) ? 1 : 0;
+
+ if ((pins & PIPR_CD) == PIPR_CD)
+ skt->cs_state.csc_mask |= SS_INSERTION;
+ state->detect = (pins & PIPR_CD) ? 0 : 1;
+ state->wrprot = (pins & PIPR_WP) ? 1 : 0;
+ state->vs_3v = (pins & PIPR_VS_5V) ? 0 : 1;
+ state->vs_Xv = 0;
+}
+
+
+static void mxc_pcmcia_soft_reset(struct soc_pcmcia_socket *skt)
+{
+ struct mxc_pcmcia *mxc_pcmcia = driver_data(skt);
+
+ set_register_bits(mxc_pcmcia, PCMCIA_PGCR, PGCR_RESET);
+ msleep(2);
+
+ clear_register_bits(mxc_pcmcia, PCMCIA_PGCR, PGCR_RESET | PGCR_LPMEN);
+ set_register_bits(mxc_pcmcia, PCMCIA_PGCR, PGCR_POE);
+ msleep(2);
+ dev_dbg(skt->dev, "soft_reset:PGCR = %08x\n",
+ readl(mxc_pcmcia->regs + PCMCIA_PGCR));
+}
+
+static int
+mxc_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ struct mxc_pcmcia *mxc_pcmcia = driver_data(skt);
+
+ if (state->Vcc != 0 && state->Vcc != 33 && state->Vcc != 50) {
+ dev_err(skt->dev, "unrecognized Vcc %d\n", state->Vcc);
+ return -EINVAL;
+ }
+
+ dev_dbg(skt->dev, "PIPR = %x, desired Vcc = %d.%dV\n",
+ readl(mxc_pcmcia->regs + PCMCIA_PIPR),
+ state->Vcc / 10, state->Vcc % 10);
+
+ if (state->flags & SS_RESET) {
+ mxc_pcmcia_soft_reset(skt);
+
+ /* clean out previous tenant's trash */
+ writel(
+ PGSR_NWINE | PGSR_LPE | PGSR_SE | PGSR_CDE | PGSR_WPE,
+ mxc_pcmcia->regs + PCMCIA_PGSR);
+ }
+ /* enable interrupts if requested, else turn 'em off */
+ if (skt->irq)
+ writel(PER_RDYLE | PER_CDE1 | PER_CDE2,
+ mxc_pcmcia->regs + PCMCIA_PER);
+ else
+ writel(0, mxc_pcmcia->regs + PCMCIA_PER);
+
+ return 0;
+}
+
+
+/*
+ * Enable card status IRQs on (re-)initialisation. This can
+ * be called at initialisation, power management event, or
+ * pcmcia event.
+ */
+static void mxc_pcmcia_socket_init(struct soc_pcmcia_socket *skt)
+{
+ struct mxc_pcmcia *mxc_pcmcia = driver_data(skt);
+
+ mxc_pcmcia_soft_reset(skt);
+ soc_pcmcia_enable_irqs(skt, &mxc_pcmcia->irqs, 1);
+}
+
+/*
+ * Disable card status IRQ on suspend.
+ */
+static void mxc_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt)
+{
+ struct mxc_pcmcia *mxc_pcmcia = driver_data(skt);
+
+ soc_pcmcia_disable_irqs(skt, &mxc_pcmcia->irqs, 1);
+ set_register_bits(mxc_pcmcia, PCMCIA_PGCR, PGCR_LPMEN);
+}
+
+static void mxc_pcmcia_set_window_timing(struct soc_pcmcia_socket *skt,
+ u_int speed_ns, u_int window, u_int clk_ns)
+{
+ struct mxc_pcmcia *mxc_pcmcia = driver_data(skt);
+ u_int cycles = speed_ns / clk_ns;
+ u_int code = POR_PSHT(cycles) | POR_PSST(cycles) | POR_PSL(cycles + 2);
+
+ /* Disable the window */
+ clear_register_bits(mxc_pcmcia, PCMCIA_POR(window), POR_PV);
+
+ /* Clear the register first */
+ clear_register_bits(mxc_pcmcia, PCMCIA_POR(window),
+ POR_PSST_MASK | POR_PSL_MASK | POR_PSHT_MASK);
+
+ /* And then set the register */
+ set_register_bits(mxc_pcmcia, PCMCIA_POR(window), code);
+
+ /* Enable the window */
+ set_register_bits(mxc_pcmcia, PCMCIA_POR(window), POR_PV);
+}
+
+
+static int mxc_pcmcia_set_timing(struct soc_pcmcia_socket *skt)
+{
+ u_int clk_ns;
+ struct soc_pcmcia_timing timing;
+ struct mxc_pcmcia *mxc_pcmcia = driver_data(skt);
+
+ clk_ns = (1000 * 1000 * 1000) / clk_get_rate(mxc_pcmcia->clk);
+
+ soc_common_pcmcia_get_timing(skt, &timing);
+ dev_dbg(skt->dev, "timing: io %d, mem %d, attr %d clk_ns=%d\n",
+ timing.io, timing.mem, timing.attr, clk_ns);
+
+ mxc_pcmcia_set_window_timing(skt, timing.io,
+ IO_WINDOW, clk_ns);
+ mxc_pcmcia_set_window_timing(skt, timing.mem,
+ COMMON_MEMORY_WINDOW, clk_ns);
+ mxc_pcmcia_set_window_timing(skt, timing.attr,
+ ATTRIBUTE_MEMORY_WINDOW, clk_ns);
+
+ return 0;
+}
+
+
+static void mxc_pcmcia_ack_interrupt(struct soc_pcmcia_socket *skt)
+{
+ struct mxc_pcmcia *mxc_pcmcia = driver_data(skt);
+
+ writel(readl(mxc_pcmcia->regs + PCMCIA_PSCR),
+ mxc_pcmcia->regs + PCMCIA_PSCR);
+ writel(readl(mxc_pcmcia->regs + PCMCIA_PGSR),
+ mxc_pcmcia->regs + PCMCIA_PGSR);
+}
+
+static struct pcmcia_low_level mxc_pcmcia_ops = {
+ .owner = THIS_MODULE,
+ .hw_init = mxc_pcmcia_hw_init,
+ .hw_shutdown = mxc_pcmcia_hw_shutdown,
+ .socket_state = mxc_pcmcia_socket_state,
+ .configure_socket = mxc_pcmcia_configure_socket,
+
+ .socket_init = mxc_pcmcia_socket_init,
+ .socket_suspend = mxc_pcmcia_socket_suspend,
+
+ .set_timing = mxc_pcmcia_set_timing,
+ .set_io_map = mxc_set_io_map,
+ .set_mem_map = mxc_set_mem_map,
+ .ack_interrupt = mxc_pcmcia_ack_interrupt,
+};
+
+
+static void mxc_pcmcia_setup_resources(struct soc_pcmcia_socket *skt,
+ struct mxc_pcmcia_soc *soc)
+{
+ skt->res_skt.start = soc->base;
+ skt->res_skt.flags = IORESOURCE_MEM;
+
+ skt->res_attr.start = soc->base;
+ skt->res_attr.end = skt->res_attr.start +
+ soc->windows[ATTRIBUTE_MEMORY_WINDOW].size - 1;
+ skt->res_attr.name = "attribute";
+ skt->res_attr.flags = IORESOURCE_MEM;
+
+ skt->res_mem.start = skt->res_attr.end + 1;
+ skt->res_mem.end = skt->res_mem.start +
+ soc->windows[COMMON_MEMORY_WINDOW].size - 1;
+ skt->res_mem.name = "memory";
+ skt->res_mem.flags = IORESOURCE_MEM;
+
+ skt->res_io.start = skt->res_mem.end + 1;
+ skt->res_io.end = skt->res_io.start +
+ soc->windows[IO_WINDOW].size - 1;
+ skt->res_io.name = "io";
+ skt->res_io.flags = IORESOURCE_MEM | IORESOURCE_BUSY;
+
+ skt->res_skt.end = skt->res_io.end;
+
+ driver_data(skt)->soc = soc;
+}
+
+static struct mxc_pcmcia_soc *mxc_pcmcia_find_soc(void)
+{
+ if (cpu_is_mx3())
+ return &mx3x_soc;
+ if (cpu_is_mx27())
+ return &mx27_soc;
+ if (cpu_is_mx21())
+ return &mx21_soc;
+ return NULL;
+}
+
+static int __devinit mxc_drv_pcmcia_probe(struct platform_device *pdev)
+{
+ struct skt_dev_info *sinfo;
+ struct soc_pcmcia_socket *skt;
+ struct mxc_pcmcia *driver_data;
+ struct mxc_pcmcia_soc *soc;
+ struct resource *res;
+ struct pccard_io_map map;
+ int ret;
+
+ printk(KERN_INFO "mxc pcmcia driver\n");
+
+ soc = mxc_pcmcia_find_soc();
+ if (!soc) {
+ dev_err(&pdev->dev, "unsupported SoC\n");
+ return -ENODEV;
+ }
+
+ sinfo = kzalloc(sizeof(struct skt_dev_info)
+ + sizeof(struct soc_pcmcia_socket), GFP_KERNEL);
+ if (!sinfo)
+ return -ENOMEM;
+
+ driver_data = kzalloc(sizeof(*driver_data), GFP_KERNEL);
+ if (!driver_data) {
+ ret = -ENOMEM;
+ goto failed_alloc;
+ }
+
+ sinfo->nskt = 1;
+ skt = &sinfo->skt[0];
+ skt->nr = 0;
+ skt->socket.driver_data = driver_data;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -ENXIO;
+ goto failed_regs_resource;
+ }
+
+ res = request_mem_region(res->start, resource_size(res), pdev->name);
+ if (!res) {
+ ret = -EBUSY;
+ goto failed_request_regs_mem;
+ }
+
+ driver_data->regs = ioremap(res->start, resource_size(res));
+ if (!driver_data->regs) {
+ ret = -ENOMEM;
+ goto failed_map_regs;
+ }
+
+ skt->irq = platform_get_irq(pdev, 0);
+ if (skt->irq < 0) {
+ ret = skt->irq;
+ goto failed_irq;
+ }
+ driver_data->irqs.irq = skt->irq;
+ driver_data->irqs.flags = IRQF_SHARED;
+ driver_data->irqs.str = skt_name;
+
+ driver_data->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(driver_data->clk)) {
+ ret = PTR_ERR(driver_data->clk);
+ goto failed_clock;
+ }
+
+ skt->res_skt.name = skt_name;
+ mxc_pcmcia_setup_resources(skt, soc);
+
+ ret = soc_common_drv_pcmcia_probe(&pdev->dev, &mxc_pcmcia_ops, sinfo);
+ if (ret)
+ goto failed_common;
+
+ platform_set_drvdata(pdev, sinfo);
+
+ /* Core does not setup maps for io when in static mode */
+ map.map = 0;
+ map.start = 0;
+ map.stop = driver_data->soc->windows[IO_WINDOW].size - 1;
+ map.flags = MAP_ACTIVE | MAP_16BIT;
+ map.speed = 0;
+ skt->socket.ops->set_io_map(&skt->socket, &map);
+
+ return 0;
+
+failed_common:
+ clk_put(driver_data->clk);
+failed_clock:
+failed_irq:
+ iounmap(driver_data->regs);
+failed_map_regs:
+ release_mem_region(res->start, resource_size(res));
+failed_request_regs_mem:
+failed_regs_resource:
+ kfree(driver_data);
+failed_alloc:
+ kfree(sinfo);
+
+ return ret;
+}
+
+
+static int __devexit mxc_drv_pcmcia_remove(struct platform_device *pdev)
+{
+ struct skt_dev_info *sinfo = platform_get_drvdata(pdev);
+
+ soc_common_drv_pcmcia_remove(&pdev->dev);
+
+ if (sinfo) {
+ struct soc_pcmcia_socket *skt = &sinfo->skt[0];
+ struct mxc_pcmcia *driver_data = skt->socket.driver_data;
+ struct resource *res;
+
+ iounmap(driver_data->regs);
+ clk_put(driver_data->clk);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res)
+ release_mem_region(res->start, resource_size(res));
+ kfree(driver_data);
+ }
+
+ kfree(sinfo);
+ return 0;
+}
+
+
+static int mxc_drv_pcmcia_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ return pcmcia_socket_dev_suspend(&pdev->dev);
+}
+
+static int mxc_drv_pcmcia_resume(struct platform_device *pdev)
+{
+ return pcmcia_socket_dev_resume(&pdev->dev);
+}
+
+static struct platform_driver mxc_pcmcia_driver = {
+ .driver = {
+ .name = "mxc-pcmcia",
+ .owner = THIS_MODULE,
+ },
+ .probe = mxc_drv_pcmcia_probe,
+ .remove = mxc_drv_pcmcia_remove,
+ .suspend = mxc_drv_pcmcia_suspend,
+ .resume = mxc_drv_pcmcia_resume,
+};
+
+static int __init mxc_pcmcia_init(void)
+{
+ return platform_driver_register(&mxc_pcmcia_driver);
+}
+
+static void __exit mxc_pcmcia_exit(void)
+{
+ platform_driver_unregister(&mxc_pcmcia_driver);
+}
+
+fs_initcall(mxc_pcmcia_init);
+module_exit(mxc_pcmcia_exit);
+
+MODULE_AUTHOR("Martin Fuzzey");
+MODULE_DESCRIPTION("MXC PCMCIA Socket Controller");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:mxc-pcmcia");
+
More information about the linux-arm-kernel
mailing list