diff options
Diffstat (limited to 'drivers/pcmcia/hd64461_ss.c')
-rw-r--r-- | drivers/pcmcia/hd64461_ss.c | 555 |
1 files changed, 555 insertions, 0 deletions
diff --git a/drivers/pcmcia/hd64461_ss.c b/drivers/pcmcia/hd64461_ss.c new file mode 100644 index 00000000000000..a179b771ec1aaa --- /dev/null +++ b/drivers/pcmcia/hd64461_ss.c @@ -0,0 +1,555 @@ +/* + * drivers/pcmcia/hd64461_ss.c + * + * PCMCIA platform driver for Hitachi HD64461 companion chip + * Copyright (C) 2006-2008 Kristoffer Ericson <kristoffer.ericson@gmail.com> + * + * 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 <askulysh@image.kiev.ua> + * COPYRIGHT (C) ? Greg Banks <gbanks@pocketpenguins.com> + * 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 that to other drivers such as pata. + * + */ +#include <linux/autoconf.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.h> + +#define MODNAME "HD64461_ss" + +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; + +static hd64461_socket_t hd64461_sockets[1]; + +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); + + 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 = ctrl_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); + + sp->state.Vcc = 0; + sp->state.Vpp = 0; + hd64461_set_voltage(0, 0); + + gpadr = ctrl_inw(HD64461_GPADR); + gpadr &= ~HD64461_GPADR_PCMCIA0; + ctrl_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; + ctrl_outb(gcr, gcr_reg); + hd64461_set_voltage(0, 0); + + gpadr = ctrl_inw(HD64461_GPADR); + gpadr |= HD64461_GPADR_PCMCIA0; + ctrl_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); + + /* get status of pcmcia socket */ + isr = inb(HD64461_PCC0ISR); + + /* is card inserted and powerd? */ + 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 { + status |= SS_STSCHG; + } + + switch (~isr & (HD64461_PCCISR_VS2 | HD64461_PCCISR_VS1)) { + case HD64461_PCCISR_VS1: + /* 5V card, Implies that the card shouldn't work, but sometimes it actually does */ + /* so we set the 3V just to give it a try */ + status |= SS_3VCARD; + break; + case 0: + case HD64461_PCCISR_VS2: + /* This card is an ordinary 3V card */ + status |= SS_3VCARD; + break; + case HD64461_PCCISR_VS2 | HD64461_PCCISR_VS1: + break; + } + + if ((sp->state.Vcc != 0) || (sp->state.Vpp != 0)) { + status |= SS_POWERON; + } + } + *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; + + 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); + return -EINVAL; + } + } + + /* lets only push the changes */ + changed = sp->state.csc_mask ^ state->csc_mask; + cscier = ctrl_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; + } + + ctrl_outb(cscier, cscier_reg); + + changed = sp->state.flags ^ state->flags; + + gcr = ctrl_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 */ + 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; + } + + 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 */ + io->start = 0xba000000; + io->stop = 0xbc000000; + + 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; + + 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 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, +}; + +static irqreturn_t hd64461_interrupt(int irq, void *dev) +{ + hd64461_socket_t *sp = (hd64461_socket_t *) dev; + unsigned events = 0; + unsigned char cscr; + + cscr = ctrl_inb(HD64461_PCC0CSCR); + + /* If IREQ pin is in low state */ + if (cscr & HD64461_PCCCSCR_IREQ) { + cscr &= ~HD64461_PCCCSCR_IREQ; + /* silence interrupt source and hand over interrupt */ + ctrl_outb(cscr, HD64461_PCC0CSCR); + return IRQ_NONE; + } + + /* if both CD1 and CD2 has changed */ + if ((cscr & HD64461_PCCCSCR_CDC)) { + /* 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 card is ejected then cleanup */ + if (((ctrl_inb(HD64461_PCC0ISR)) & ~HD64461_PCCISR_PCD_MASK)) { + 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; + } + } + ctrl_outb(cscr, HD64461_PCC0CSCR); + + /* make sure we push these changes into pcmcia events */ + if (events) + pcmcia_parse_events(&sp->socket, events); + + return IRQ_HANDLED; +} + +int hd64461_init_socket(int sock, int irq, unsigned long mem_base,unsigned short io_offset) +{ + hd64461_socket_t *sp = &hd64461_sockets[sock]; + unsigned gcr_reg = HD64461_PCC0GCR; + u8 gcr; + int i; + + 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.ops = &hd64461_operations; + sp->socket.map_size = HD64461_PCC_WINDOW; /* 16MB fixed window size */ + sp->socket.pci_irq = irq; + sp->socket.io_offset = io_offset; + 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; + + outb(0, cscier_reg); + hd64461_suspend(&sp->socket); + +} + +static int __devexit hd64461_pcmcia_drv_remove(struct platform_device *dev) +{ + /* Libpata handles CF slot (slot 1), so we only handle PCMCIA (slot 0) */ + 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; + 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; + 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 host bridge driver\n"); + + if (platform_driver_register(&hd64461_pcmcia_driver)) + return -ENODEV; + + i = hd64461_init_socket(0, HD64461_IRQ_PCC0, HD64461_PCC0_BASE, 0xf000); + 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 host device!\n"); + return -ENODEV; + } + + i = platform_device_add(hd64461_pcmcia_device); + if (i) { + platform_device_put(hd64461_pcmcia_device); + goto failed2; + } + + hd64461_sockets[0].socket.dev.parent = &hd64461_pcmcia_device->dev; + + i = pcmcia_register_socket(&hd64461_sockets[0].socket); + return 0; + +/* 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 <kristoffer.ericson@gmail.com>"); +MODULE_DESCRIPTION("PCMCIA driver for Hitachi HD64461 companion chip"); +MODULE_LICENSE("GPL"); |