URL
https://opencores.org/ocsvn/or1k/or1k/trunk
Subversion Repositories or1k
[/] [or1k/] [trunk/] [linux/] [linux-2.4/] [drivers/] [pcmcia/] [sibyte_generic.c] - Rev 1765
Compare with Previous | Blame | View Log
/* * Copyright (C) 2003 Broadcom Corporation * originally contributed to SiByte, Inc as * "sb1250pc.c 0.10 (Stanley Chen & James Liao)" * * 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. */ /* * Notes / Apologies: * - only ATA cards tested so far * - requires hack in cs.c to avoid poking the CISCOR register * - ack_intr routine might be improved to avoid error msgs. * - remove and re-insert doesn't work (crash or fail to probe drive) */ #include <linux/config.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/ioport.h> #include <linux/sched.h> #include <linux/proc_fs.h> #include <linux/timer.h> #include <linux/ide.h> #include <pcmcia/version.h> #include <pcmcia/cs_types.h> #include <pcmcia/ss.h> #include <pcmcia/bulkmem.h> #include <pcmcia/cistpl.h> #include <pcmcia/cs.h> #include "cs_internal.h" #include <asm/io.h> #include <asm/sibyte/board.h> #include <asm/sibyte/sb1250_regs.h> #include <asm/sibyte/sb1250_scd.h> #include <asm/sibyte/sb1250_int.h> #include <asm/sibyte/sb1250_genbus.h> #include <asm/sibyte/64bit.h> #define PFX "sibyte-pcmcia: " MODULE_AUTHOR("Kip Walker, Stanley Chen & James Liao"); MODULE_DESCRIPTION("SiByte PCMCIA socket driver"); #undef PCMCIA_DEBUG #ifdef PCMCIA_DEBUG #define DPRINTK(args...) do { printk(KERN_DEBUG args); } while (0) #else #define DPRINTK(n, args...) do { } while (0) #endif static unsigned long sb_pcmcia_base = PCMCIA_PHYS; static unsigned long sb_pcmcia_size; #define SIBYTE_CS_REG(pcaddr) (IO_SPACE_BASE + sb_pcmcia_base - mips_io_port_base + pcaddr) #define SB_PC_PORT 0xff00 extern void sibyte_set_ideops(ide_hwif_t *hwif); /* The card status change interrupt */ static int cs_irq = K_INT_PCMCIA; /* Memory map windows */ static struct pccard_mem_map sibyte_memmap[MAX_WIN]; static struct pccard_io_map sibyte_iomap[MAX_IO_WIN]; /*====================================================================*/ /* Socket structures */ /*====================================================================*/ static void sb1250pc_interrupt(int irq, void *dev, struct pt_regs *regs); static struct pccard_operations sb1250pc_operations; typedef struct socket_handler_t { void (*handler)(void *info, u_int events); void *info; } socket_handler_t; static socket_handler_t socket_handler; /* * cap features: * full 32-bit addressing for 16-bit PCcard memory windows * 16-bit card memory and IO accesses need bus_ops * only 16-bit PCcards * align memory windows * statically mapped memory windows */ static socket_cap_t sb1250pc_cap = { features: (SS_CAP_PAGE_REGS | // SS_CAP_VIRTUAL_BUS | SS_CAP_PCCARD | SS_CAP_MEM_ALIGN | SS_CAP_STATIC_MAP), irq_mask: 0, /* tell ide layer to take PCI irq */ map_size: 0x4000000, /* 64MB minimum window size (What *should* this be?)*/ io_offset: SB_PC_PORT, /* This is ide5 -- just a special token for ide-sibyte */ pci_irq: K_INT_PC_READY, /* XXXKW This serves as IREQ# for CompactFlash */ cb_dev: NULL, bus: NULL }; /*====================================================================*/ /* Useful macros */ /*====================================================================*/ #define READ_PHYSADDR(addr) (*(volatile u32 *)(KSEG1ADDR(addr))) #define WRITE_PHYSADDR(addr, data) (*(volatile u32 *)(KSEG1ADDR(addr))) = (data) #define READ_CSR32(reg) csr_in32(IO_SPACE_BASE + (reg)) #define WRITE_CSR32(data, reg) csr_out32(data, IO_SPACE_BASE + (reg)) #define sb1250pc_write_config(data) WRITE_CSR32(data, A_IO_PCMCIA_CFG) #define sb1250pc_read_config() READ_CSR32(A_IO_PCMCIA_CFG) #define sb1250pc_read_status() READ_CSR32(A_IO_PCMCIA_STATUS) #define CARDPRESENT(s) (((s) & (M_PCMCIA_STATUS_CD1 | M_PCMCIA_STATUS_CD2)) == 0) int sb_pcmcia_ack_intr(struct hwif_s *hwif) { /* * XXXKW verify interrupt and return appropriate value? * Simple check of the bit in A_GPIO_READ didn't DTRT */ /* Clear out the GPIO edge detector */ WRITE_CSR32(1 << K_GPIO_PC_READY, A_GPIO_CLR_EDGE); return 1; } /*====================================================================*/ /* Interrupt handling */ /*====================================================================*/ static unsigned int pending_events; static spinlock_t pending_event_lock = SPIN_LOCK_UNLOCKED; static void sb1250pc_bh(void *dummy) { unsigned int events; spin_lock_irq(&pending_event_lock); events = pending_events; pending_events = 0; spin_unlock_irq(&pending_event_lock); if (socket_handler.handler) socket_handler.handler(socket_handler.info, events); } static struct tq_struct sb1250pc_task = { routine: sb1250pc_bh }; static void sb1250pc_interrupt(int irq, void *dev, struct pt_regs *regs) { unsigned int events = 0; uint32_t status; status = sb1250pc_read_status(); if (status & M_PCMCIA_STATUS_CDCHG) { events = SS_DETECT; if (CARDPRESENT(status)) { events |= SS_INSERTION; } else { events |= SS_EJECTION; } } #if 0 /* XXXKW ignore everything but CD? */ if (status & M_PCMCIA_STATUS_RDYCHG) { if (status & M_PCMCIA_STATUS_RDY) { /* XXXKW if ide, ack the interrupt? */ events |= SS_READY; } } #endif if (events) { DPRINTK(PFX " passing %x to bh\n", events); /* Pass the events off to the bottom-half */ spin_lock(&pending_event_lock); pending_events |= events; spin_unlock(&pending_event_lock); schedule_task(&sb1250pc_task); } } /*====================================================================*/ /* PC Card Operations */ /*====================================================================*/ static int sb1250pc_register_callback(unsigned int lsock, void (*handler)(void *, unsigned int), void * info) { DPRINTK(PFX "sb1250pc_register_callback(%d)\n", lsock); socket_handler.handler = handler; socket_handler.info = info; if (handler == NULL) { MOD_DEC_USE_COUNT; } else { MOD_INC_USE_COUNT; } return 0; } /*====================================================================*/ static int sb1250pc_get_status(unsigned int lsock, u_int *value) { u_int val; uint32_t status; #if PCMCIA_DEBUG u32 config; #endif status = sb1250pc_read_status(); #if PCMCIA_DEBUG config = sb1250pc_read_config(); #endif val = CARDPRESENT(status) ? SS_DETECT : 0; val |= (status & M_PCMCIA_CFG_RESET) ? SS_RESET : 0; val |= (status & (M_PCMCIA_STATUS_3VEN | M_PCMCIA_STATUS_5VEN)) ? SS_POWERON : 0; val |= (status & M_PCMCIA_STATUS_RDY) ? SS_READY : 0; val |= (status & M_PCMCIA_STATUS_WP) ? SS_WRPROT : 0; val |= ((status & M_PCMCIA_STATUS_VS2) && (~status & M_PCMCIA_STATUS_VS1)) ? SS_3VCARD : 0; val |= (status & (M_PCMCIA_STATUS_CDCHG | M_PCMCIA_STATUS_WPCHG | M_PCMCIA_STATUS_RDYCHG)) ? SS_STSCHG : 0; /* XXXKW SS_INSERTION on cdchange? */ DPRINTK(PFX "GetStatus(%d) = %x", lsock, val); #if PCMCIA_DEBUG DPRINTK(" [config(0x%4.4x) status(0x%4.4x)]", config, status); #endif DPRINTK("\n"); *value = val; return 0; } /*====================================================================*/ static int sb1250pc_inquire_socket(unsigned int lsock, socket_cap_t *cap) { *cap = sb1250pc_cap; DPRINTK(PFX "InquireSocket(%d) = features 0x%4.4x, irq_mask " "0x%4.4x, map_size 0x%4.4x\n", lsock, cap->features, cap->irq_mask, cap->map_size); return 0; } /*====================================================================*/ // This garbage function never seems to get called... static int sb1250pc_get_socket(unsigned int lsock, socket_state_t *state) { DPRINTK(PFX "Does GetSocket(%d) ever get called???", lsock); return -1; } /* sb1250pc_get_socket */ /*====================================================================*/ static uint32_t sibyte_set_power(uint32_t config, int vcc) { config &= ~(M_PCMCIA_CFG_3VEN | M_PCMCIA_CFG_5VEN); if (vcc == 33) { config |= M_PCMCIA_CFG_3VEN; } else if (vcc == 50) { config |= M_PCMCIA_CFG_5VEN; } sb1250pc_write_config(config); return config; } static int sb1250pc_set_socket(unsigned int lsock, socket_state_t *state) { uint32_t config; DPRINTK(PFX "SetSocket(%d, flags %#3.3x, Vcc %d, Vpp %d, " "io_irq %d, csc_mask %#2.2x)\n", lsock, state->flags, state->Vcc, state->Vpp, state->io_irq, state->csc_mask); config = sb1250pc_read_config(); config = sibyte_set_power(config, state->Vcc); if (state->flags & SS_DEBOUNCED) state->flags &= ~SS_DEBOUNCED; /* SS_DEBOUNCED is oneshot */ /* XXXKW SS_OUTPUT_ENA? */ /* XXXKW SS_PWR_AUTO? */ /* XXXKW SS_IOCARD? */ if (state->csc_mask & SS_DETECT) config &= ~M_PCMCIA_CFG_CDMASK; else config |= M_PCMCIA_CFG_CDMASK; config &= ~M_PCMCIA_CFG_RESET; if (state->flags & SS_RESET) { DPRINTK(PFX " resetting PCMCIA\n"); config |= M_PCMCIA_CFG_RESET; } sb1250pc_write_config(config); DPRINTK(PFX " new config: %x\n", sb1250pc_read_config()); return 0; } /*====================================================================*/ static int sb1250pc_get_io_map(unsigned int lsock, struct pccard_io_map *io) { *io = sibyte_iomap[io->map]; DPRINTK(PFX "GetIOMap(%d, %d) = %#2.2x, %d ns, " "%#4.4x-%#4.4x\n", lsock, io->map, io->flags, io->speed, io->start, io->stop); return 0; } /* sb1250pc_get_io_map */ static int sb1250pc_set_io_map(unsigned int lsock, struct pccard_io_map *io) { unsigned int speed; unsigned long start; u32 config; /* SB1250 uses direct mapping */ DPRINTK(PFX "SetIOMap(%d, %d, %#2.2x, %d ns, " "%#4.4x-%#4.4x) called\n", lsock, io->map, io->flags, io->speed, io->start, io->stop); if (io->map >= MAX_IO_WIN) { DPRINTK(KERN_ERR PFX "map (%d) out of range\n", io->map); return -1; } if (io->flags & MAP_ACTIVE) { speed = (io->speed > 0) ? io->speed : 255; } config = sb1250pc_read_config(); if (io->flags & MAP_ATTRIB) { DPRINTK(PFX " Setting pcmcia_cfg_reg to 1 (Attribute Mode)\n"); config |= M_PCMCIA_CFG_ATTRMEM; } else { DPRINTK(PFX " Setting pcmcia_cfg_reg to 0 (Data Mode)\n"); config &= ~M_PCMCIA_CFG_ATTRMEM; } sb1250pc_write_config(config); start = io->start; if (io->stop == 1) { io->stop = PAGE_SIZE - 1; } if (io->start == 0) io->start = sb_pcmcia_base; io->stop = io->start + (io->stop - start); sibyte_iomap[io->map] = *io; DPRINTK(PFX "SetIOMap(%d, %d, %#2.2x, %d ns, " "%#4.4x-%#4.4x) returns\n", lsock, io->map, io->flags, io->speed, io->start, io->stop); return 0; } /* sb1250pc_set_io_map */ /*====================================================================*/ static int sb1250pc_get_mem_map(unsigned int lsock, struct pccard_mem_map *mem) { if(mem->map >= MAX_WIN) return -EINVAL; *mem = sibyte_memmap[mem->map]; DPRINTK(PFX "GetMemMap(%d, mem[%d, %#2.2x, %d ns, " "%#5.5lx-%#5.5lx, %#5.5x) called\n", lsock, mem->map, mem->flags, mem->speed, mem->sys_start, mem->sys_stop, mem->card_start); return 0; } static int sb1250pc_set_mem_map(unsigned int lsock, struct pccard_mem_map *mem) { u32 old_config, new_config; if (mem->map >= MAX_WIN) { DPRINTK(KERN_ERR PFX "map (%d) out of range\n", mem->map); return -1; } if (mem->sys_start == 0) mem->sys_start = mem->card_start + sb_pcmcia_base; if (mem->sys_stop == 0) mem->sys_stop = mem->sys_start + sb_pcmcia_size - 1; old_config = sb1250pc_read_config(); DPRINTK(PFX " Setting mem_map %p\n", mem); if (mem->flags & MAP_ATTRIB) { DPRINTK(PFX " Setting pcmcia_cfg_reg to 1 (Attribute Mode)\n"); new_config = old_config | M_PCMCIA_CFG_ATTRMEM; } else { DPRINTK(PFX " Setting pcmcia_cfg_reg to 0 (Data Mode)\n"); new_config = old_config & ~M_PCMCIA_CFG_ATTRMEM; } if (new_config != old_config) sb1250pc_write_config(new_config); sibyte_memmap[mem->map] = *mem; DPRINTK(PFX "SetMemMap(%d, mem[%d, %#2.2x, %d ns], " "%#5.5lx-%#5.5lx, %#5.5x)\n", lsock, mem->map, mem->flags, mem->speed, mem->sys_start, mem->sys_stop, mem->card_start); return 0; } /*====================================================================*/ #ifdef CONFIG_PROC_FS /* sb1250pc_proc_status() * Implements the /proc/bus/pccard/??/status file. * * Returns: the number of characters added to the buffer * * Be aware that reading status clears the "change" bits; this seems * unlikely to bite us by making us miss interrupts. */ static int sb1250pc_proc_status(char *buf, char **start, off_t pos, int count, int *eof, void *data) { char *p = buf; u32 addr, temp; u32 status, config; //unsigned int sock = (unsigned int) data; config = sb1250pc_read_config(); status = sb1250pc_read_status(); p += sprintf(p, "config(0x%4.4x) status(0x%4.4x)\n", config, status); for (addr = sb_pcmcia_base; addr < sb_pcmcia_base + 8; addr+=4) { temp = READ_PHYSADDR(addr); p += sprintf(p, " Looking up addr 0x%x: 0x%8.8x\n", addr, temp); } return p - buf; } static void sb1250pc_proc_setup(unsigned int sock, struct proc_dir_entry *base) { struct proc_dir_entry *entry; if ((entry = create_proc_entry("sb1250pc", 0, base)) == NULL) { printk(KERN_ERR PFX "Unable to install \"sb1250pc\" procfs entry\n"); return; } else printk(KERN_INFO PFX "Setting up \"sb1250pc\" procfs entry\n"); entry->read_proc = sb1250pc_proc_status; entry->data = (void *) sock; } #endif /*====================================================================*/ static int sibyte_pcmcia_initted = 0; static int sb1250pc_init(unsigned int s) { u32 config, status; DPRINTK(PFX "Initializing SB1250 PCMCIA:\n"); /* Read status to clear interrupt sources */ status = sb1250pc_read_status(); /* * Before getting setting up the IRQ, set the config: * reset off, auto-power off * interrupt mask: WP, CD, Ready off */ config = sb1250pc_read_config(); config = M_PCMCIA_CFG_CDMASK | M_PCMCIA_CFG_WPMASK | M_PCMCIA_CFG_RDYMASK; sb1250pc_write_config(config); if (!sibyte_pcmcia_initted) { uint32_t gpio_ctrl; /* Set up the GPIO for PC_READY for use in ide-cs */ gpio_ctrl = READ_CSR32(A_GPIO_INT_TYPE); gpio_ctrl &= ~M_GPIO_INTR_TYPEX(K_GPIO_PC_READY); gpio_ctrl |= V_GPIO_INTR_TYPEX(K_GPIO_PC_READY, K_GPIO_INTR_EDGE); WRITE_CSR32(gpio_ctrl, A_GPIO_INT_TYPE); WRITE_CSR32(1 << K_GPIO_PC_READY, A_GPIO_CLR_EDGE); /* Invert to get busy->ready transition */ gpio_ctrl = READ_CSR32(A_GPIO_INPUT_INVERT); gpio_ctrl |= 1 << K_GPIO_PC_READY; WRITE_CSR32(gpio_ctrl, A_GPIO_INPUT_INVERT); /* Should not be any pending since we masked all sources */ if (request_irq(cs_irq, sb1250pc_interrupt, 0, "pcmcia", NULL)) return -ENODEV; DPRINTK(PFX " IRQ %d registered\n", cs_irq); sibyte_pcmcia_initted = 1; } status = sb1250pc_read_status(); DPRINTK(PFX " config(0x%4.4x) status(0x%4.4x)\n", config, status); sb1250pc_set_socket(s, &dead_socket); return 0; } static int sb1250pc_suspend(unsigned int sock) { free_irq(cs_irq, NULL); DPRINTK(KERN_INFO PFX " IRQ %d freed\n", cs_irq); return sb1250pc_set_socket(sock, &dead_socket); } static struct pccard_operations sb1250pc_operations = { sb1250pc_init, sb1250pc_suspend, sb1250pc_register_callback, sb1250pc_inquire_socket, sb1250pc_get_status, sb1250pc_get_socket, sb1250pc_set_socket, sb1250pc_get_io_map, sb1250pc_set_io_map, sb1250pc_get_mem_map, sb1250pc_set_mem_map, #ifdef CONFIG_PROC_FS sb1250pc_proc_setup #endif }; /* * XXXKW This is a hack. The ide-cs stuff seems to leave us in * Attribute mode. Since I know that a SELECT_DRIVE will happen as * the first I/O access, use this opportunity to enter data mode. */ static void sibyte_pcmcia_selectproc(ide_drive_t *drive) { sb1250pc_write_config(sb1250pc_read_config() & ~M_PCMCIA_CFG_ATTRMEM); } static int sibyte_pc_prep_ide(void) { int i; ide_hwif_t *hwif = NULL; /* Stake early claim on an ide_hwif */ for (i = 0; i < MAX_HWIFS; i++) { if (!ide_hwifs[i].io_ports[IDE_DATA_OFFSET]) { hwif = &ide_hwifs[i]; break; } } if (hwif == NULL) { printk("No space for SiByte onboard PCMCIA driver in ide_hwifs[]. Not enabled.\n"); return 1; } /* * Prime the hwif with port values, so when a card is * detected, the 'io_offset' from the capabilities will lead * it here */ hwif->hw.io_ports[IDE_DATA_OFFSET] = SIBYTE_CS_REG(0); hwif->hw.io_ports[IDE_ERROR_OFFSET] = SIBYTE_CS_REG(1); hwif->hw.io_ports[IDE_NSECTOR_OFFSET] = SIBYTE_CS_REG(2); hwif->hw.io_ports[IDE_SECTOR_OFFSET] = SIBYTE_CS_REG(3); hwif->hw.io_ports[IDE_LCYL_OFFSET] = SIBYTE_CS_REG(4); hwif->hw.io_ports[IDE_HCYL_OFFSET] = SIBYTE_CS_REG(5); hwif->hw.io_ports[IDE_SELECT_OFFSET] = SIBYTE_CS_REG(6); hwif->hw.io_ports[IDE_STATUS_OFFSET] = SIBYTE_CS_REG(7); hwif->hw.io_ports[IDE_CONTROL_OFFSET] = SIBYTE_CS_REG(6); /* XXXKW ? */ hwif->hw.ack_intr = sb_pcmcia_ack_intr; hwif->selectproc = sibyte_pcmcia_selectproc; hwif->hold = 1; hwif->mmio = 2; sibyte_set_ideops(&ide_hwifs[i]); printk("SiByte onboard PCMCIA-IDE configured as device %i\n", i); return 0; } static int __init sibyte_pcmcia_init(void) { servinfo_t info; u32 addr, temp; u64 cfg; CardServices(GetCardServicesInfo, &info); if (info.Revision != CS_RELEASE_CODE) { printk(KERN_ERR PFX "Card Services release does not match!\n"); return -1; } cfg = in64(IO_SPACE_BASE + A_SCD_SYSTEM_CFG); if (!(cfg & M_SYS_PCMCIA_ENABLE)) { printk(KERN_INFO PFX "chip not configured for PCMCIA\n"); return -1; } /* Find memory base address and size */ addr = A_IO_EXT_REG(R_IO_EXT_REG(R_IO_EXT_MULT_SIZE, PCMCIA_CS)); temp = G_IO_MULT_SIZE(csr_in32(IO_SPACE_BASE + addr)); printk(PFX "Looking up addr 0x%x: 0x%4.4x (IO size)\n", addr, temp); sb_pcmcia_size = (temp+1) << S_IO_REGSIZE; addr = A_IO_EXT_REG(R_IO_EXT_REG(R_IO_EXT_START_ADDR, PCMCIA_CS)); temp = G_IO_START_ADDR(csr_in32(KSEG1|addr)); printk(PFX "Looking up addr 0x%x: 0x%4.4x (IO Base Address)\n", addr, temp); if (temp << S_IO_ADDRBASE != PCMCIA_PHYS) panic(PFX "pcmcia base doesn't match gencs value\n"); /* check and request memory region */ if (check_mem_region(sb_pcmcia_base, sb_pcmcia_size)) { printk(KERN_ERR PFX "Can't request memory region?\n"); } else { request_mem_region(sb_pcmcia_base, sb_pcmcia_size, "sibyte-pcmcia"); printk(PFX "Memory region 0x%8.8lx of size 0x%8.8lx requested.\n", sb_pcmcia_base, sb_pcmcia_size); } /* register with socket services */ if (register_ss_entry(1, &sb1250pc_operations)) { printk(KERN_ERR PFX "register_ss_entry() failed\n"); release_region(sb_pcmcia_base, sb_pcmcia_size); return -ENODEV; } if (!sibyte_pc_prep_ide()) { /* XXXKW hack for ide-cs warning squash */ request_region(sb1250pc_cap.io_offset, 16, "ide-cs"); request_mem_region(SIBYTE_CS_REG(0), 8, "sibyte-ide-cs"); } return 0; } static void __exit sibyte_pcmcia_exit(void) { /* XXXKW untested as module */ unregister_ss_entry(&sb1250pc_operations); release_region(sb_pcmcia_base, sb_pcmcia_size); } module_init(sibyte_pcmcia_init); module_exit(sibyte_pcmcia_exit);