diff options
Diffstat (limited to 'drivers/pcmcia/hd64461_ss.c')
-rw-r--r-- | drivers/pcmcia/hd64461_ss.c | 679 |
1 files changed, 679 insertions, 0 deletions
diff --git a/drivers/pcmcia/hd64461_ss.c b/drivers/pcmcia/hd64461_ss.c new file mode 100644 index 00000000000000..3f9a1994098c00 --- /dev/null +++ b/drivers/pcmcia/hd64461_ss.c @@ -0,0 +1,679 @@ +/* + * drivers/pcmcia/hd64461_ss.c + * + * PCMCIA support for Hitachi HD64461 companion chip + * by Andriy Skulysh <askulysh@image.kiev.ua> 2002, 2003, 2004 + * + * based on hd64461_ss.c by Greg Banks <gbanks@pocketpenguins.com> + * + */ +#include <linux/config.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/ss.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" +#include <asm/io.h> +#include <asm/hd64461.h> +#include <asm/hp6xx/hp6xx.h> + +#define MODNAME "HD64461_ss" + +#ifdef DEBUG +static int hd64461_pc_debug = 2; + +module_param_named(pc_debug, hd64461_pc_debug, int, 0644); + +#define DPRINTK(n, args...) \ +do { \ + if (hd64461_pc_debug >= (n)) \ + printk(args); \ +} while (0) +#else +#define DPRINTK(n, args...) do { } while (0) +#endif + +#define HD64461_PCC(s,reg) \ + ( CONFIG_HD64461_IOBASE-HD64461_STBCR+ ( (s) ? HD64461_PCC1##reg : \ + HD64461_PCC0##reg ) ) + +typedef struct hd64461_socket_t { + unsigned int irq; + unsigned long mem_base; + socket_state_t state; + pccard_mem_map mem_maps[MAX_WIN]; + unsigned char IC_memory; + struct pcmcia_socket socket; + u8 cscier; +} hd64461_socket_t; + +static hd64461_socket_t hd64461_sockets[CONFIG_HD64461_PCMCIA_SOCKETS]; + +#define hd64461_sockno(sp) (sp - hd64461_sockets) + +static void hd64461_enable_int(unsigned int irq) +{ + u8 cscier; + u32 cscier_reg = HD64461_PCC(0, CSCIER); + + cscier = ctrl_inb(cscier_reg); + cscier &= ~HD64461_PCCCSCIER_IREQE_MASK; + cscier |= HD64461_PCCCSCIER_IREQE_LEVEL; + ctrl_outb(cscier, cscier_reg); +} + +static void hd64461_disable_int(unsigned int irq) +{ + u8 cscier; + u32 cscier_reg = HD64461_PCC(0, CSCIER); + + cscier = ctrl_inb(cscier_reg); + cscier &= ~HD64461_PCCCSCIER_IREQE_MASK; + ctrl_outb(cscier, cscier_reg); +} + +static void hd64461_enable_irq(unsigned int irq) +{ + DPRINTK(3, "hd64461_enable_irq(irq=%d)\n", irq); + hd64461_enable_int(irq); +} + +static void hd64461_disable_irq(unsigned int irq) +{ + DPRINTK(3, "hd64461_disable_irq(irq=%d)\n", irq); + hd64461_disable_int(irq); +} + +static unsigned int hd64461_startup_irq(unsigned int irq) +{ + DPRINTK(3, "hd64461_startup_irq(irq=%d)\n", irq); + hd64461_enable_irq(irq); + return 0; +} + +static void hd64461_shutdown_irq(unsigned int irq) +{ + DPRINTK(3, "hd64461_shutdown_irq(irq=%d)\n", irq); + hd64461_disable_irq(irq); +} + +static void hd64461_mask_and_ack_irq(unsigned int irq) +{ + DPRINTK(3, "hd64461_mask_and_ack_irq(irq=%d)\n", irq); + hd64461_disable_irq(irq); +} + +static void hd64461_end_irq(unsigned int irq) +{ + DPRINTK(3, "hd64461_end_irq(irq=%d)\n", irq); + hd64461_enable_irq(irq); +} + +static struct hw_interrupt_type hd64461_ss_irq_type = { + .typename = "HD64461_SS-IRQ", + .startup = hd64461_startup_irq, + .shutdown = hd64461_shutdown_irq, + .enable = hd64461_enable_irq, + .disable = hd64461_disable_irq, + .ack = hd64461_mask_and_ack_irq, + .end = hd64461_end_irq +}; + +static int hd64461_set_voltage(int sock, int Vcc, int Vpp) +{ + u8 gcr, scr; + u16 stbcr; + u32 gcr_reg = HD64461_PCC(sock, GCR); + u32 scr_reg = HD64461_PCC(sock, SCR); + DPRINTK(2, "hd64461_set_voltage(%d, %d, %d)\n", sock, Vcc, Vpp); + + gcr = ctrl_inb(gcr_reg); + scr = ctrl_inb(scr_reg); + + switch (Vcc) { + case 0: + gcr |= HD64461_PCCGCR_VCC0; + scr |= HD64461_PCCSCR_VCC1; + break; + case 33: + if (sock == 1) { + gcr &= ~HD64461_PCCGCR_VCC0; + scr &= ~HD64461_PCCSCR_VCC1; + } else { + gcr |= HD64461_PCCGCR_VCC0; + scr &= ~HD64461_PCCSCR_VCC1; + } + break; + case 50: + gcr &= ~HD64461_PCCGCR_VCC0; + scr &= ~HD64461_PCCSCR_VCC1; + break; + } + + ctrl_outb(gcr, gcr_reg); + ctrl_outb(scr, scr_reg); + + stbcr = inw(HD64461_STBCR); + + if (Vcc > 0) { + stbcr &= ~(sock == 0 ? HD64461_STBCR_SPC0ST : + HD64461_STBCR_SPC1ST); + } else { + stbcr |= (sock == 0 ? HD64461_STBCR_SPC0ST : + HD64461_STBCR_SPC1ST); + } + + outw(stbcr, HD64461_STBCR); + + return 1; +} + +static int hd64461_init(struct pcmcia_socket *s) +{ + u16 gpadr; + hd64461_socket_t *sp = container_of(s, struct hd64461_socket_t, socket); + + DPRINTK(0, "hd64461_init(%d)\n", s->sock); + + sp->state.Vcc = 0; + sp->state.Vpp = 0; + hd64461_set_voltage(s->sock, 0, 0); + + if (mach_is_hp6xx() && s->sock == 0) { + gpadr = inw(HD64461_GPADR); + gpadr &= ~HD64461_GPADR_PCMCIA0; + outw(gpadr, HD64461_GPADR); + } + + return 0; +} + +static int hd64461_suspend(struct pcmcia_socket *s) +{ + u16 gpadr; + u8 gcr; + u32 gcr_reg = HD64461_PCC(s->sock, GCR); + + DPRINTK(0, "hd64461_suspend(%d)\n", s->sock); + + gcr = ctrl_inb(gcr_reg); + gcr &= ~HD64461_PCCGCR_DRVE; + ctrl_outb(gcr, gcr_reg); + hd64461_set_voltage(s->sock, 0, 0); + + if ((mach_is_hp6xx())&&(s->sock == 0)) { + gpadr = inw(HD64461_GPADR); + gpadr |= HD64461_GPADR_PCMCIA0; + outw(gpadr, HD64461_GPADR); + } + + return 0; +} + +static int hd64461_get_status(struct pcmcia_socket *s, u32 * value) +{ + u8 isr; + u32 status = 0; + hd64461_socket_t *sp = container_of(s, struct hd64461_socket_t, socket); + + isr = ctrl_inb(HD64461_PCC(s->sock, ISR)); + + if ((isr & HD64461_PCCISR_PCD_MASK) == 0) { + status |= SS_DETECT; + + if (sp->IC_memory) { + switch (isr & HD64461_PCCISR_BVD_MASK) { + case HD64461_PCCISR_BVD_BATGOOD: + break; + case HD64461_PCCISR_BVD_BATWARN: + status |= SS_BATWARN; + break; + default: + status |= SS_BATDEAD; + break; + } + + if (isr & HD64461_PCCISR_READY) + status |= SS_READY; + if (isr & HD64461_PCCISR_MWP) + status |= SS_WRPROT; + } else { + if (isr & HD64461_PCCISR_BVD1) + status |= SS_STSCHG; + } + + switch (isr & (HD64461_PCCISR_VS2 | HD64461_PCCISR_VS1)) { + case HD64461_PCCISR_VS1: + printk(KERN_NOTICE MODNAME + ": cannot handle X.XV card, ignored\n"); + status = 0; + break; + case 0: + case HD64461_PCCISR_VS2: + status |= SS_3VCARD; + break; + case HD64461_PCCISR_VS2 | HD64461_PCCISR_VS1: + break; + } + + if ((sp->state.Vcc != 0) || (sp->state.Vpp != 0)) + status |= SS_POWERON; + } + DPRINTK(0, "hd64461_get_status(%d) = %x\n", s->sock, status); + + *value = status; + return 0; +} + +static int hd64461_set_socket(struct pcmcia_socket *s, socket_state_t * state) +{ + u32 flags; + u32 changed; + u8 gcr, cscier; + hd64461_socket_t *sp = container_of(s, struct hd64461_socket_t, socket); + u32 gcr_reg = HD64461_PCC(s->sock, GCR); + u32 cscier_reg = HD64461_PCC(s->sock, CSCIER); + + DPRINTK(0, "%s(sock=%d, flags=%x, csc_mask=%x, Vcc=%d, Vpp=%d, io_irq=%d)\n", + __FUNCTION__, s->sock, state->flags, state->csc_mask, state->Vcc, + state->Vpp, state->io_irq); + + local_irq_save(flags); + + if (state->Vpp != sp->state.Vpp || state->Vcc != sp->state.Vcc) { + if (!hd64461_set_voltage(s->sock, state->Vcc, state->Vpp)) { + local_irq_restore(flags); + return -EINVAL; + } + } + + changed = sp->state.csc_mask ^ state->csc_mask; + cscier = ctrl_inb(cscier_reg); + + if (changed & SS_DETECT) { + if (state->csc_mask & SS_DETECT) + cscier |= HD64461_PCCCSCIER_CDE; + else + cscier &= ~HD64461_PCCCSCIER_CDE; + } + + if (changed & SS_READY) { + if (state->csc_mask & SS_READY) + cscier |= HD64461_PCCCSCIER_RE; + else + cscier &= ~HD64461_PCCCSCIER_RE; + } + + if (changed & SS_BATDEAD) { + if (state->csc_mask & SS_BATDEAD) + cscier |= HD64461_PCCCSCIER_BDE; + else + cscier &= ~HD64461_PCCCSCIER_BDE; + } + + if (changed & SS_BATWARN) { + if (state->csc_mask & SS_BATWARN) + cscier |= HD64461_PCCCSCIER_BWE; + else + cscier &= ~HD64461_PCCCSCIER_BWE; + } + + if (changed & SS_STSCHG) { + if (state->csc_mask & SS_STSCHG) + cscier |= HD64461_PCCCSCIER_SCE; + else + cscier &= ~HD64461_PCCCSCIER_SCE; + } + + ctrl_outb(cscier, cscier_reg); + + changed = sp->state.flags ^ state->flags; + + gcr = ctrl_inb(gcr_reg); + + if (changed & SS_IOCARD) { + DPRINTK(0, "card type: %s\n", + (state->flags & SS_IOCARD ? "i/o" : "memory")); + if (state->flags & SS_IOCARD) { + if (s->sock == 1) { + printk(KERN_ERR + "socket 1 can be only IC Memory card\n"); + } else { + gcr |= HD64461_PCCGCR_PCCT; + sp->IC_memory = 0; + } + } else { + gcr &= ~HD64461_PCCGCR_PCCT; + sp->IC_memory = 1; + } + } + + if (changed & SS_RESET) { + DPRINTK(0, "%s reset card\n", + (state->flags & SS_RESET ? "start" : "stop")); + if (state->flags & SS_RESET) + gcr |= HD64461_PCCGCR_PCCR; + else + gcr &= ~HD64461_PCCGCR_PCCR; + } + + if (changed & SS_OUTPUT_ENA) { + DPRINTK(0, "%sabling card output\n", + (state->flags & SS_OUTPUT_ENA ? "en" : "dis")); + if (state->flags & SS_OUTPUT_ENA) + gcr |= HD64461_PCCGCR_DRVE; + else + gcr &= ~HD64461_PCCGCR_DRVE; + } + + DPRINTK(2, "cscier=%02x ", cscier); + DPRINTK(2, "gcr=%02x\n", gcr); + ctrl_outb(gcr, gcr_reg); + + sp->state = *state; + + local_irq_restore(flags); + + return 0; +} + +static int hd64461_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io) +{ + /* this is not needed due to static mappings */ + DPRINTK(0, "hd64461_set_io_map(%d)\n", s->sock); + + return 0; +} + +static int hd64461_set_mem_map(struct pcmcia_socket *s, + struct pccard_mem_map *mem) +{ + hd64461_socket_t *sp = container_of(s, struct hd64461_socket_t, socket); + struct pccard_mem_map *smem; + int map = mem->map; + unsigned long saddr; + + DPRINTK(0, "%s(sock=%d, map=%d, flags=0x%x,static_start=0x%08lx, card_start=0x%08x)\n", + __FUNCTION__, s->sock, map, mem->flags, mem->static_start, mem->card_start); + + if (map >= MAX_WIN) + return -EINVAL; + + smem = &sp->mem_maps[map]; + saddr = sp->mem_base + mem->card_start; + + if (!(mem->flags & MAP_ATTRIB)) + saddr += HD64461_PCC_WINDOW; + + mem->static_start = saddr; + *smem = *mem; + + return 0; +} + +static int hd64461_pcmcia_irq_demux(int irq, void *dev) +{ + hd64461_socket_t *sp = (hd64461_socket_t *) dev; + unsigned char cscr; + unsigned cscr_reg = HD64461_PCC(0, CSCR); + + DPRINTK(3, "hd64461_pcmcia_irq_demux(irq= %d - ", irq); + + cscr = ctrl_inb(cscr_reg); + if (cscr & HD64461_PCCCSCR_IREQ) { + cscr &= ~HD64461_PCCCSCR_IREQ; + ctrl_outb(cscr, cscr_reg); + irq = sp->socket.pci_irq; + } + + DPRINTK(3, "%d)\n", irq); + + return irq; +} + +static irqreturn_t hd64461_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + hd64461_socket_t *sp = (hd64461_socket_t *) dev; + unsigned events = 0; + unsigned char cscr; + unsigned cscr_reg = HD64461_PCC(hd64461_sockno(sp), CSCR); + + cscr = ctrl_inb(cscr_reg); + + DPRINTK(3, "hd64461_interrupt: cscr=%04x irq=%d\n", cscr, irq); + + if (cscr & HD64461_PCCCSCR_CDC) { + cscr &= ~HD64461_PCCCSCR_CDC; + events |= SS_DETECT; + + if ((ctrl_inb(HD64461_PCC(hd64461_sockno(sp), ISR)) & + HD64461_PCCISR_PCD_MASK) != 0) { + cscr &= ~(HD64461_PCCCSCR_RC | HD64461_PCCCSCR_BW | + HD64461_PCCCSCR_BD | HD64461_PCCCSCR_SC); + } + } + + if (sp->IC_memory) { + if (cscr & HD64461_PCCCSCR_RC) { + cscr &= ~HD64461_PCCCSCR_RC; + events |= SS_READY; + } + + if (cscr & HD64461_PCCCSCR_BW) { + cscr &= ~HD64461_PCCCSCR_BW; + events |= SS_BATWARN; + } + + if (cscr & HD64461_PCCCSCR_BD) { + cscr &= ~HD64461_PCCCSCR_BD; + events |= SS_BATDEAD; + } + } else { + if (cscr & HD64461_PCCCSCR_SC) { + cscr &= ~HD64461_PCCCSCR_SC; + events |= SS_STSCHG; + } + } + + ctrl_outb(cscr, cscr_reg); + + if (events) + pcmcia_parse_events(&sp->socket, events); + + return IRQ_HANDLED; +} + +static struct pccard_operations hd64461_operations = { + .init = hd64461_init, + .suspend = hd64461_suspend, + .get_status = hd64461_get_status, + .set_socket = hd64461_set_socket, + .set_io_map = hd64461_set_io_map, + .set_mem_map = hd64461_set_mem_map, +}; + +int hd64461_init_socket(int sock, int irq, int io_irq, unsigned long mem_base, + unsigned short io_offset) +{ + hd64461_socket_t *sp = &hd64461_sockets[sock]; + unsigned gcr_reg = HD64461_PCC(sock, GCR); + int irq_flags = (sock == 0) ? SA_INTERRUPT : SA_SHIRQ; + u8 gcr; + int i, err; + + ctrl_outb(0, HD64461_PCC(sock, CSCIER)); + + memset(sp, 0, sizeof(*sp)); + sp->IC_memory = 1; + sp->irq = irq; + sp->mem_base = mem_base; + sp->socket.features = + SS_CAP_PCCARD | SS_CAP_STATIC_MAP | SS_CAP_PAGE_REGS; + sp->socket.resource_ops = &pccard_static_ops; + sp->socket.map_size = HD64461_PCC_WINDOW; /* 16MB fixed window size */ + sp->socket.pci_irq = io_irq; + sp->socket.io_offset = io_offset; + sp->socket.owner = THIS_MODULE; + sp->socket.ops = &hd64461_operations; + + for (i = 0; i != MAX_WIN; i++) + sp->mem_maps[i].map = i; + + if ((err = + request_irq(irq, hd64461_interrupt, irq_flags, MODNAME, sp)) < 0) { + printk(KERN_ERR + "HD64461 PCMCIA socket %d: can't request irq %d\n", sock, + sp->irq); + return err; + } + + if (sock == 0) { + irq_desc[io_irq].handler = &hd64461_ss_irq_type; + hd64461_register_irq_demux(sp->irq, hd64461_pcmcia_irq_demux, + sp); + } + + gcr = ctrl_inb(gcr_reg); + gcr |= HD64461_PCCGCR_PMMOD; /* 16MB mapping mode */ + gcr &= ~(HD64461_PCCGCR_PA25 | HD64461_PCCGCR_PA24); /* lowest 16MB of Common */ + ctrl_outb(gcr, gcr_reg); + + return 0; +} + +void hd64461_exit_socket(int sock) +{ + hd64461_socket_t *sp = &hd64461_sockets[sock]; + unsigned cscier_reg = HD64461_PCC(sock, CSCIER); + + ctrl_outb(0, cscier_reg); + hd64461_suspend(&sp->socket); + + if (sp->irq) { + if (sock == 0) + hd64461_unregister_irq_demux(sp->irq); + free_irq(sp->irq, sp); + if (sock == 0) + irq_desc[sp->socket.pci_irq].handler = &no_irq_type; + } +} + +static int __devexit hd64461_pcmcia_drv_remove(struct platform_device *dev) +{ + int i; + + for (i = 0; i != CONFIG_HD64461_PCMCIA_SOCKETS; i++) { + pcmcia_unregister_socket(&hd64461_sockets[i].socket); + hd64461_exit_socket(i); + } + + return 0; +} + +#ifdef CONFIG_PM +static int hd64461_pcmcia_drv_suspend(struct platform_device *dev, pm_message_t state) +{ + int ret = 0; + int i; + + for (i = 0; i != CONFIG_HD64461_PCMCIA_SOCKETS; i++) { + u32 cscier_reg = HD64461_PCC(i, CSCIER); + hd64461_sockets[i].cscier = ctrl_inb(cscier_reg); + ctrl_outb(0, cscier_reg); + ret = pcmcia_socket_dev_suspend(&dev->dev, state); + } + return ret; + } + +static int hd64461_pcmcia_drv_resume(struct platform_device *dev) +{ + int ret = 0; + int i; + + for (i = 0; i != CONFIG_HD64461_PCMCIA_SOCKETS; i++) { + u32 cscier_reg = HD64461_PCC(i, CSCIER); + ctrl_outb(hd64461_sockets[i].cscier, cscier_reg); + ret = pcmcia_socket_dev_resume(&dev->dev); + } + + return ret; +} +#endif + +static struct platform_driver hd64461_pcmcia_driver = { + .remove = __devexit_p(hd64461_pcmcia_drv_remove), +#ifdef CONFIG_PM + .suspend = hd64461_pcmcia_drv_suspend, + .resume = hd64461_pcmcia_drv_resume, +#endif + .driver = { + .name = "hd64461-pcmcia", + }, +}; + +static struct platform_device *hd64461_pcmcia_device; + +static int __init init_hd64461_ss(void) +{ + int i; + + printk(KERN_INFO "HD64461 PCMCIA bridge.\n"); + if (platform_driver_register(&hd64461_pcmcia_driver)) + return -EINVAL; + + i = hd64461_init_socket(0, HD64461_IRQ_PCC0, HD64461_IRQ_PCC0 + 1, + HD64461_PCC0_BASE, 0xf000); + if (i < 0) { + platform_driver_unregister(&hd64461_pcmcia_driver); + return i; + } +#if CONFIG_HD64461_PCMCIA_SOCKETS==2 + i = hd64461_init_socket(1, HD64461_IRQ_PCC1, HD64461_IRQ_PCC1, + HD64461_PCC1_BASE, 0); + if (i < 0) { + platform_driver_unregister(&hd64461_pcmcia_driver); + return i; + } +#endif + + hd64461_pcmcia_device = platform_device_register_simple("hd64461-pcmcia", -1, NULL, 0); + if (IS_ERR(hd64461_pcmcia_device)) { + platform_driver_unregister(&hd64461_pcmcia_driver); + return PTR_ERR(hd64461_pcmcia_device); + } + + for (i = 0; i != CONFIG_HD64461_PCMCIA_SOCKETS; i++) { + unsigned int ret; + hd64461_sockets[i].socket.dev.dev = &hd64461_pcmcia_device->dev; + ret = pcmcia_register_socket(&hd64461_sockets[i].socket); + if (ret && i) + pcmcia_unregister_socket(&hd64461_sockets[0].socket); + } + + return 0; +} + +static void __exit exit_hd64461_ss(void) +{ + platform_device_unregister(hd64461_pcmcia_device); + platform_driver_unregister(&hd64461_pcmcia_driver); +} + +module_init(init_hd64461_ss); +module_exit(exit_hd64461_ss); + +MODULE_AUTHOR("Andriy Skulysh <askulysh@gmail.com>"); +MODULE_DESCRIPTION("PCMCIA driver for Hitachi HD64461 companion chip"); +MODULE_LICENSE("GPL"); |