Signed-off-by: Martin Fuzzey <mfuz...@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 <mfuz...@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"); + _______________________________________________ Linux PCMCIA reimplementation list http://lists.infradead.org/mailman/listinfo/linux-pcmcia