/* * file: drivers/pcmcia/bfin_cf.c * * based on: drivers/pcmcia/omap_cf.c * omap_cf.c -- OMAP 16xx CompactFlash controller driver * * Copyright (c) 2005 David Brownell * Copyright (c) 2006-2008 Michael Hennerich Analog Devices Inc. * * bugs: enter bugs at http://blackfin.uclinux.org/ * * 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, 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; see the file copying. * if not, write to the free software foundation, * 59 temple place - suite 330, boston, ma 02111-1307, usa. */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/platform_device.h> #include <linux/errno.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/io.h> #include <pcmcia/ss.h> #include <pcmcia/cisreg.h> #include <asm/gpio.h> #define SZ_1K 0x00000400 #define SZ_8K 0x00002000 #define SZ_2K (2 * SZ_1K) #define POLL_INTERVAL (2 * HZ) #define CF_ATASEL_ENA 0x20311802 /* Inverts RESET */ #define CF_ATASEL_DIS 0x20311800 #define bfin_cf_present(pfx) (gpio_get_value(pfx)) /*--------------------------------------------------------------------------*/ static const char driver_name[] = "bfin_cf_pcmcia"; struct bfin_cf_socket { struct pcmcia_socket socket; struct timer_list timer; unsigned present:1; unsigned active:1; struct platform_device *pdev; unsigned long phys_cf_io; unsigned long phys_cf_attr; u_int irq; u_short cd_pfx; }; /*--------------------------------------------------------------------------*/ static int bfin_cf_reset(void) { outw(0, CF_ATASEL_ENA); mdelay(200); outw(0, CF_ATASEL_DIS); return 0; } static int bfin_cf_ss_init(struct pcmcia_socket *s) { return 0; } /* the timer is primarily to kick this socket's pccardd */ static void bfin_cf_timer(unsigned long _cf) { struct bfin_cf_socket *cf = (void *)_cf; unsigned short present = bfin_cf_present(cf->cd_pfx); if (present != cf->present) { cf->present = present; dev_dbg(&cf->pdev->dev, ": card %s\n", present ? "present" : "gone"); pcmcia_parse_events(&cf->socket, SS_DETECT); } if (cf->active) mod_timer(&cf->timer, jiffies + POLL_INTERVAL); } static int bfin_cf_get_status(struct pcmcia_socket *s, u_int *sp) { struct bfin_cf_socket *cf; if (!sp) return -EINVAL; cf = container_of(s, struct bfin_cf_socket, socket); if (bfin_cf_present(cf->cd_pfx)) { *sp = SS_READY | SS_DETECT | SS_POWERON | SS_3VCARD; s->pcmcia_irq = 0; s->pci_irq = cf->irq; } else *sp = 0; return 0; } static int bfin_cf_set_socket(struct pcmcia_socket *sock, struct socket_state_t *s) { struct bfin_cf_socket *cf; cf = container_of(sock, struct bfin_cf_socket, socket); switch (s->Vcc) { case 0: case 33: break; case 50: break; default: return -EINVAL; } if (s->flags & SS_RESET) { disable_irq(cf->irq); bfin_cf_reset(); enable_irq(cf->irq); } dev_dbg(&cf->pdev->dev, ": Vcc %d, io_irq %d, flags %04x csc %04x\n", s->Vcc, s->io_irq, s->flags, s->csc_mask); return 0; } static int bfin_cf_ss_suspend(struct pcmcia_socket *s) { return bfin_cf_set_socket(s, &dead_socket); } /* regions are 2K each: mem, attrib, io (and reserved-for-ide) */ static int bfin_cf_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io) { struct bfin_cf_socket *cf; cf = container_of(s, struct bfin_cf_socket, socket); io->flags &= MAP_ACTIVE | MAP_ATTRIB | MAP_16BIT; io->start = cf->phys_cf_io; io->stop = io->start + SZ_2K - 1; return 0; } static int bfin_cf_set_mem_map(struct pcmcia_socket *s, struct pccard_mem_map *map) { struct bfin_cf_socket *cf; if (map->card_start) return -EINVAL; cf = container_of(s, struct bfin_cf_socket, socket); map->static_start = cf->phys_cf_io; map->flags &= MAP_ACTIVE | MAP_ATTRIB | MAP_16BIT; if (map->flags & MAP_ATTRIB) map->static_start = cf->phys_cf_attr; return 0; } static struct pccard_operations bfin_cf_ops = { .init = bfin_cf_ss_init, .suspend = bfin_cf_ss_suspend, .get_status = bfin_cf_get_status, .set_socket = bfin_cf_set_socket, .set_io_map = bfin_cf_set_io_map, .set_mem_map = bfin_cf_set_mem_map, }; /*--------------------------------------------------------------------------*/ static int __devinit bfin_cf_probe(struct platform_device *pdev) { struct bfin_cf_socket *cf; struct resource *io_mem, *attr_mem; int irq; unsigned short cd_pfx; int status = 0; dev_info(&pdev->dev, "Blackfin CompactFlash/PCMCIA Socket Driver\n"); irq = platform_get_irq(pdev, 0); if (irq <= 0) return -EINVAL; cd_pfx = platform_get_irq(pdev, 1); /*Card Detect GPIO PIN */ if (gpio_request(cd_pfx, "pcmcia: CD")) { dev_err(&pdev->dev, "Failed ro request Card Detect GPIO_%d\n", cd_pfx); return -EBUSY; } gpio_direction_input(cd_pfx); cf = kzalloc(sizeof *cf, GFP_KERNEL); if (!cf) { gpio_free(cd_pfx); return -ENOMEM; } cf->cd_pfx = cd_pfx; setup_timer(&cf->timer, bfin_cf_timer, (unsigned long)cf); cf->pdev = pdev; platform_set_drvdata(pdev, cf); cf->irq = irq; cf->socket.pci_irq = irq; irq_set_irq_type(irq, IRQF_TRIGGER_LOW); io_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); attr_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!io_mem || !attr_mem) goto fail0; cf->phys_cf_io = io_mem->start; cf->phys_cf_attr = attr_mem->start; /* pcmcia layer only remaps "real" memory */ cf->socket.io_offset = (unsigned long) ioremap(cf->phys_cf_io, SZ_2K); if (!cf->socket.io_offset) goto fail0; dev_err(&pdev->dev, ": on irq %d\n", irq); dev_dbg(&pdev->dev, ": %s\n", bfin_cf_present(cf->cd_pfx) ? "present" : "(not present)"); cf->socket.owner = THIS_MODULE; cf->socket.dev.parent = &pdev->dev; cf->socket.ops = &bfin_cf_ops; cf->socket.resource_ops = &pccard_static_ops; cf->socket.features = SS_CAP_PCCARD | SS_CAP_STATIC_MAP | SS_CAP_MEM_ALIGN; cf->socket.map_size = SZ_2K; status = pcmcia_register_socket(&cf->socket); if (status < 0) goto fail2; cf->active = 1; mod_timer(&cf->timer, jiffies + POLL_INTERVAL); return 0; fail2: iounmap((void __iomem *)cf->socket.io_offset); release_mem_region(cf->phys_cf_io, SZ_8K); fail0: gpio_free(cf->cd_pfx); kfree(cf); platform_set_drvdata(pdev, NULL); return status; } static int __devexit bfin_cf_remove(struct platform_device *pdev) { struct bfin_cf_socket *cf = platform_get_drvdata(pdev); gpio_free(cf->cd_pfx); cf->active = 0; pcmcia_unregister_socket(&cf->socket); del_timer_sync(&cf->timer); iounmap((void __iomem *)cf->socket.io_offset); release_mem_region(cf->phys_cf_io, SZ_8K); platform_set_drvdata(pdev, NULL); kfree(cf); return 0; } static struct platform_driver bfin_cf_driver = { .driver = { .name = (char *)driver_name, .owner = THIS_MODULE, }, .probe = bfin_cf_probe, .remove = __devexit_p(bfin_cf_remove), }; static int __init bfin_cf_init(void) { return platform_driver_register(&bfin_cf_driver); } static void __exit bfin_cf_exit(void) { platform_driver_unregister(&bfin_cf_driver); } module_init(bfin_cf_init); module_exit(bfin_cf_exit); MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); MODULE_DESCRIPTION("BFIN CF/PCMCIA Driver"); MODULE_LICENSE("GPL");