[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