/* * drivers/pcmcia/hd64461_ss.c * * PCMCIA platform driver for Hitachi HD64461 companion chip * Copyright (C) 2006-2007 Kristoffer Ericson * * This driver is based on hd64461_ss.c that was maintained in LinuxSH cvs before merger with mainline * by * COPYRIGHT (C) 2002-2005 Andriy Skulysh * COPYRIGHT (C) ? Greg Banks * COPYRIGHT (C) 2000 YAEGASHI Takeshi * * Please note that although the hd64461 chipset supports two sockets (0 & 1) this driver only * supports the PCMCIA one. The CF slot cannot handle anything other than memory cards, so its * better to leave support for that on libata. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cs_internal.h" #include #include #include #define MODNAME "HD64461_ss" /* * * Our socket implementation **************************************/ typedef struct hd64461_socket_t { u8 cscier; 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; } hd64461_socket_t; /* socket declaration */ static hd64461_socket_t hd64461_sockets[CONFIG_HD64461_PCMCIA_SOCKETS]; /* * hd64461_set_voltage(int Vcc, int Vpp) * * returns : %1 on success (no error checking!) */ static int hd64461_set_voltage(int Vcc, int Vpp) { u8 gcr, scr; u16 stbcr; u32 gcr_reg = HD64461_PCC0GCR; u32 scr_reg = HD64461_PCC0SCR; gcr = inb(gcr_reg); scr = inb(scr_reg); /* Handling voltage control pins */ switch (Vcc) { case 0: gcr |= HD64461_PCCGCR_VCC0; scr |= HD64461_PCCSCR_VCC1; break; case 33: gcr |= HD64461_PCCGCR_VCC0; scr &= ~HD64461_PCCSCR_VCC1; break; case 50: gcr &= ~HD64461_PCCGCR_VCC0; scr &= ~HD64461_PCCSCR_VCC1; break; } outb(gcr, gcr_reg); outb(scr, scr_reg); stbcr = inw(HD64461_STBCR); if (Vcc > 0) { stbcr &= ~HD64461_STBCR_SPC0ST; } else { stbcr |= HD64461_STBCR_SPC0ST; } 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); printk(KERN_INFO "hd64461_ss: entering hd64461_init from pcmcia_register_socket\n"); sp->state.Vcc = 0; sp->state.Vpp = 0; hd64461_set_voltage(0, 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_PCC0GCR; gcr = inb(gcr_reg); gcr &= ~HD64461_PCCGCR_DRVE; outb(gcr, gcr_reg); hd64461_set_voltage(0, 0); gpadr = inw(HD64461_GPADR); gpadr |= HD64461_GPADR_PCMCIA0; outw(gpadr, HD64461_GPADR); return 0; } /* * hd64461_get_status(struct pcmcia_socket *s, u32 * value) * * */ 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); /* get status of pcmcia socket */ isr = inb(HD64461_PCC0ISR); printk(KERN_INFO "hd64461_get_status: reading HD64461_PCC0ISR status %d\n", isr); /* is card inserted and powerd? */ if ((isr & HD64461_PCCISR_PCD_MASK) == 0) { // if (isr & HD64461_PCCISR_PCD_MASK) { status |= SS_DETECT; /* If its an memory card, lets find out the voltage */ 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) printk(KERN_INFO "get_status: status has changed! \n"); status |= SS_STSCHG; } switch (isr & (HD64461_PCCISR_VS2 | HD64461_PCCISR_VS1)) { case HD64461_PCCISR_VS1: printk(KERN_INFO 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; } printk(KERN_INFO "get_status: returning status = %d\n", 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_PCC0GCR; u32 cscier_reg = HD64461_PCC0CSCIER; printk(KERN_INFO "hd64461: entering hd64461_set_socket\n"); /* no interruptions please */ local_irq_save(flags); /* compair old power status with new */ if (state->Vpp != sp->state.Vpp || state->Vcc != sp->state.Vcc) { if (!hd64461_set_voltage(state->Vcc, state->Vpp)) { local_irq_restore(flags); printk(KERN_INFO "hd64461_set_socket: We've produced an EINVAL\n"); return -EINVAL; } } /* lets only push the changes */ changed = sp->state.csc_mask ^ state->csc_mask; cscier = inb(cscier_reg); /* set it so interrupt occurs when values of CD1 and CD2 are changed */ if (changed & SS_DETECT) { if (state->csc_mask & SS_DETECT) cscier |= HD64461_PCCCSCIER_CDE; else cscier &= ~HD64461_PCCCSCIER_CDE; } /* set so interrupt occurs when pin changes from low -> high */ if (changed & SS_READY) { if (state->csc_mask & SS_READY) cscier |= HD64461_PCCCSCIER_RE; else cscier &= ~HD64461_PCCCSCIER_RE; } /* set so interrupt occurs when BVD1 & BVD2 are set to bat_dead */ if (changed & SS_BATDEAD) { if (state->csc_mask & SS_BATDEAD) cscier |= HD64461_PCCCSCIER_BDE; else cscier &= ~HD64461_PCCCSCIER_BDE; } /* set so interrupt occurs when BVD1 & BVD2 are set to bat_warn */ if (changed & SS_BATWARN) { if (state->csc_mask & SS_BATWARN) cscier |= HD64461_PCCCSCIER_BWE; else cscier &= ~HD64461_PCCCSCIER_BWE; } /* set so "pccard connection" interrupt initializes PCC0SCR and PCC0GCR */ if (changed & SS_STSCHG) { if (state->csc_mask & SS_STSCHG) cscier |= HD64461_PCCCSCIER_SCE; else cscier &= ~HD64461_PCCCSCIER_SCE; } outb(cscier, cscier_reg); changed = sp->state.flags ^ state->flags; gcr = inb(gcr_reg); if (changed & SS_IOCARD) { if (state->flags & SS_IOCARD) { if (s->sock == 1) { printk(KERN_INFO "socket 1 can be only IC Memory card\n"); } else { /* Reset the card and set as IO card */ printk(KERN_INFO "hd64461_set_socket: Lets reset and set to IO card \n"); gcr |= HD64461_PCCGCR_PCCT; sp->IC_memory = 0; } } else { /* Reset and set as memory card */ gcr &= ~HD64461_PCCGCR_PCCT; sp->IC_memory = 1; } } /* if bit 3 = 0 while pccard accessed, output 1 on pccreg */ if (changed & SS_RESET) { if (state->flags & SS_RESET) gcr |= HD64461_PCCGCR_PCCR; else gcr &= ~HD64461_PCCGCR_PCCR; } /* Set low level of external buffer */ if (changed & SS_OUTPUT_ENA) { if (state->flags & SS_OUTPUT_ENA) gcr |= HD64461_PCCGCR_DRVE; else gcr &= ~HD64461_PCCGCR_DRVE; } outb(gcr, gcr_reg); sp->state = *state; /* enable interruptions */ local_irq_restore(flags); printk(KERN_INFO "hd64461_set_socket: We've passed the routine successfully\n"); 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 */ 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; printk(KERN_INFO "hd64461: entering hd64461_set_mem_map\n"); if (map >= MAX_WIN) return -EINVAL; /* lets setup pointers to memory */ 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; } /* * * functions needed for operations **************************************/ 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, }; /* hd64461_interrupt() * * Here we check what caused the interrupt and * pass it on to pcmcia parser * ***************************************/ static irqreturn_t hd64461_interrupt(int irq, void *dev) { hd64461_socket_t *sp = (hd64461_socket_t *) dev; unsigned events = 0; unsigned char cscr,cscr2; //printk(KERN_INFO "HD64461_SS: Interrupt 78\n"); cscr = inb(HD64461_PCC0CSCR); /* If IREQ pin is in low state */ if (cscr & HD64461_PCCCSCR_IREQ) { if (!(cscr & HD64461_PCCCSCR_SC)) { cscr &= ~HD64461_PCCCSCR_IREQ; /* silence interrupt source and hand over interrupt */ outb(cscr, HD64461_PCC0CSCR); cscr2 = inb(HD64461_PCC0CSCR); if (!(cscr2 & cscr)) printk(KERN_INFO "hd64461_interrupt: Pah! we are in bloody level mode!\n"); return IRQ_NONE; } } // printk(KERN_INFO "HD64461_SS: Interrupt caused by Slot 0\n"); /* if both CD1 and CD2 has changed */ if (cscr & HD64461_PCCCSCR_CDC) { printk(KERN_INFO "HD64461_SS: We are inside the CD1/CD2 changed loop\n"); /* silence it by writing a 0 to bit 3 */ cscr &= ~HD64461_PCCCSCR_CDC; /* we've detected something being inserted or unplugged */ events |= SS_DETECT; /* if CD1 and CD2 don't both display 1, we need to clean up */ if (((inb(HD64461_PCC0ISR)) & HD64461_PCCISR_PCD_MASK) != 0) { cscr &= ~(HD64461_PCCCSCR_RC | HD64461_PCCCSCR_BW | HD64461_PCCCSCR_BD | HD64461_PCCCSCR_SC); } } /* MEMORY CARD */ if (sp->IC_memory) { if (cscr & HD64461_PCCCSCR_RC) { /* ? */ cscr &= ~HD64461_PCCCSCR_RC; events |= SS_READY; } if (cscr & HD64461_PCCCSCR_BW) { /* battery warning */ cscr &= ~HD64461_PCCCSCR_BW; events |= SS_BATWARN; } if (cscr & HD64461_PCCCSCR_BD) { /* battery dead */ cscr &= ~HD64461_PCCCSCR_BD; events |= SS_BATDEAD; } } else { /* IO CARD */ if (cscr & HD64461_PCCCSCR_SC) { /* status changed */ cscr &= ~HD64461_PCCCSCR_SC; events |= SS_STSCHG; } } outb(cscr, HD64461_PCC0CSCR); /* make sure we push these changes into pcmcia events */ if (events) { pcmcia_parse_events(&sp->socket, events); printk(KERN_INFO "hd64461_interrupt: we are pushing changed events! events=%d\n", events); } return IRQ_HANDLED; } /* *hd_64461_init_socket() * * Add values to socket and link it with pcmcia operations *********************************************************/ int hd64461_init_socket(int sock, int irq, unsigned long mem_base,unsigned long io_offset) { hd64461_socket_t *sp = &hd64461_sockets[sock]; unsigned gcr_reg = HD64461_PCC0GCR; u8 gcr; int i, err; outb(0, HD64461_PCC0CSCIER); memset(sp, 0, sizeof(*sp)); sp->IC_memory = 0; sp->irq = irq; //sp->socket.irq_mask = 0; 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.ops = &hd64461_operations; sp->socket.map_size = HD64461_PCC_WINDOW; /* 16MB fixed window size */ sp->socket.pci_irq = irq; sp->socket.io_offset = 0; sp->socket.owner = THIS_MODULE; for (i = 0; i != MAX_WIN; i++) sp->mem_maps[i].map = i; if ((request_irq(irq, hd64461_interrupt, IRQF_SHARED, "hd64461_ss-irq", sp)) < 0) { printk(KERN_INFO "hd64461_init: request for irq %d: failed\n", sp->irq); return -1; } gcr = inb(gcr_reg); /* continuous 16MB area mode for both memory and I/O operations */ gcr |= HD64461_PCCGCR_PMMOD; /* ??? */ gcr &= ~(HD64461_PCCGCR_PA25 | HD64461_PCCGCR_PA24); outb(gcr, gcr_reg); return 0; } void hd64461_exit_socket(int sock) { hd64461_socket_t *sp = &hd64461_sockets[0]; unsigned cscier_reg = HD64461_PCC0CSCIER; printk(KERN_INFO "hd64461_exit_socket: cleaning up irq %d and %d\n", sp->irq, sp->socket.pci_irq); outb(0, cscier_reg); hd64461_suspend(&sp->socket); } static int __devexit hd64461_pcmcia_drv_remove(struct platform_device *dev) { /* Libpata handles CF slot, so we only handle PCMCIA slot */ pcmcia_unregister_socket(&hd64461_sockets[0].socket); hd64461_exit_socket(0); return 0; } #ifdef CONFIG_PM static int hd64461_pcmcia_drv_suspend(struct platform_device *dev, pm_message_t state) { int ret = 0; int i; u32 cscier_reg = HD64461_PCC0CSCIER; hd64461_sockets[0].cscier = inb(cscier_reg); 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; u32 cscier_reg = HD64461_PCC0CSCIER; outb(hd64461_sockets[0].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_ss_init: pcmcia driver for hd64461 chipset started\n"); if (platform_driver_register(&hd64461_pcmcia_driver)) goto failed1; i = hd64461_init_socket(0, HD64461_IRQ_PCC0, HD64461_PCC0_BASE, 0x0); if (i < 0) goto failed2; hd64461_pcmcia_device = platform_device_alloc("hd64461-pcmcia",-1); if(!hd64461_pcmcia_device) { printk(KERN_INFO "hd64461_ss_init: Cannot find pcmcia device!\n"); return -ENODEV; } i = platform_device_add(hd64461_pcmcia_device); if (i) { platform_device_put(hd64461_pcmcia_device); printk(KERN_INFO "hd64461_ss_init: Cannot add pcmcia device, return value %d\n",i); return -ENODEV; } /* Lets register the pcmcia socket */ printk(KERN_INFO "hd64461_ss_init: setting socket device\n"); hd64461_sockets[0].socket.dev.parent = &hd64461_pcmcia_device->dev; i = pcmcia_register_socket(&hd64461_sockets[0].socket); printk(KERN_INFO "hd64461_ss_init: return value %d from pcmcia_register_socket\n", i); return 0; /* Unregister driver nothing else */ failed1: printk(KERN_INFO "hd64461_ss_init: Failed registering pcmcia platform driver\n"); platform_driver_unregister(&hd64461_pcmcia_driver); return -EINVAL; /* Unregister driver nothing else */ failed2: printk(KERN_INFO "hd64461_ss_init: Failed to startup socket 0\n"); platform_driver_unregister(&hd64461_pcmcia_driver); return i; } static void __exit exit_hd64461_ss(void) { /* Only remove if there's something to remove */ if (hd64461_pcmcia_device) { platform_device_unregister(hd64461_pcmcia_device); platform_driver_unregister(&hd64461_pcmcia_driver); } } module_init(init_hd64461_ss); module_exit(exit_hd64461_ss); MODULE_AUTHOR("Kristoffer Ericson "); MODULE_DESCRIPTION("PCMCIA driver for Hitachi HD64461 companion chip"); MODULE_LICENSE("GPL");