From a1ceb846a968d898251f66d8a222351c9a235807 Mon Sep 17 00:00:00 2001 From: Haavard Skinnemoen Date: Mon, 9 Jul 2007 16:43:19 +0200 Subject: [PATCH] Add CompactFlash glue driver for AT32AP CPUs This driver uses the Static Memory Controller on AT32AP CPUs to communicate with CompactFlash devices in memory or I/O mode. Signed-off-by: Haavard Skinnemoen --- drivers/pcmcia/Kconfig | 7 + drivers/pcmcia/Makefile | 1 + drivers/pcmcia/at32_cf.c | 527 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 535 insertions(+), 0 deletions(-) create mode 100644 drivers/pcmcia/at32_cf.c diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig index 35f8864..29388e1 100644 --- a/drivers/pcmcia/Kconfig +++ b/drivers/pcmcia/Kconfig @@ -270,6 +270,13 @@ config AT91_CF Say Y here to support the CompactFlash controller on AT91 chips. Or choose M to compile the driver as a module named "at91_cf". +config AT32_CF + tristate "AT32AP CompactFlash Controller" + depends on PCMCIA && AVR32 && PLATFORM_AT32AP + help + Say Y here to support the CompactFlash controller on AT32 chips. + Or choose M to compile the driver as a module named "at32_cf". + config PCCARD_NONSTATIC tristate diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile index 4276965..08d7ffa 100644 --- a/drivers/pcmcia/Makefile +++ b/drivers/pcmcia/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_PCMCIA_VRC4171) += vrc4171_card.o obj-$(CONFIG_PCMCIA_VRC4173) += vrc4173_cardu.o obj-$(CONFIG_OMAP_CF) += omap_cf.o obj-$(CONFIG_AT91_CF) += at91_cf.o +obj-$(CONFIG_AT32_CF) += at32_cf.o sa11xx_core-y += soc_common.o sa11xx_base.o pxa2xx_core-y += soc_common.o pxa2xx_base.o diff --git a/drivers/pcmcia/at32_cf.c b/drivers/pcmcia/at32_cf.c new file mode 100644 index 0000000..6123729 --- /dev/null +++ b/drivers/pcmcia/at32_cf.c @@ -0,0 +1,527 @@ +/* + * Driver for AVR32 Static Memory Controller: CompactFlash support + * + * Copyright (C) 2006 Atmel Norway + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * The full GNU General Public License is included in this + * distribution in the file called COPYING. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +struct at32_cf_socket { + struct pcmcia_socket socket; + int detect_pin; + int reset_pin; + int vcc_pin; + int ready_pin; + struct resource res_attr; + struct resource res_mem; + struct resource res_io; + struct smc_config smc; + unsigned int irq; + unsigned int cf_cs; + socket_state_t state; + unsigned present:1; +}; +#define to_at32_cf(sock) container_of(sock, struct at32_cf_socket, socket) + +/* + * We have the following memory layout relative to the base address: + * + * Alt IDE Mode: 00e0 0000 -> 00ff ffff + * True IDE Mode: 00c0 0000 -> 00df ffff + * I/O memory: 0080 0000 -> 00bf ffff + * Common memory: 0040 0000 -> 007f ffff + * Attribute memory: 0000 0000 -> 003f ffff + */ +#define CF_ATTR_OFFSET 0x00000000 +#define CF_MEM_OFFSET 0x00400000 +#define CF_IO_OFFSET 0x00800000 +#define CF_RES_SIZE 4096 + +#ifdef DEBUG + +static int pc_debug; +module_param(pc_debug, int, 0644); + +static void at32_cf_debug(struct at32_cf_socket *cf, const char *func, + int level, const char *fmt, ...) +{ + va_list args; + + if (pc_debug > level) { + printk(KERN_DEBUG "at32_cf/%u: %s: ", cf->cf_cs, func); + va_start(args, fmt); + vprintk(fmt, args); + va_end(args); + } +} + +#define debug(cf, lvl, fmt, arg...) \ + at32_cf_debug(cf, __func__, lvl, fmt, ##arg) + +#else +#define debug(cf, lvl, fmt, arg...) do { } while (0) +#endif + +static inline int at32_cf_present(struct at32_cf_socket *cf) +{ + int present = 1; + + /* If we don't have a detect pin, assume the card is present */ + if (cf->detect_pin >= 0) + present = !gpio_get_value(cf->detect_pin); + + return present; +} + +static irqreturn_t at32_cf_irq(int irq, void *dev_id) +{ + struct at32_cf_socket *cf = dev_id; + unsigned int present; + + present = at32_cf_present(cf); + if (present != cf->present) { + cf->present = present; + debug(cf, 3, "card %s\n", present ? "present" : "gone"); + pcmcia_parse_events(&cf->socket, SS_DETECT); + } + + return IRQ_HANDLED; +} + +static int at32_cf_get_status(struct pcmcia_socket *sock, u_int *value) +{ + struct at32_cf_socket *cf; + u_int status = 0; + + cf = container_of(sock, struct at32_cf_socket, socket); + + if (at32_cf_present(cf)) { + /* NOTE: gpio on AP7xxx is 3.3V */ + status = SS_DETECT | SS_3VCARD; + if (cf->ready_pin < 0 || gpio_get_value(cf->ready_pin)) + status |= SS_READY; + if (cf->vcc_pin < 0 || gpio_get_value(cf->vcc_pin)) + status |= SS_POWERON; + } + + *value = status; + return 0; +} + +static int at32_cf_set_socket(struct pcmcia_socket *sock, socket_state_t *state) +{ + struct at32_cf_socket *cf = container_of(sock, struct at32_cf_socket, socket); + + debug(cf, 2, "mask: %s%s%s%s%s%sflags: %s%s%s%s%s%sVcc %d Vpp %d irq %d\n", + (state->csc_mask==0)?" ":"", + (state->csc_mask&SS_DETECT)?"DETECT ":"", + (state->csc_mask&SS_READY)?"READY ":"", + (state->csc_mask&SS_BATDEAD)?"BATDEAD ":"", + (state->csc_mask&SS_BATWARN)?"BATWARN ":"", + (state->csc_mask&SS_STSCHG)?"STSCHG ":"", + (state->flags==0)?" ":"", + (state->flags&SS_PWR_AUTO)?"PWR_AUTO ":"", + (state->flags&SS_IOCARD)?"IOCARD ":"", + (state->flags&SS_RESET)?"RESET ":"", + (state->flags&SS_SPKR_ENA)?"SPKR_ENA ":"", + (state->flags&SS_OUTPUT_ENA)?"OUTPUT_ENA ":"", + state->Vcc, state->Vpp, state->io_irq); + + /* + * TODO: Allow boards to override this in case they have level + * converters. + */ + switch (state->Vcc) { + case 0: + if (cf->vcc_pin >= 0) + gpio_set_value(cf->vcc_pin, 0); + break; + case 33: + if (cf->vcc_pin >= 0) + gpio_set_value(cf->vcc_pin, 1); + break; + default: + return -EINVAL; + } + + if (cf->reset_pin >= 0) + gpio_set_value(cf->reset_pin, state->flags & SS_RESET); + + cf->state = *state; + + return 0; +} + +static int at32_cf_socket_init(struct pcmcia_socket *sock) +{ + debug(to_at32_cf(sock), 2, "called\n"); + + return 0; +} + +static int at32_cf_suspend(struct pcmcia_socket *sock) +{ + debug(to_at32_cf(sock), 2, "called\n"); + + at32_cf_set_socket(sock, &dead_socket); + + return 0; +} + +static int at32_cf_set_io_map(struct pcmcia_socket *sock, + struct pccard_io_map *map) +{ + struct at32_cf_socket *cf = container_of(sock, struct at32_cf_socket, socket); + int retval; + + debug(cf, 2, "map %u speed %u start 0x%08x stop 0x%08x\n", + map->map, map->speed, map->start, map->stop); + debug(cf, 2, "flags: %s%s%s%s%s%s%s%s\n", + (map->flags == 0) ? "":"", + (map->flags & MAP_ACTIVE) ? "ACTIVE " : "", + (map->flags & MAP_16BIT) ? "16BIT " : "", + (map->flags & MAP_AUTOSZ) ? "AUTOSZ " : "", + (map->flags & MAP_0WS) ? "0WS " : "", + (map->flags & MAP_WRPROT) ? "WRPROT " : "", + (map->flags & MAP_USE_WAIT) ? "USE_WAIT " : "", + (map->flags & MAP_PREFETCH) ? "PREFETCH " : ""); + + map->flags &= MAP_ACTIVE | MAP_16BIT | MAP_USE_WAIT; + + if (map->flags & MAP_16BIT) + cf->smc.bus_width = 2; + else + cf->smc.bus_width = 1; + + if (map->flags & MAP_USE_WAIT) + cf->smc.nwait_mode = 3; + else + cf->smc.nwait_mode = 0; + + retval = smc_set_configuration(cf->cf_cs, &cf->smc); + if (retval) { + printk(KERN_ERR "at32_cf: could not set up SMC for I/O\n"); + return retval; + } + + map->start = cf->socket.io_offset; + map->stop = map->start + CF_RES_SIZE - 1; + + return 0; +} + +static int +at32_cf_set_mem_map(struct pcmcia_socket *sock, struct pccard_mem_map *map) +{ + struct at32_cf_socket *cf; + struct resource *res; + int retval; + + cf = container_of(sock, struct at32_cf_socket, socket); + + debug(cf, 2, "map %u speed %u card_start %08x\n", + map->map, map->speed, map->card_start); + debug(cf, 2, "flags: %s%s%s%s%s%s%s%s\n", + (map->flags==0)?"":"", + (map->flags&MAP_ACTIVE)?"ACTIVE ":"", + (map->flags&MAP_16BIT)?"16BIT ":"", + (map->flags&MAP_AUTOSZ)?"AUTOSZ ":"", + (map->flags&MAP_0WS)?"0WS ":"", + (map->flags&MAP_WRPROT)?"WRPROT ":"", + (map->flags&MAP_ATTRIB)?"ATTRIB ":"", + (map->flags&MAP_USE_WAIT)?"USE_WAIT ":""); + + if (map->card_start) + return -EINVAL; + + map->flags &= MAP_ACTIVE | MAP_ATTRIB | MAP_16BIT | MAP_USE_WAIT; + + if (map->flags & MAP_ATTRIB) { + res = &cf->res_attr; + + /* Linksys WCF12 seems to use WAIT when reading CIS */ + map->flags |= MAP_USE_WAIT; + } else { + res = &cf->res_mem; + } + + if (map->flags & MAP_USE_WAIT) + cf->smc.nwait_mode = 3; + else + cf->smc.nwait_mode = 0; + + retval = smc_set_configuration(cf->cf_cs, &cf->smc); + if (retval) { + printk(KERN_ERR "at32_cf: could not set up SMC for mem\n"); + return retval; + } + + map->static_start = res->start; + + return 0; +} + +static struct pccard_operations at32_cf_ops = { + .init = at32_cf_socket_init, + .suspend = at32_cf_suspend, + .get_status = at32_cf_get_status, + .set_socket = at32_cf_set_socket, + .set_io_map = at32_cf_set_io_map, + .set_mem_map = at32_cf_set_mem_map, +}; + +static int __init request_pin(struct platform_device *pdev, + unsigned int pin, const char *name) +{ + if (gpio_request(pin, name)) { + dev_warn(&pdev->dev, "failed to request %s pin\n", name); + return -1; + } + + return pin; +} + +static int __init at32_cf_probe(struct platform_device *pdev) +{ + struct at32_cf_socket *cf; + struct cf_platform_data *board = pdev->dev.platform_data; + struct resource *res_skt; + int irq; + int ret; + + dev_dbg(&pdev->dev, "probe"); + + if (!board) + return -ENXIO; + + res_skt = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res_skt) + return -ENXIO; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + cf = kzalloc(sizeof(struct at32_cf_socket), GFP_KERNEL); + if (!cf) + return -ENOMEM; + + cf->detect_pin = -1; + cf->reset_pin = -1; + cf->vcc_pin = -1; + cf->ready_pin = -1; + cf->cf_cs = board->cs; + + if (board->detect_pin) + cf->detect_pin = request_pin(pdev, board->detect_pin, + "cf_detect"); + if (board->reset_pin) + cf->reset_pin = request_pin(pdev, board->reset_pin, + "cf_reset"); + if (board->vcc_pin) + cf->vcc_pin = request_pin(pdev, board->reset_pin, + "cf_vcc"); + if (board->ready_pin) + /* READY is also used for irq through EIM */ + cf->ready_pin = board->ready_pin; + + debug(cf, 2, "pins: detect=%d reset=%d vcc=%d\n", + cf->detect_pin, cf->reset_pin, cf->vcc_pin); + + cf->socket.pci_irq = irq; + cf->socket.ops = &at32_cf_ops; + cf->socket.resource_ops = &pccard_static_ops; + cf->socket.dev.parent = &pdev->dev; + cf->socket.owner = THIS_MODULE; + cf->socket.features = + SS_CAP_MEM_ALIGN | SS_CAP_STATIC_MAP | SS_CAP_PCCARD; + cf->socket.map_size = CF_RES_SIZE; + + cf->res_attr.start = res_skt->start + CF_ATTR_OFFSET; + cf->res_attr.end = cf->res_attr.start + CF_RES_SIZE - 1; + cf->res_attr.name = "attribute"; + cf->res_attr.flags = IORESOURCE_MEM; + ret = request_resource(res_skt, &cf->res_attr); + if (ret) + goto err_request_res_attr; + + cf->res_mem.start = res_skt->start + CF_MEM_OFFSET; + cf->res_mem.end = cf->res_mem.start + CF_RES_SIZE - 1; + cf->res_mem.name = "memory"; + cf->res_mem.flags = IORESOURCE_MEM; + ret = request_resource(res_skt, &cf->res_mem); + if (ret) + goto err_request_res_mem; + + cf->res_io.start = res_skt->start + CF_IO_OFFSET; + cf->res_io.end = cf->res_io.start + CF_RES_SIZE - 1; + cf->res_io.name = "io"; + cf->res_io.flags = IORESOURCE_MEM; + ret = request_resource(res_skt, &cf->res_io); + if (ret) + goto err_request_res_io; + + cf->socket.io_offset = cf->res_io.start; + + if (cf->detect_pin >= 0) { + ret = request_irq(gpio_to_irq(cf->detect_pin), at32_cf_irq, + IRQF_SHARED, "cf_detect", cf); + if (ret) { + debug(cf, 1, + "failed to request cf_detect interrupt\n"); + goto err_detect_irq; + } + } + + /* Setup SMC timings */ + cf->smc.ncs_read_setup = 30; + cf->smc.nrd_setup = 100; + cf->smc.ncs_write_setup = 30; + cf->smc.nwe_setup = 100; + + cf->smc.ncs_read_pulse = 360; + cf->smc.nrd_pulse = 290; + cf->smc.ncs_write_pulse = 360; + cf->smc.nwe_pulse = 290; + + cf->smc.read_cycle = 420; + cf->smc.write_cycle = 420; + + cf->smc.bus_width = 2; + cf->smc.nrd_controlled = 1; + cf->smc.nwe_controlled = 1; + cf->smc.nwait_mode = 0; + cf->smc.byte_write = 0; + cf->smc.tdf_cycles = 8; + cf->smc.tdf_mode = 0; + + ret = smc_set_configuration(cf->cf_cs, &cf->smc); + if (ret) { + debug(cf, 1, "failed to configure SMC\n", ret); + goto err_smc; + } + + ret = pcmcia_register_socket(&cf->socket); + if (ret) { + debug(cf, 1, "failed to register socket: %d\n", ret); + goto err_register_socket; + } + + if (cf->reset_pin >= 0) + gpio_direction_output(cf->reset_pin, 0); + + platform_set_drvdata(pdev, cf); + + dev_info(&pdev->dev, "Atmel SMC CF interface at 0x%08lx\n", + (unsigned long)res_skt->start); + + return 0; + +err_register_socket: +err_smc: + if (cf->detect_pin >= 0) + free_irq(gpio_to_irq(cf->detect_pin), cf); +err_detect_irq: + release_resource(&cf->res_io); +err_request_res_io: + release_resource(&cf->res_mem); +err_request_res_mem: + release_resource(&cf->res_attr); +err_request_res_attr: + if (cf->vcc_pin >= 0) + gpio_free(cf->vcc_pin); + if (cf->reset_pin >= 0) + gpio_free(cf->reset_pin); + if (cf->detect_pin >= 0) + gpio_free(cf->detect_pin); + kfree(cf); + + return ret; +} + +static int __exit at32_cf_remove(struct platform_device *pdev) +{ + struct at32_cf_socket *cf = platform_get_drvdata(pdev); + + pcmcia_unregister_socket(&cf->socket); + if (cf->detect_pin >= 0) { + free_irq(gpio_to_irq(cf->detect_pin), cf); + gpio_free(cf->detect_pin); + } + if (cf->vcc_pin >= 0) + gpio_free(cf->vcc_pin); + if (cf->reset_pin >= 0) + gpio_free(cf->reset_pin); + + release_resource(&cf->res_io); + release_resource(&cf->res_mem); + release_resource(&cf->res_attr); + kfree(cf); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver at32_cf_driver = { + .remove = __exit_p(at32_cf_remove), + .driver = { + .name = "at32_cf", + .owner = THIS_MODULE, + }, +}; + +static int __init at32_cf_init(void) +{ + int ret; + + ret = platform_driver_probe(&at32_cf_driver, at32_cf_probe); + if (ret) + printk(KERN_ERR "at32_cf: probe failed: %d\n", ret); + return ret; +} + +static void __exit at32_cf_exit(void) +{ + platform_driver_unregister(&at32_cf_driver); +} + +module_init(at32_cf_init); +module_exit(at32_cf_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Driver for SMC PCMCIA interface"); +MODULE_AUTHOR("Hans-Christian Egtvedt "); -- 1.5.2.3