diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/pcmcia | |
download | linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.bz2 linux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/pcmcia')
61 files changed, 24437 insertions, 0 deletions
diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig new file mode 100644 index 000000000000..6375ebc85020 --- /dev/null +++ b/drivers/pcmcia/Kconfig @@ -0,0 +1,203 @@ +# +# PCMCIA bus subsystem configuration +# +# Right now the non-CardBus choices are not supported +# by the integrated kernel driver. +# + +menu "PCCARD (PCMCIA/CardBus) support" + +config PCCARD + tristate "PCCard (PCMCIA/CardBus) support" + select HOTPLUG + ---help--- + Say Y here if you want to attach PCMCIA- or PC-cards to your Linux + computer. These are credit-card size devices such as network cards, + modems or hard drives often used with laptops computers. There are + actually two varieties of these cards: the older 16 bit PCMCIA cards + and the newer 32 bit CardBus cards. + + To compile this driver as modules, choose M here: the + module will be called pcmcia_core. + +if PCCARD + +config PCMCIA_DEBUG + bool "Enable PCCARD debugging" + help + Say Y here to enable PCMCIA subsystem debugging. You + will need to choose the debugging level either via the + kernel command line, or module options depending whether + you build the PCMCIA as modules. + + The kernel command line options are: + pcmcia_core.pc_debug=N + ds.pc_debug=N + sa11xx_core.pc_debug=N + + The module option is called pc_debug=N + + In all the above examples, N is the debugging verbosity + level. + +config PCMCIA + tristate "16-bit PCMCIA support" + default y + ---help--- + This option enables support for 16-bit PCMCIA cards. Most older + PC-cards are such 16-bit PCMCIA cards, so unless you know you're + only using 32-bit CardBus cards, say Y or M here. + + To use 16-bit PCMCIA cards, you will need supporting software from + David Hinds' pcmcia-cs package (see the file <file:Documentation/Changes> + for location). Please also read the PCMCIA-HOWTO, available from + <http://www.tldp.org/docs.html#howto>. + + To compile this driver as modules, choose M here: the + module will be called pcmcia. + + If unsure, say Y. + +config CARDBUS + bool "32-bit CardBus support" + depends on PCI + default y + ---help--- + CardBus is a bus mastering architecture for PC-cards, which allows + for 32 bit PC-cards (the original PCMCIA standard specifies only + a 16 bit wide bus). Many newer PC-cards are actually CardBus cards. + + To use 32 bit PC-cards, you also need a CardBus compatible host + bridge. Virtually all modern PCMCIA bridges do this, and most of + them are "yenta-compatible", so say Y or M there, too. + + If unsure, say Y. + +comment "PC-card bridges" + +config YENTA + tristate "CardBus yenta-compatible bridge support" + depends on PCI +#fixme: remove dependendcy on CARDBUS + depends on CARDBUS + select PCCARD_NONSTATIC + ---help--- + This option enables support for CardBus host bridges. Virtually + all modern PCMCIA bridges are CardBus compatible. A "bridge" is + the hardware inside your computer that PCMCIA cards are plugged + into. + + To compile this driver as modules, choose M here: the + module will be called yenta_socket. + + If unsure, say Y. + +config PD6729 + tristate "Cirrus PD6729 compatible bridge support" + depends on PCMCIA && PCI + select PCCARD_NONSTATIC + help + This provides support for the Cirrus PD6729 PCI-to-PCMCIA bridge + device, found in some older laptops and PCMCIA card readers. + +config I82092 + tristate "i82092 compatible bridge support" + depends on PCMCIA && PCI + select PCCARD_NONSTATIC + help + This provides support for the Intel I82092AA PCI-to-PCMCIA bridge device, + found in some older laptops and more commonly in evaluation boards for the + chip. + +config I82365 + tristate "i82365 compatible bridge support" + depends on PCMCIA && ISA + select PCCARD_NONSTATIC + help + Say Y here to include support for ISA-bus PCMCIA host bridges that + are register compatible with the Intel i82365. These are found on + older laptops and ISA-bus card readers for desktop systems. A + "bridge" is the hardware inside your computer that PCMCIA cards are + plugged into. If unsure, say N. + +config TCIC + tristate "Databook TCIC host bridge support" + depends on PCMCIA + select PCCARD_NONSTATIC + help + Say Y here to include support for the Databook TCIC family of PCMCIA + host bridges. These are only found on a handful of old systems. + "Bridge" is the name used for the hardware inside your computer that + PCMCIA cards are plugged into. If unsure, say N. + +config HD64465_PCMCIA + tristate "HD64465 host bridge support" + depends on HD64465 && PCMCIA + +config PCMCIA_AU1X00 + tristate "Au1x00 pcmcia support" + depends on SOC_AU1X00 && PCMCIA + +config PCMCIA_SA1100 + tristate "SA1100 support" + depends on ARM && ARCH_SA1100 && PCMCIA + help + Say Y here to include support for SA11x0-based PCMCIA or CF + sockets, found on HP iPAQs, Yopy, and other StrongARM(R)/ + Xscale(R) embedded machines. + + This driver is also available as a module called sa1100_cs. + +config PCMCIA_SA1111 + tristate "SA1111 support" + depends on ARM && ARCH_SA1100 && SA1111 && PCMCIA + help + Say Y here to include support for SA1111-based PCMCIA or CF + sockets, found on the Jornada 720, Graphicsmaster and other + StrongARM(R)/Xscale(R) embedded machines. + + This driver is also available as a module called sa1111_cs. + +config PCMCIA_PXA2XX + tristate "PXA2xx support" + depends on ARM && ARCH_PXA && PCMCIA + help + Say Y here to include support for the PXA2xx PCMCIA controller + +config PCMCIA_PROBE + bool + default y if ISA && !ARCH_SA1100 && !ARCH_CLPS711X + +config M32R_PCC + bool "M32R PCMCIA I/F" + depends on M32R && CHIP_M32700 && PCMCIA + help + Say Y here to use the M32R PCMCIA controller. + +config M32R_CFC + bool "M32R CF I/F Controller" + depends on M32R && (PLAT_USRV || PLAT_M32700UT || PLAT_MAPPI2 || PLAT_OPSPUT) + help + Say Y here to use the M32R CompactFlash controller. + +config M32R_CFC_NUM + int "M32R CF I/F number" + depends on M32R_CFC + default "1" if PLAT_USRV || PLAT_M32700UT || PLAT_MAPPI2 || PLAT_OPSPUT + help + Set the number of M32R CF slots. + +config PCMCIA_VRC4171 + tristate "NEC VRC4171 Card Controllers support" + depends on VRC4171 && PCMCIA + +config PCMCIA_VRC4173 + tristate "NEC VRC4173 CARDU support" + depends on CPU_VR41XX && PCI && PCMCIA + +config PCCARD_NONSTATIC + tristate + +endif # PCCARD + +endmenu diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile new file mode 100644 index 000000000000..50c29361bc5f --- /dev/null +++ b/drivers/pcmcia/Makefile @@ -0,0 +1,65 @@ +# +# Makefile for the kernel pcmcia subsystem (c/o David Hinds) +# + +ifeq ($(CONFIG_PCMCIA_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif + +pcmcia_core-y += cs.o cistpl.o rsrc_mgr.o socket_sysfs.o +pcmcia_core-$(CONFIG_CARDBUS) += cardbus.o +obj-$(CONFIG_PCCARD) += pcmcia_core.o + +pcmcia-y += ds.o pcmcia_compat.o +obj-$(CONFIG_PCMCIA) += pcmcia.o + +obj-$(CONFIG_PCCARD_NONSTATIC) += rsrc_nonstatic.o + + +# socket drivers + +obj-$(CONFIG_YENTA) += yenta_socket.o + +obj-$(CONFIG_PD6729) += pd6729.o +obj-$(CONFIG_I82365) += i82365.o +obj-$(CONFIG_I82092) += i82092.o +obj-$(CONFIG_TCIC) += tcic.o +obj-$(CONFIG_HD64465_PCMCIA) += hd64465_ss.o +obj-$(CONFIG_PCMCIA_SA1100) += sa11xx_core.o sa1100_cs.o +obj-$(CONFIG_PCMCIA_SA1111) += sa11xx_core.o sa1111_cs.o +obj-$(CONFIG_PCMCIA_PXA2XX) += pxa2xx_core.o pxa2xx_cs.o +obj-$(CONFIG_M32R_PCC) += m32r_pcc.o +obj-$(CONFIG_M32R_CFC) += m32r_cfc.o +obj-$(CONFIG_PCMCIA_AU1X00) += au1x00_ss.o +obj-$(CONFIG_PCMCIA_VRC4171) += vrc4171_card.o +obj-$(CONFIG_PCMCIA_VRC4173) += vrc4173_cardu.o + +sa11xx_core-y += soc_common.o sa11xx_base.o +pxa2xx_core-y += soc_common.o pxa2xx_base.o + +au1x00_ss-y += au1000_generic.o +au1x00_ss-$(CONFIG_MIPS_PB1000) += au1000_pb1x00.o +au1x00_ss-$(CONFIG_MIPS_PB1100) += au1000_pb1x00.o +au1x00_ss-$(CONFIG_MIPS_PB1500) += au1000_pb1x00.o +au1x00_ss-$(CONFIG_MIPS_DB1000) += au1000_db1x00.o +au1x00_ss-$(CONFIG_MIPS_DB1100) += au1000_db1x00.o +au1x00_ss-$(CONFIG_MIPS_DB1500) += au1000_db1x00.o +au1x00_ss-$(CONFIG_MIPS_DB1550) += au1000_db1x00.o +au1x00_ss-$(CONFIG_MIPS_XXS1500) += au1000_xxs1500.o + +sa1111_cs-y += sa1111_generic.o +sa1111_cs-$(CONFIG_ASSABET_NEPONSET) += sa1100_neponset.o +sa1111_cs-$(CONFIG_SA1100_BADGE4) += sa1100_badge4.o +sa1111_cs-$(CONFIG_SA1100_JORNADA720) += sa1100_jornada720.o + +sa1100_cs-y += sa1100_generic.o +sa1100_cs-$(CONFIG_SA1100_ASSABET) += sa1100_assabet.o +sa1100_cs-$(CONFIG_SA1100_CERF) += sa1100_cerf.o +sa1100_cs-$(CONFIG_SA1100_H3600) += sa1100_h3600.o +sa1100_cs-$(CONFIG_SA1100_SHANNON) += sa1100_shannon.o +sa1100_cs-$(CONFIG_SA1100_SIMPAD) += sa1100_simpad.o + +pxa2xx_cs-$(CONFIG_ARCH_LUBBOCK) += pxa2xx_lubbock.o sa1111_generic.o +pxa2xx_cs-$(CONFIG_MACH_MAINSTONE) += pxa2xx_mainstone.o +pxa2xx_cs-$(CONFIG_PXA_SHARPSL) += pxa2xx_sharpsl.o + diff --git a/drivers/pcmcia/au1000_db1x00.c b/drivers/pcmcia/au1000_db1x00.c new file mode 100644 index 000000000000..42cf8bfbcc98 --- /dev/null +++ b/drivers/pcmcia/au1000_db1x00.c @@ -0,0 +1,288 @@ +/* + * + * Alchemy Semi Db1x00 boards specific pcmcia routines. + * + * Copyright 2002 MontaVista Software Inc. + * Author: MontaVista Software, Inc. + * ppopov@mvista.com or source@mvista.com + * + * Copyright 2004 Pete Popov, updated the driver to 2.6. + * Followed the sa11xx API and largely copied many of the hardware + * independent functions. + * + * ######################################################################## + * + * This program is free software; you can distribute it and/or modify it + * under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * ######################################################################## + * + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/init.h> + +#include <asm/irq.h> +#include <asm/signal.h> +#include <asm/mach-au1x00/au1000.h> +#include <asm/mach-db1x00/db1x00.h> + +#include "au1000_generic.h" + +#if 0 +#define debug(x,args...) printk(KERN_DEBUG "%s: " x, __func__ , ##args) +#else +#define debug(x,args...) +#endif + +static BCSR * const bcsr = (BCSR *)BCSR_KSEG1_ADDR; + +struct au1000_pcmcia_socket au1000_pcmcia_socket[PCMCIA_NUM_SOCKS]; +extern int au1x00_pcmcia_socket_probe(struct device *, struct pcmcia_low_level *, int, int); + +static int db1x00_pcmcia_hw_init(struct au1000_pcmcia_socket *skt) +{ +#ifdef CONFIG_MIPS_DB1550 + skt->irq = skt->nr ? AU1000_GPIO_5 : AU1000_GPIO_3; +#else + skt->irq = skt->nr ? AU1000_GPIO_5 : AU1000_GPIO_2; +#endif + return 0; +} + +static void db1x00_pcmcia_shutdown(struct au1000_pcmcia_socket *skt) +{ + bcsr->pcmcia = 0; /* turn off power */ + au_sync_delay(2); +} + +static void +db1x00_pcmcia_socket_state(struct au1000_pcmcia_socket *skt, struct pcmcia_state *state) +{ + u32 inserted; + unsigned char vs; + + state->ready = 0; + state->vs_Xv = 0; + state->vs_3v = 0; + state->detect = 0; + + switch (skt->nr) { + case 0: + vs = bcsr->status & 0x3; + inserted = !(bcsr->status & (1<<4)); + break; + case 1: + vs = (bcsr->status & 0xC)>>2; + inserted = !(bcsr->status & (1<<5)); + break; + default:/* should never happen */ + return; + } + + if (inserted) + debug("db1x00 socket %d: inserted %d, vs %d pcmcia %x\n", + skt->nr, inserted, vs, bcsr->pcmcia); + + if (inserted) { + switch (vs) { + case 0: + case 2: + state->vs_3v=1; + break; + case 3: /* 5V */ + break; + default: + /* return without setting 'detect' */ + printk(KERN_ERR "db1x00 bad VS (%d)\n", + vs); + } + state->detect = 1; + state->ready = 1; + } + else { + /* if the card was previously inserted and then ejected, + * we should turn off power to it + */ + if ((skt->nr == 0) && (bcsr->pcmcia & BCSR_PCMCIA_PC0RST)) { + bcsr->pcmcia &= ~(BCSR_PCMCIA_PC0RST | + BCSR_PCMCIA_PC0DRVEN | + BCSR_PCMCIA_PC0VPP | + BCSR_PCMCIA_PC0VCC); + au_sync_delay(10); + } + else if ((skt->nr == 1) && bcsr->pcmcia & BCSR_PCMCIA_PC1RST) { + bcsr->pcmcia &= ~(BCSR_PCMCIA_PC1RST | + BCSR_PCMCIA_PC1DRVEN | + BCSR_PCMCIA_PC1VPP | + BCSR_PCMCIA_PC1VCC); + au_sync_delay(10); + } + } + + state->bvd1=1; + state->bvd2=1; + state->wrprot=0; +} + +static int +db1x00_pcmcia_configure_socket(struct au1000_pcmcia_socket *skt, struct socket_state_t *state) +{ + u16 pwr; + int sock = skt->nr; + + debug("config_skt %d Vcc %dV Vpp %dV, reset %d\n", + sock, state->Vcc, state->Vpp, + state->flags & SS_RESET); + + /* pcmcia reg was set to zero at init time. Be careful when + * initializing a socket not to wipe out the settings of the + * other socket. + */ + pwr = bcsr->pcmcia; + pwr &= ~(0xf << sock*8); /* clear voltage settings */ + + state->Vpp = 0; + switch(state->Vcc){ + case 0: /* Vcc 0 */ + pwr |= SET_VCC_VPP(0,0,sock); + break; + case 50: /* Vcc 5V */ + switch(state->Vpp) { + case 0: + pwr |= SET_VCC_VPP(2,0,sock); + break; + case 50: + pwr |= SET_VCC_VPP(2,1,sock); + break; + case 12: + pwr |= SET_VCC_VPP(2,2,sock); + break; + case 33: + default: + pwr |= SET_VCC_VPP(0,0,sock); + printk("%s: bad Vcc/Vpp (%d:%d)\n", + __FUNCTION__, + state->Vcc, + state->Vpp); + break; + } + break; + case 33: /* Vcc 3.3V */ + switch(state->Vpp) { + case 0: + pwr |= SET_VCC_VPP(1,0,sock); + break; + case 12: + pwr |= SET_VCC_VPP(1,2,sock); + break; + case 33: + pwr |= SET_VCC_VPP(1,1,sock); + break; + case 50: + default: + pwr |= SET_VCC_VPP(0,0,sock); + printk("%s: bad Vcc/Vpp (%d:%d)\n", + __FUNCTION__, + state->Vcc, + state->Vpp); + break; + } + break; + default: /* what's this ? */ + pwr |= SET_VCC_VPP(0,0,sock); + printk(KERN_ERR "%s: bad Vcc %d\n", + __FUNCTION__, state->Vcc); + break; + } + + bcsr->pcmcia = pwr; + au_sync_delay(300); + + if (sock == 0) { + if (!(state->flags & SS_RESET)) { + pwr |= BCSR_PCMCIA_PC0DRVEN; + bcsr->pcmcia = pwr; + au_sync_delay(300); + pwr |= BCSR_PCMCIA_PC0RST; + bcsr->pcmcia = pwr; + au_sync_delay(100); + } + else { + pwr &= ~(BCSR_PCMCIA_PC0RST | BCSR_PCMCIA_PC0DRVEN); + bcsr->pcmcia = pwr; + au_sync_delay(100); + } + } + else { + if (!(state->flags & SS_RESET)) { + pwr |= BCSR_PCMCIA_PC1DRVEN; + bcsr->pcmcia = pwr; + au_sync_delay(300); + pwr |= BCSR_PCMCIA_PC1RST; + bcsr->pcmcia = pwr; + au_sync_delay(100); + } + else { + pwr &= ~(BCSR_PCMCIA_PC1RST | BCSR_PCMCIA_PC1DRVEN); + bcsr->pcmcia = pwr; + au_sync_delay(100); + } + } + return 0; +} + +/* + * Enable card status IRQs on (re-)initialisation. This can + * be called at initialisation, power management event, or + * pcmcia event. + */ +void db1x00_socket_init(struct au1000_pcmcia_socket *skt) +{ + /* nothing to do for now */ +} + +/* + * Disable card status IRQs and PCMCIA bus on suspend. + */ +void db1x00_socket_suspend(struct au1000_pcmcia_socket *skt) +{ + /* nothing to do for now */ +} + +struct pcmcia_low_level db1x00_pcmcia_ops = { + .owner = THIS_MODULE, + + .hw_init = db1x00_pcmcia_hw_init, + .hw_shutdown = db1x00_pcmcia_shutdown, + + .socket_state = db1x00_pcmcia_socket_state, + .configure_socket = db1x00_pcmcia_configure_socket, + + .socket_init = db1x00_socket_init, + .socket_suspend = db1x00_socket_suspend +}; + +int __init au1x_board_init(struct device *dev) +{ + int ret = -ENODEV; + bcsr->pcmcia = 0; /* turn off power, if it's not already off */ + au_sync_delay(2); + ret = au1x00_pcmcia_socket_probe(dev, &db1x00_pcmcia_ops, 0, 2); + return ret; +} diff --git a/drivers/pcmcia/au1000_generic.c b/drivers/pcmcia/au1000_generic.c new file mode 100644 index 000000000000..9fa7f15d89e5 --- /dev/null +++ b/drivers/pcmcia/au1000_generic.c @@ -0,0 +1,579 @@ +/* + * + * Alchemy Semi Au1000 pcmcia driver + * + * Copyright 2001-2003 MontaVista Software Inc. + * Author: MontaVista Software, Inc. + * ppopov@embeddedalley.com or source@mvista.com + * + * Copyright 2004 Pete Popov, Embedded Alley Solutions, Inc. + * Updated the driver to 2.6. Followed the sa11xx API and largely + * copied many of the hardware independent functions. + * + * ######################################################################## + * + * This program is free software; you can distribute it and/or modify it + * under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * ######################################################################## + * + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/config.h> +#include <linux/cpufreq.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/notifier.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/device.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include <asm/mach-au1x00/au1000.h> +#include "au1000_generic.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Pete Popov <ppopov@embeddedalley.com>"); +MODULE_DESCRIPTION("Linux PCMCIA Card Services: Au1x00 Socket Controller"); + +#if 0 +#define debug(x,args...) printk(KERN_DEBUG "%s: " x, __func__ , ##args) +#else +#define debug(x,args...) +#endif + +#define MAP_SIZE 0x100000 +extern struct au1000_pcmcia_socket au1000_pcmcia_socket[]; +#define PCMCIA_SOCKET(x) (au1000_pcmcia_socket + (x)) +#define to_au1000_socket(x) container_of(x, struct au1000_pcmcia_socket, socket) + +/* Some boards like to support CF cards as IDE root devices, so they + * grab pcmcia sockets directly. + */ +u32 *pcmcia_base_vaddrs[2]; +extern const unsigned long mips_io_port_base; + +DECLARE_MUTEX(pcmcia_sockets_lock); + +static int (*au1x00_pcmcia_hw_init[])(struct device *dev) = { + au1x_board_init, +}; + +static int +au1x00_pcmcia_skt_state(struct au1000_pcmcia_socket *skt) +{ + struct pcmcia_state state; + unsigned int stat; + + memset(&state, 0, sizeof(struct pcmcia_state)); + + skt->ops->socket_state(skt, &state); + + stat = state.detect ? SS_DETECT : 0; + stat |= state.ready ? SS_READY : 0; + stat |= state.wrprot ? SS_WRPROT : 0; + stat |= state.vs_3v ? SS_3VCARD : 0; + stat |= state.vs_Xv ? SS_XVCARD : 0; + stat |= skt->cs_state.Vcc ? SS_POWERON : 0; + + if (skt->cs_state.flags & SS_IOCARD) + stat |= state.bvd1 ? SS_STSCHG : 0; + else { + if (state.bvd1 == 0) + stat |= SS_BATDEAD; + else if (state.bvd2 == 0) + stat |= SS_BATWARN; + } + return stat; +} + +/* + * au100_pcmcia_config_skt + * + * Convert PCMCIA socket state to our socket configure structure. + */ +static int +au1x00_pcmcia_config_skt(struct au1000_pcmcia_socket *skt, socket_state_t *state) +{ + int ret; + + ret = skt->ops->configure_socket(skt, state); + if (ret == 0) { + skt->cs_state = *state; + } + + if (ret < 0) + debug("unable to configure socket %d\n", skt->nr); + + return ret; +} + +/* au1x00_pcmcia_sock_init() + * + * (Re-)Initialise the socket, turning on status interrupts + * and PCMCIA bus. This must wait for power to stabilise + * so that the card status signals report correctly. + * + * Returns: 0 + */ +static int au1x00_pcmcia_sock_init(struct pcmcia_socket *sock) +{ + struct au1000_pcmcia_socket *skt = to_au1000_socket(sock); + + debug("initializing socket %u\n", skt->nr); + + skt->ops->socket_init(skt); + return 0; +} + +/* + * au1x00_pcmcia_suspend() + * + * Remove power on the socket, disable IRQs from the card. + * Turn off status interrupts, and disable the PCMCIA bus. + * + * Returns: 0 + */ +static int au1x00_pcmcia_suspend(struct pcmcia_socket *sock) +{ + struct au1000_pcmcia_socket *skt = to_au1000_socket(sock); + + debug("suspending socket %u\n", skt->nr); + + skt->ops->socket_suspend(skt); + + return 0; +} + +static DEFINE_SPINLOCK(status_lock); + +/* + * au1x00_check_status() + */ +static void au1x00_check_status(struct au1000_pcmcia_socket *skt) +{ + unsigned int events; + + debug("entering PCMCIA monitoring thread\n"); + + do { + unsigned int status; + unsigned long flags; + + status = au1x00_pcmcia_skt_state(skt); + + spin_lock_irqsave(&status_lock, flags); + events = (status ^ skt->status) & skt->cs_state.csc_mask; + skt->status = status; + spin_unlock_irqrestore(&status_lock, flags); + + debug("events: %s%s%s%s%s%s\n", + events == 0 ? "<NONE>" : "", + events & SS_DETECT ? "DETECT " : "", + events & SS_READY ? "READY " : "", + events & SS_BATDEAD ? "BATDEAD " : "", + events & SS_BATWARN ? "BATWARN " : "", + events & SS_STSCHG ? "STSCHG " : ""); + + if (events) + pcmcia_parse_events(&skt->socket, events); + } while (events); +} + +/* + * au1x00_pcmcia_poll_event() + * Let's poll for events in addition to IRQs since IRQ only is unreliable... + */ +static void au1x00_pcmcia_poll_event(unsigned long dummy) +{ + struct au1000_pcmcia_socket *skt = (struct au1000_pcmcia_socket *)dummy; + debug("polling for events\n"); + + mod_timer(&skt->poll_timer, jiffies + AU1000_PCMCIA_POLL_PERIOD); + + au1x00_check_status(skt); +} + +/* au1x00_pcmcia_get_status() + * + * From the sa11xx_core.c: + * Implements the get_status() operation for the in-kernel PCMCIA + * service (formerly SS_GetStatus in Card Services). Essentially just + * fills in bits in `status' according to internal driver state or + * the value of the voltage detect chipselect register. + * + * As a debugging note, during card startup, the PCMCIA core issues + * three set_socket() commands in a row the first with RESET deasserted, + * the second with RESET asserted, and the last with RESET deasserted + * again. Following the third set_socket(), a get_status() command will + * be issued. The kernel is looking for the SS_READY flag (see + * setup_socket(), reset_socket(), and unreset_socket() in cs.c). + * + * Returns: 0 + */ +static int +au1x00_pcmcia_get_status(struct pcmcia_socket *sock, unsigned int *status) +{ + struct au1000_pcmcia_socket *skt = to_au1000_socket(sock); + + skt->status = au1x00_pcmcia_skt_state(skt); + *status = skt->status; + + return 0; +} + +/* au1x00_pcmcia_get_socket() + * Implements the get_socket() operation for the in-kernel PCMCIA + * service (formerly SS_GetSocket in Card Services). Not a very + * exciting routine. + * + * Returns: 0 + */ +static int +au1x00_pcmcia_get_socket(struct pcmcia_socket *sock, socket_state_t *state) +{ + struct au1000_pcmcia_socket *skt = to_au1000_socket(sock); + + debug("for sock %u\n", skt->nr); + *state = skt->cs_state; + return 0; +} + +/* au1x00_pcmcia_set_socket() + * Implements the set_socket() operation for the in-kernel PCMCIA + * service (formerly SS_SetSocket in Card Services). We more or + * less punt all of this work and let the kernel handle the details + * of power configuration, reset, &c. We also record the value of + * `state' in order to regurgitate it to the PCMCIA core later. + * + * Returns: 0 + */ +static int +au1x00_pcmcia_set_socket(struct pcmcia_socket *sock, socket_state_t *state) +{ + struct au1000_pcmcia_socket *skt = to_au1000_socket(sock); + + debug("for sock %u\n", skt->nr); + + debug("\tmask: %s%s%s%s%s%s\n\tflags: %s%s%s%s%s%s\n", + (state->csc_mask==0)?"<NONE>":"", + (state->csc_mask&SS_DETECT)?"DETECT ":"", + (state->csc_mask&SS_READY)?"READY ":"", + (state->csc_mask&SS_BATDEAD)?"BATDEAD ":"", + (state->csc_mask&SS_BATWARN)?"BATWARN ":"", + (state->csc_mask&SS_STSCHG)?"STSCHG ":"", + (state->flags==0)?"<NONE>":"", + (state->flags&SS_PWR_AUTO)?"PWR_AUTO ":"", + (state->flags&SS_IOCARD)?"IOCARD ":"", + (state->flags&SS_RESET)?"RESET ":"", + (state->flags&SS_SPKR_ENA)?"SPKR_ENA ":"", + (state->flags&SS_OUTPUT_ENA)?"OUTPUT_ENA ":""); + debug("\tVcc %d Vpp %d irq %d\n", + state->Vcc, state->Vpp, state->io_irq); + + return au1x00_pcmcia_config_skt(skt, state); +} + +int +au1x00_pcmcia_set_io_map(struct pcmcia_socket *sock, struct pccard_io_map *map) +{ + struct au1000_pcmcia_socket *skt = to_au1000_socket(sock); + unsigned int speed; + + if(map->map>=MAX_IO_WIN){ + debug("map (%d) out of range\n", map->map); + return -1; + } + + if(map->flags&MAP_ACTIVE){ + speed=(map->speed>0)?map->speed:AU1000_PCMCIA_IO_SPEED; + skt->spd_io[map->map] = speed; + } + + map->start=(ioaddr_t)(u32)skt->virt_io; + map->stop=map->start+MAP_SIZE; + return 0; + +} /* au1x00_pcmcia_set_io_map() */ + + +static int +au1x00_pcmcia_set_mem_map(struct pcmcia_socket *sock, struct pccard_mem_map *map) +{ + struct au1000_pcmcia_socket *skt = to_au1000_socket(sock); + unsigned short speed = map->speed; + + if(map->map>=MAX_WIN){ + debug("map (%d) out of range\n", map->map); + return -1; + } + + if (map->flags & MAP_ATTRIB) { + skt->spd_attr[map->map] = speed; + skt->spd_mem[map->map] = 0; + } else { + skt->spd_attr[map->map] = 0; + skt->spd_mem[map->map] = speed; + } + + if (map->flags & MAP_ATTRIB) { + map->static_start = skt->phys_attr + map->card_start; + } + else { + map->static_start = skt->phys_mem + map->card_start; + } + + debug("set_mem_map %d start %08lx card_start %08x\n", + map->map, map->static_start, map->card_start); + return 0; + +} /* au1x00_pcmcia_set_mem_map() */ + +static struct pccard_operations au1x00_pcmcia_operations = { + .init = au1x00_pcmcia_sock_init, + .suspend = au1x00_pcmcia_suspend, + .get_status = au1x00_pcmcia_get_status, + .get_socket = au1x00_pcmcia_get_socket, + .set_socket = au1x00_pcmcia_set_socket, + .set_io_map = au1x00_pcmcia_set_io_map, + .set_mem_map = au1x00_pcmcia_set_mem_map, +}; + +static const char *skt_names[] = { + "PCMCIA socket 0", + "PCMCIA socket 1", +}; + +struct skt_dev_info { + int nskt; +}; + +int au1x00_pcmcia_socket_probe(struct device *dev, struct pcmcia_low_level *ops, int first, int nr) +{ + struct skt_dev_info *sinfo; + int ret, i; + + sinfo = kmalloc(sizeof(struct skt_dev_info), GFP_KERNEL); + if (!sinfo) { + ret = -ENOMEM; + goto out; + } + + memset(sinfo, 0, sizeof(struct skt_dev_info)); + sinfo->nskt = nr; + + /* + * Initialise the per-socket structure. + */ + for (i = 0; i < nr; i++) { + struct au1000_pcmcia_socket *skt = PCMCIA_SOCKET(i); + memset(skt, 0, sizeof(*skt)); + + skt->socket.ops = &au1x00_pcmcia_operations; + skt->socket.owner = ops->owner; + skt->socket.dev.dev = dev; + + init_timer(&skt->poll_timer); + skt->poll_timer.function = au1x00_pcmcia_poll_event; + skt->poll_timer.data = (unsigned long)skt; + skt->poll_timer.expires = jiffies + AU1000_PCMCIA_POLL_PERIOD; + + skt->nr = first + i; + skt->irq = 255; + skt->dev = dev; + skt->ops = ops; + + skt->res_skt.name = skt_names[skt->nr]; + skt->res_io.name = "io"; + skt->res_io.flags = IORESOURCE_MEM | IORESOURCE_BUSY; + skt->res_mem.name = "memory"; + skt->res_mem.flags = IORESOURCE_MEM; + skt->res_attr.name = "attribute"; + skt->res_attr.flags = IORESOURCE_MEM; + + /* + * PCMCIA client drivers use the inb/outb macros to access the + * IO registers. Since mips_io_port_base is added to the + * access address of the mips implementation of inb/outb, + * we need to subtract it here because we want to access the + * I/O or MEM address directly, without going through this + * "mips_io_port_base" mechanism. + */ + if (i == 0) { + skt->virt_io = (void *) + (ioremap((phys_t)AU1X_SOCK0_IO, 0x1000) - + (u32)mips_io_port_base); + skt->phys_attr = AU1X_SOCK0_PSEUDO_PHYS_ATTR; + skt->phys_mem = AU1X_SOCK0_PSEUDO_PHYS_MEM; + } +#ifndef CONFIG_MIPS_XXS1500 + else { + skt->virt_io = (void *) + (ioremap((phys_t)AU1X_SOCK1_IO, 0x1000) - + (u32)mips_io_port_base); + skt->phys_attr = AU1X_SOCK1_PSEUDO_PHYS_ATTR; + skt->phys_mem = AU1X_SOCK1_PSEUDO_PHYS_MEM; + } +#endif + pcmcia_base_vaddrs[i] = (u32 *)skt->virt_io; + ret = ops->hw_init(skt); + + skt->socket.features = SS_CAP_STATIC_MAP|SS_CAP_PCCARD; + skt->socket.irq_mask = 0; + skt->socket.map_size = MAP_SIZE; + skt->socket.pci_irq = skt->irq; + skt->socket.io_offset = (unsigned long)skt->virt_io; + + skt->status = au1x00_pcmcia_skt_state(skt); + + ret = pcmcia_register_socket(&skt->socket); + if (ret) + goto out_err; + + WARN_ON(skt->socket.sock != i); + + add_timer(&skt->poll_timer); + } + + dev_set_drvdata(dev, sinfo); + return 0; + + do { + struct au1000_pcmcia_socket *skt = PCMCIA_SOCKET(i); + + del_timer_sync(&skt->poll_timer); + pcmcia_unregister_socket(&skt->socket); +out_err: + flush_scheduled_work(); + ops->hw_shutdown(skt); + + i--; + } while (i > 0); + kfree(sinfo); +out: + return ret; +} + +int au1x00_drv_pcmcia_remove(struct device *dev) +{ + struct skt_dev_info *sinfo = dev_get_drvdata(dev); + int i; + + down(&pcmcia_sockets_lock); + dev_set_drvdata(dev, NULL); + + for (i = 0; i < sinfo->nskt; i++) { + struct au1000_pcmcia_socket *skt = PCMCIA_SOCKET(i); + + del_timer_sync(&skt->poll_timer); + pcmcia_unregister_socket(&skt->socket); + flush_scheduled_work(); + skt->ops->hw_shutdown(skt); + au1x00_pcmcia_config_skt(skt, &dead_socket); + iounmap(skt->virt_io); + skt->virt_io = NULL; + } + + kfree(sinfo); + up(&pcmcia_sockets_lock); + return 0; +} + + +/* + * PCMCIA "Driver" API + */ + +static int au1x00_drv_pcmcia_probe(struct device *dev) +{ + int i, ret = -ENODEV; + + down(&pcmcia_sockets_lock); + for (i=0; i < ARRAY_SIZE(au1x00_pcmcia_hw_init); i++) { + ret = au1x00_pcmcia_hw_init[i](dev); + if (ret == 0) + break; + } + up(&pcmcia_sockets_lock); + return ret; +} + + +static int au1x00_drv_pcmcia_suspend(struct device *dev, u32 state, u32 level) +{ + int ret = 0; + if (level == SUSPEND_SAVE_STATE) + ret = pcmcia_socket_dev_suspend(dev, state); + return ret; +} + +static int au1x00_drv_pcmcia_resume(struct device *dev, u32 level) +{ + int ret = 0; + if (level == RESUME_RESTORE_STATE) + ret = pcmcia_socket_dev_resume(dev); + return ret; +} + + +static struct device_driver au1x00_pcmcia_driver = { + .probe = au1x00_drv_pcmcia_probe, + .remove = au1x00_drv_pcmcia_remove, + .name = "au1x00-pcmcia", + .bus = &platform_bus_type, + .suspend = au1x00_drv_pcmcia_suspend, + .resume = au1x00_drv_pcmcia_resume +}; + +static struct platform_device au1x00_device = { + .name = "au1x00-pcmcia", + .id = 0, +}; + +/* au1x00_pcmcia_init() + * + * This routine performs low-level PCMCIA initialization and then + * registers this socket driver with Card Services. + * + * Returns: 0 on success, -ve error code on failure + */ +static int __init au1x00_pcmcia_init(void) +{ + int error = 0; + if ((error = driver_register(&au1x00_pcmcia_driver))) + return error; + platform_device_register(&au1x00_device); + return error; +} + +/* au1x00_pcmcia_exit() + * Invokes the low-level kernel service to free IRQs associated with this + * socket controller and reset GPIO edge detection. + */ +static void __exit au1x00_pcmcia_exit(void) +{ + driver_unregister(&au1x00_pcmcia_driver); + platform_device_unregister(&au1x00_device); +} + +module_init(au1x00_pcmcia_init); +module_exit(au1x00_pcmcia_exit); diff --git a/drivers/pcmcia/au1000_generic.h b/drivers/pcmcia/au1000_generic.h new file mode 100644 index 000000000000..417bc1500bad --- /dev/null +++ b/drivers/pcmcia/au1000_generic.h @@ -0,0 +1,150 @@ +/* + * Alchemy Semi Au1000 pcmcia driver include file + * + * Copyright 2001 MontaVista Software Inc. + * Author: MontaVista Software, Inc. + * ppopov@mvista.com or source@mvista.com + * + * This program is free software; you can distribute it and/or modify it + * under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ +#ifndef __ASM_AU1000_PCMCIA_H +#define __ASM_AU1000_PCMCIA_H + +/* include the world */ +#include <pcmcia/version.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" + +#define AU1000_PCMCIA_POLL_PERIOD (2*HZ) +#define AU1000_PCMCIA_IO_SPEED (255) +#define AU1000_PCMCIA_MEM_SPEED (300) + +#define AU1X_SOCK0_IO 0xF00000000 +#define AU1X_SOCK0_PHYS_ATTR 0xF40000000 +#define AU1X_SOCK0_PHYS_MEM 0xF80000000 +/* pseudo 32 bit phys addresses, which get fixed up to the + * real 36 bit address in fixup_bigphys_addr() */ +#define AU1X_SOCK0_PSEUDO_PHYS_ATTR 0xF4000000 +#define AU1X_SOCK0_PSEUDO_PHYS_MEM 0xF8000000 + +/* pcmcia socket 1 needs external glue logic so the memory map + * differs from board to board. + */ +#if defined(CONFIG_MIPS_PB1000) || defined(CONFIG_MIPS_PB1100) || defined(CONFIG_MIPS_PB1500) || defined(CONFIG_MIPS_PB1550) +#define AU1X_SOCK1_IO 0xF08000000 +#define AU1X_SOCK1_PHYS_ATTR 0xF48000000 +#define AU1X_SOCK1_PHYS_MEM 0xF88000000 +#define AU1X_SOCK1_PSEUDO_PHYS_ATTR 0xF4800000 +#define AU1X_SOCK1_PSEUDO_PHYS_MEM 0xF8800000 +#elif defined(CONFIG_MIPS_DB1000) || defined(CONFIG_MIPS_DB1100) || defined(CONFIG_MIPS_DB1500) || defined(CONFIG_MIPS_DB1550) +#define AU1X_SOCK1_IO 0xF04000000 +#define AU1X_SOCK1_PHYS_ATTR 0xF44000000 +#define AU1X_SOCK1_PHYS_MEM 0xF84000000 +#define AU1X_SOCK1_PSEUDO_PHYS_ATTR 0xF4400000 +#define AU1X_SOCK1_PSEUDO_PHYS_MEM 0xF8400000 +#endif + +struct pcmcia_state { + unsigned detect: 1, + ready: 1, + wrprot: 1, + bvd1: 1, + bvd2: 1, + vs_3v: 1, + vs_Xv: 1; +}; + +struct pcmcia_configure { + unsigned sock: 8, + vcc: 8, + vpp: 8, + output: 1, + speaker: 1, + reset: 1; +}; + +struct pcmcia_irqs { + int sock; + int irq; + const char *str; +}; + + +struct au1000_pcmcia_socket { + struct pcmcia_socket socket; + + /* + * Info from low level handler + */ + struct device *dev; + unsigned int nr; + unsigned int irq; + + /* + * Core PCMCIA state + */ + struct pcmcia_low_level *ops; + + unsigned int status; + socket_state_t cs_state; + + unsigned short spd_io[MAX_IO_WIN]; + unsigned short spd_mem[MAX_WIN]; + unsigned short spd_attr[MAX_WIN]; + + struct resource res_skt; + struct resource res_io; + struct resource res_mem; + struct resource res_attr; + + void * virt_io; + ioaddr_t phys_io; + unsigned int phys_attr; + unsigned int phys_mem; + unsigned short speed_io, speed_attr, speed_mem; + + unsigned int irq_state; + + struct timer_list poll_timer; +}; + +struct pcmcia_low_level { + struct module *owner; + + int (*hw_init)(struct au1000_pcmcia_socket *); + void (*hw_shutdown)(struct au1000_pcmcia_socket *); + + void (*socket_state)(struct au1000_pcmcia_socket *, struct pcmcia_state *); + int (*configure_socket)(struct au1000_pcmcia_socket *, struct socket_state_t *); + + /* + * Enable card status IRQs on (re-)initialisation. This can + * be called at initialisation, power management event, or + * pcmcia event. + */ + void (*socket_init)(struct au1000_pcmcia_socket *); + + /* + * Disable card status IRQs and PCMCIA bus on suspend. + */ + void (*socket_suspend)(struct au1000_pcmcia_socket *); +}; + +extern int au1x_board_init(struct device *dev); + +#endif /* __ASM_AU1000_PCMCIA_H */ diff --git a/drivers/pcmcia/au1000_pb1x00.c b/drivers/pcmcia/au1000_pb1x00.c new file mode 100644 index 000000000000..df19ce1ea4f3 --- /dev/null +++ b/drivers/pcmcia/au1000_pb1x00.c @@ -0,0 +1,419 @@ +/* + * + * Alchemy Semi Pb1x00 boards specific pcmcia routines. + * + * Copyright 2002 MontaVista Software Inc. + * Author: MontaVista Software, Inc. + * ppopov@mvista.com or source@mvista.com + * + * ######################################################################## + * + * This program is free software; you can distribute it and/or modify it + * under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/tqueue.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/proc_fs.h> +#include <linux/version.h> +#include <linux/types.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/ss.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/bus_ops.h> +#include "cs_internal.h" + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include <asm/au1000.h> +#include <asm/au1000_pcmcia.h> + +#define debug(fmt, arg...) do { } while (0) + +#ifdef CONFIG_MIPS_PB1000 +#include <asm/pb1000.h> +#define PCMCIA_IRQ AU1000_GPIO_15 +#elif defined (CONFIG_MIPS_PB1500) +#include <asm/pb1500.h> +#define PCMCIA_IRQ AU1500_GPIO_203 +#elif defined (CONFIG_MIPS_PB1100) +#include <asm/pb1100.h> +#define PCMCIA_IRQ AU1000_GPIO_11 +#endif + +static int pb1x00_pcmcia_init(struct pcmcia_init *init) +{ +#ifdef CONFIG_MIPS_PB1000 + u16 pcr; + pcr = PCR_SLOT_0_RST | PCR_SLOT_1_RST; + + au_writel(0x8000, PB1000_MDR); /* clear pcmcia interrupt */ + au_sync_delay(100); + au_writel(0x4000, PB1000_MDR); /* enable pcmcia interrupt */ + au_sync(); + + pcr |= SET_VCC_VPP(VCC_HIZ,VPP_HIZ,0); + pcr |= SET_VCC_VPP(VCC_HIZ,VPP_HIZ,1); + au_writel(pcr, PB1000_PCR); + au_sync_delay(20); + + return PCMCIA_NUM_SOCKS; + +#else /* fixme -- take care of the Pb1500 at some point */ + + u16 pcr; + pcr = au_readw(PCMCIA_BOARD_REG) & ~0xf; /* turn off power */ + pcr &= ~(PC_DEASSERT_RST | PC_DRV_EN); + au_writew(pcr, PCMCIA_BOARD_REG); + au_sync_delay(500); + return PCMCIA_NUM_SOCKS; +#endif +} + +static int pb1x00_pcmcia_shutdown(void) +{ +#ifdef CONFIG_MIPS_PB1000 + u16 pcr; + pcr = PCR_SLOT_0_RST | PCR_SLOT_1_RST; + pcr |= SET_VCC_VPP(VCC_HIZ,VPP_HIZ,0); + pcr |= SET_VCC_VPP(VCC_HIZ,VPP_HIZ,1); + au_writel(pcr, PB1000_PCR); + au_sync_delay(20); + return 0; +#else + u16 pcr; + pcr = au_readw(PCMCIA_BOARD_REG) & ~0xf; /* turn off power */ + pcr &= ~(PC_DEASSERT_RST | PC_DRV_EN); + au_writew(pcr, PCMCIA_BOARD_REG); + au_sync_delay(2); + return 0; +#endif +} + +static int +pb1x00_pcmcia_socket_state(unsigned sock, struct pcmcia_state *state) +{ + u32 inserted0, inserted1; + u16 vs0, vs1; + +#ifdef CONFIG_MIPS_PB1000 + vs0 = vs1 = (u16)au_readl(PB1000_ACR1); + inserted0 = !(vs0 & (ACR1_SLOT_0_CD1 | ACR1_SLOT_0_CD2)); + inserted1 = !(vs1 & (ACR1_SLOT_1_CD1 | ACR1_SLOT_1_CD2)); + vs0 = (vs0 >> 4) & 0x3; + vs1 = (vs1 >> 12) & 0x3; +#else + vs0 = (au_readw(BOARD_STATUS_REG) >> 4) & 0x3; +#ifdef CONFIG_MIPS_PB1500 + inserted0 = !((au_readl(GPIO2_PINSTATE) >> 1) & 0x1); /* gpio 201 */ +#else /* Pb1100 */ + inserted0 = !((au_readl(SYS_PINSTATERD) >> 9) & 0x1); /* gpio 9 */ +#endif + inserted1 = 0; +#endif + + state->ready = 0; + state->vs_Xv = 0; + state->vs_3v = 0; + state->detect = 0; + + if (sock == 0) { + if (inserted0) { + switch (vs0) { + case 0: + case 2: + state->vs_3v=1; + break; + case 3: /* 5V */ + break; + default: + /* return without setting 'detect' */ + printk(KERN_ERR "pb1x00 bad VS (%d)\n", + vs0); + return 0; + } + state->detect = 1; + } + } + else { + if (inserted1) { + switch (vs1) { + case 0: + case 2: + state->vs_3v=1; + break; + case 3: /* 5V */ + break; + default: + /* return without setting 'detect' */ + printk(KERN_ERR "pb1x00 bad VS (%d)\n", + vs1); + return 0; + } + state->detect = 1; + } + } + + if (state->detect) { + state->ready = 1; + } + + state->bvd1=1; + state->bvd2=1; + state->wrprot=0; + return 1; +} + + +static int pb1x00_pcmcia_get_irq_info(struct pcmcia_irq_info *info) +{ + + if(info->sock > PCMCIA_MAX_SOCK) return -1; + + /* + * Even in the case of the Pb1000, both sockets are connected + * to the same irq line. + */ + info->irq = PCMCIA_IRQ; + + return 0; +} + + +static int +pb1x00_pcmcia_configure_socket(const struct pcmcia_configure *configure) +{ + u16 pcr; + + if(configure->sock > PCMCIA_MAX_SOCK) return -1; + +#ifdef CONFIG_MIPS_PB1000 + pcr = au_readl(PB1000_PCR); + + if (configure->sock == 0) { + pcr &= ~(PCR_SLOT_0_VCC0 | PCR_SLOT_0_VCC1 | + PCR_SLOT_0_VPP0 | PCR_SLOT_0_VPP1); + } + else { + pcr &= ~(PCR_SLOT_1_VCC0 | PCR_SLOT_1_VCC1 | + PCR_SLOT_1_VPP0 | PCR_SLOT_1_VPP1); + } + + pcr &= ~PCR_SLOT_0_RST; + debug("Vcc %dV Vpp %dV, pcr %x\n", + configure->vcc, configure->vpp, pcr); + switch(configure->vcc){ + case 0: /* Vcc 0 */ + switch(configure->vpp) { + case 0: + pcr |= SET_VCC_VPP(VCC_HIZ,VPP_GND, + configure->sock); + break; + case 12: + pcr |= SET_VCC_VPP(VCC_HIZ,VPP_12V, + configure->sock); + break; + case 50: + pcr |= SET_VCC_VPP(VCC_HIZ,VPP_5V, + configure->sock); + break; + case 33: + pcr |= SET_VCC_VPP(VCC_HIZ,VPP_3V, + configure->sock); + break; + default: + pcr |= SET_VCC_VPP(VCC_HIZ,VPP_HIZ, + configure->sock); + printk("%s: bad Vcc/Vpp (%d:%d)\n", + __FUNCTION__, + configure->vcc, + configure->vpp); + break; + } + break; + case 50: /* Vcc 5V */ + switch(configure->vpp) { + case 0: + pcr |= SET_VCC_VPP(VCC_5V,VPP_GND, + configure->sock); + break; + case 50: + pcr |= SET_VCC_VPP(VCC_5V,VPP_5V, + configure->sock); + break; + case 12: + pcr |= SET_VCC_VPP(VCC_5V,VPP_12V, + configure->sock); + break; + case 33: + pcr |= SET_VCC_VPP(VCC_5V,VPP_3V, + configure->sock); + break; + default: + pcr |= SET_VCC_VPP(VCC_HIZ,VPP_HIZ, + configure->sock); + printk("%s: bad Vcc/Vpp (%d:%d)\n", + __FUNCTION__, + configure->vcc, + configure->vpp); + break; + } + break; + case 33: /* Vcc 3.3V */ + switch(configure->vpp) { + case 0: + pcr |= SET_VCC_VPP(VCC_3V,VPP_GND, + configure->sock); + break; + case 50: + pcr |= SET_VCC_VPP(VCC_3V,VPP_5V, + configure->sock); + break; + case 12: + pcr |= SET_VCC_VPP(VCC_3V,VPP_12V, + configure->sock); + break; + case 33: + pcr |= SET_VCC_VPP(VCC_3V,VPP_3V, + configure->sock); + break; + default: + pcr |= SET_VCC_VPP(VCC_HIZ,VPP_HIZ, + configure->sock); + printk("%s: bad Vcc/Vpp (%d:%d)\n", + __FUNCTION__, + configure->vcc, + configure->vpp); + break; + } + break; + default: /* what's this ? */ + pcr |= SET_VCC_VPP(VCC_HIZ,VPP_HIZ,configure->sock); + printk(KERN_ERR "%s: bad Vcc %d\n", + __FUNCTION__, configure->vcc); + break; + } + + if (configure->sock == 0) { + pcr &= ~(PCR_SLOT_0_RST); + if (configure->reset) + pcr |= PCR_SLOT_0_RST; + } + else { + pcr &= ~(PCR_SLOT_1_RST); + if (configure->reset) + pcr |= PCR_SLOT_1_RST; + } + au_writel(pcr, PB1000_PCR); + au_sync_delay(300); + +#else + + pcr = au_readw(PCMCIA_BOARD_REG) & ~0xf; + + debug("Vcc %dV Vpp %dV, pcr %x, reset %d\n", + configure->vcc, configure->vpp, pcr, configure->reset); + + + switch(configure->vcc){ + case 0: /* Vcc 0 */ + pcr |= SET_VCC_VPP(0,0); + break; + case 50: /* Vcc 5V */ + switch(configure->vpp) { + case 0: + pcr |= SET_VCC_VPP(2,0); + break; + case 50: + pcr |= SET_VCC_VPP(2,1); + break; + case 12: + pcr |= SET_VCC_VPP(2,2); + break; + case 33: + default: + pcr |= SET_VCC_VPP(0,0); + printk("%s: bad Vcc/Vpp (%d:%d)\n", + __FUNCTION__, + configure->vcc, + configure->vpp); + break; + } + break; + case 33: /* Vcc 3.3V */ + switch(configure->vpp) { + case 0: + pcr |= SET_VCC_VPP(1,0); + break; + case 12: + pcr |= SET_VCC_VPP(1,2); + break; + case 33: + pcr |= SET_VCC_VPP(1,1); + break; + case 50: + default: + pcr |= SET_VCC_VPP(0,0); + printk("%s: bad Vcc/Vpp (%d:%d)\n", + __FUNCTION__, + configure->vcc, + configure->vpp); + break; + } + break; + default: /* what's this ? */ + pcr |= SET_VCC_VPP(0,0); + printk(KERN_ERR "%s: bad Vcc %d\n", + __FUNCTION__, configure->vcc); + break; + } + + au_writew(pcr, PCMCIA_BOARD_REG); + au_sync_delay(300); + + if (!configure->reset) { + pcr |= PC_DRV_EN; + au_writew(pcr, PCMCIA_BOARD_REG); + au_sync_delay(100); + pcr |= PC_DEASSERT_RST; + au_writew(pcr, PCMCIA_BOARD_REG); + au_sync_delay(100); + } + else { + pcr &= ~(PC_DEASSERT_RST | PC_DRV_EN); + au_writew(pcr, PCMCIA_BOARD_REG); + au_sync_delay(100); + } +#endif + return 0; +} + + +struct pcmcia_low_level pb1x00_pcmcia_ops = { + pb1x00_pcmcia_init, + pb1x00_pcmcia_shutdown, + pb1x00_pcmcia_socket_state, + pb1x00_pcmcia_get_irq_info, + pb1x00_pcmcia_configure_socket +}; diff --git a/drivers/pcmcia/au1000_xxs1500.c b/drivers/pcmcia/au1000_xxs1500.c new file mode 100644 index 000000000000..1dfc77653660 --- /dev/null +++ b/drivers/pcmcia/au1000_xxs1500.c @@ -0,0 +1,191 @@ +/* + * + * MyCable board specific pcmcia routines. + * + * Copyright 2003 MontaVista Software Inc. + * Author: Pete Popov, MontaVista Software, Inc. + * ppopov@mvista.com or source@mvista.com + * + * ######################################################################## + * + * This program is free software; you can distribute it and/or modify it + * under the terms of the GNU General Public License (Version 2) as + * published by the Free Software Foundation. + * + * This program is distributed in the hope 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. + * + * ######################################################################## + * + * + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/config.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/tqueue.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/proc_fs.h> +#include <linux/version.h> +#include <linux/types.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/ss.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/bus_ops.h> +#include "cs_internal.h" + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include <asm/au1000.h> +#include <asm/au1000_pcmcia.h> +#include <asm/xxs1500.h> + +#if 0 +#define DEBUG(x,args...) printk(__FUNCTION__ ": " x,##args) +#else +#define DEBUG(x,args...) +#endif + +static int xxs1500_pcmcia_init(struct pcmcia_init *init) +{ + return PCMCIA_NUM_SOCKS; +} + +static int xxs1500_pcmcia_shutdown(void) +{ + /* turn off power */ + au_writel(au_readl(GPIO2_PINSTATE) | (1<<14)|(1<<30), + GPIO2_OUTPUT); + au_sync_delay(100); + + /* assert reset */ + au_writel(au_readl(GPIO2_PINSTATE) | (1<<4)|(1<<20), + GPIO2_OUTPUT); + au_sync_delay(100); + return 0; +} + + +static int +xxs1500_pcmcia_socket_state(unsigned sock, struct pcmcia_state *state) +{ + u32 inserted; u32 vs; + unsigned long gpio, gpio2; + + if(sock > PCMCIA_MAX_SOCK) return -1; + + gpio = au_readl(SYS_PINSTATERD); + gpio2 = au_readl(GPIO2_PINSTATE); + + vs = gpio2 & ((1<<8) | (1<<9)); + inserted = (!(gpio & 0x1) && !(gpio & 0x2)); + + state->ready = 0; + state->vs_Xv = 0; + state->vs_3v = 0; + state->detect = 0; + + if (inserted) { + switch (vs) { + case 0: + case 1: + case 2: + state->vs_3v=1; + break; + case 3: /* 5V */ + default: + /* return without setting 'detect' */ + printk(KERN_ERR "au1x00_cs: unsupported VS\n", + vs); + return; + } + state->detect = 1; + } + + if (state->detect) { + state->ready = 1; + } + + state->bvd1= gpio2 & (1<<10); + state->bvd2 = gpio2 & (1<<11); + state->wrprot=0; + return 1; +} + + +static int xxs1500_pcmcia_get_irq_info(struct pcmcia_irq_info *info) +{ + + if(info->sock > PCMCIA_MAX_SOCK) return -1; + info->irq = PCMCIA_IRQ; + return 0; +} + + +static int +xxs1500_pcmcia_configure_socket(const struct pcmcia_configure *configure) +{ + + if(configure->sock > PCMCIA_MAX_SOCK) return -1; + + DEBUG("Vcc %dV Vpp %dV, reset %d\n", + configure->vcc, configure->vpp, configure->reset); + + switch(configure->vcc){ + case 33: /* Vcc 3.3V */ + /* turn on power */ + DEBUG("turn on power\n"); + au_writel((au_readl(GPIO2_PINSTATE) & ~(1<<14))|(1<<30), + GPIO2_OUTPUT); + au_sync_delay(100); + break; + case 50: /* Vcc 5V */ + default: /* what's this ? */ + printk(KERN_ERR "au1x00_cs: unsupported VCC\n"); + case 0: /* Vcc 0 */ + /* turn off power */ + au_sync_delay(100); + au_writel(au_readl(GPIO2_PINSTATE) | (1<<14)|(1<<30), + GPIO2_OUTPUT); + break; + } + + if (!configure->reset) { + DEBUG("deassert reset\n"); + au_writel((au_readl(GPIO2_PINSTATE) & ~(1<<4))|(1<<20), + GPIO2_OUTPUT); + au_sync_delay(100); + au_writel((au_readl(GPIO2_PINSTATE) & ~(1<<5))|(1<<21), + GPIO2_OUTPUT); + } + else { + DEBUG("assert reset\n"); + au_writel(au_readl(GPIO2_PINSTATE) | (1<<4)|(1<<20), + GPIO2_OUTPUT); + } + au_sync_delay(100); + return 0; +} + +struct pcmcia_low_level xxs1500_pcmcia_ops = { + xxs1500_pcmcia_init, + xxs1500_pcmcia_shutdown, + xxs1500_pcmcia_socket_state, + xxs1500_pcmcia_get_irq_info, + xxs1500_pcmcia_configure_socket +}; diff --git a/drivers/pcmcia/cardbus.c b/drivers/pcmcia/cardbus.c new file mode 100644 index 000000000000..3ccb5247ec50 --- /dev/null +++ b/drivers/pcmcia/cardbus.c @@ -0,0 +1,247 @@ +/* + * cardbus.c -- 16-bit PCMCIA core support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + */ + +/* + * Cardbus handling has been re-written to be more of a PCI bridge thing, + * and the PCI code basically does all the resource handling. + * + * Linus, Jan 2000 + */ + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/ioport.h> +#include <asm/irq.h> +#include <asm/io.h> + +#define IN_CARD_SERVICES +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" + +/*====================================================================*/ + +#define FIND_FIRST_BIT(n) ((n) - ((n) & ((n)-1))) + +/* Offsets in the Expansion ROM Image Header */ +#define ROM_SIGNATURE 0x0000 /* 2 bytes */ +#define ROM_DATA_PTR 0x0018 /* 2 bytes */ + +/* Offsets in the CardBus PC Card Data Structure */ +#define PCDATA_SIGNATURE 0x0000 /* 4 bytes */ +#define PCDATA_VPD_PTR 0x0008 /* 2 bytes */ +#define PCDATA_LENGTH 0x000a /* 2 bytes */ +#define PCDATA_REVISION 0x000c +#define PCDATA_IMAGE_SZ 0x0010 /* 2 bytes */ +#define PCDATA_ROM_LEVEL 0x0012 /* 2 bytes */ +#define PCDATA_CODE_TYPE 0x0014 +#define PCDATA_INDICATOR 0x0015 + +/*===================================================================== + + Expansion ROM's have a special layout, and pointers specify an + image number and an offset within that image. xlate_rom_addr() + converts an image/offset address to an absolute offset from the + ROM's base address. + +=====================================================================*/ + +static u_int xlate_rom_addr(void __iomem *b, u_int addr) +{ + u_int img = 0, ofs = 0, sz; + u_short data; + while ((readb(b) == 0x55) && (readb(b + 1) == 0xaa)) { + if (img == (addr >> 28)) + return (addr & 0x0fffffff) + ofs; + data = readb(b + ROM_DATA_PTR) + (readb(b + ROM_DATA_PTR + 1) << 8); + sz = 512 * (readb(b + data + PCDATA_IMAGE_SZ) + + (readb(b + data + PCDATA_IMAGE_SZ + 1) << 8)); + if ((sz == 0) || (readb(b + data + PCDATA_INDICATOR) & 0x80)) + break; + b += sz; + ofs += sz; + img++; + } + return 0; +} + +/*===================================================================== + + These are similar to setup_cis_mem and release_cis_mem for 16-bit + cards. The "result" that is used externally is the cb_cis_virt + pointer in the struct pcmcia_socket structure. + +=====================================================================*/ + +static void cb_release_cis_mem(struct pcmcia_socket * s) +{ + if (s->cb_cis_virt) { + cs_dbg(s, 1, "cb_release_cis_mem()\n"); + iounmap(s->cb_cis_virt); + s->cb_cis_virt = NULL; + s->cb_cis_res = NULL; + } +} + +static int cb_setup_cis_mem(struct pcmcia_socket * s, struct resource *res) +{ + unsigned int start, size; + + if (res == s->cb_cis_res) + return 0; + + if (s->cb_cis_res) + cb_release_cis_mem(s); + + start = res->start; + size = res->end - start + 1; + s->cb_cis_virt = ioremap(start, size); + + if (!s->cb_cis_virt) + return -1; + + s->cb_cis_res = res; + + return 0; +} + +/*===================================================================== + + This is used by the CIS processing code to read CIS information + from a CardBus device. + +=====================================================================*/ + +int read_cb_mem(struct pcmcia_socket * s, int space, u_int addr, u_int len, void *ptr) +{ + struct pci_dev *dev; + struct resource *res; + + cs_dbg(s, 3, "read_cb_mem(%d, %#x, %u)\n", space, addr, len); + + dev = pci_find_slot(s->cb_dev->subordinate->number, 0); + if (!dev) + goto fail; + + /* Config space? */ + if (space == 0) { + if (addr + len > 0x100) + goto fail; + for (; len; addr++, ptr++, len--) + pci_read_config_byte(dev, addr, ptr); + return 0; + } + + res = dev->resource + space - 1; + if (!res->flags) + goto fail; + + if (cb_setup_cis_mem(s, res) != 0) + goto fail; + + if (space == 7) { + addr = xlate_rom_addr(s->cb_cis_virt, addr); + if (addr == 0) + goto fail; + } + + if (addr + len > res->end - res->start) + goto fail; + + memcpy_fromio(ptr, s->cb_cis_virt + addr, len); + return 0; + +fail: + memset(ptr, 0xff, len); + return -1; +} + +/*===================================================================== + + cb_alloc() and cb_free() allocate and free the kernel data + structures for a Cardbus device, and handle the lowest level PCI + device setup issues. + +=====================================================================*/ + +/* + * Since there is only one interrupt available to CardBus + * devices, all devices downstream of this device must + * be using this IRQ. + */ +static void cardbus_assign_irqs(struct pci_bus *bus, int irq) +{ + struct pci_dev *dev; + + list_for_each_entry(dev, &bus->devices, bus_list) { + u8 irq_pin; + + pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &irq_pin); + if (irq_pin) { + dev->irq = irq; + pci_write_config_byte(dev, PCI_INTERRUPT_LINE, dev->irq); + } + + if (dev->subordinate) + cardbus_assign_irqs(dev->subordinate, irq); + } +} + +int cb_alloc(struct pcmcia_socket * s) +{ + struct pci_bus *bus = s->cb_dev->subordinate; + struct pci_dev *dev; + unsigned int max, pass; + + s->functions = pci_scan_slot(bus, PCI_DEVFN(0, 0)); +// pcibios_fixup_bus(bus); + + max = bus->secondary; + for (pass = 0; pass < 2; pass++) + list_for_each_entry(dev, &bus->devices, bus_list) + if (dev->hdr_type == PCI_HEADER_TYPE_BRIDGE || + dev->hdr_type == PCI_HEADER_TYPE_CARDBUS) + max = pci_scan_bridge(bus, dev, max, pass); + + /* + * Size all resources below the CardBus controller. + */ + pci_bus_size_bridges(bus); + pci_bus_assign_resources(bus); + cardbus_assign_irqs(bus, s->pci_irq); + pci_enable_bridges(bus); + pci_bus_add_devices(bus); + + s->irq.AssignedIRQ = s->pci_irq; + return CS_SUCCESS; +} + +void cb_free(struct pcmcia_socket * s) +{ + struct pci_dev *bridge = s->cb_dev; + + cb_release_cis_mem(s); + + if (bridge) + pci_remove_behind_bridge(bridge); +} diff --git a/drivers/pcmcia/cirrus.h b/drivers/pcmcia/cirrus.h new file mode 100644 index 000000000000..ecd4fc7f666f --- /dev/null +++ b/drivers/pcmcia/cirrus.h @@ -0,0 +1,157 @@ +/* + * cirrus.h 1.4 1999/10/25 20:03:34 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in which + * case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_CIRRUS_H +#define _LINUX_CIRRUS_H + +#ifndef PCI_VENDOR_ID_CIRRUS +#define PCI_VENDOR_ID_CIRRUS 0x1013 +#endif +#ifndef PCI_DEVICE_ID_CIRRUS_6729 +#define PCI_DEVICE_ID_CIRRUS_6729 0x1100 +#endif +#ifndef PCI_DEVICE_ID_CIRRUS_6832 +#define PCI_DEVICE_ID_CIRRUS_6832 0x1110 +#endif + +#define PD67_MISC_CTL_1 0x16 /* Misc control 1 */ +#define PD67_FIFO_CTL 0x17 /* FIFO control */ +#define PD67_MISC_CTL_2 0x1E /* Misc control 2 */ +#define PD67_CHIP_INFO 0x1f /* Chip information */ +#define PD67_ATA_CTL 0x026 /* 6730: ATA control */ +#define PD67_EXT_INDEX 0x2e /* Extension index */ +#define PD67_EXT_DATA 0x2f /* Extension data */ + +/* PD6722 extension registers -- indexed in PD67_EXT_INDEX */ +#define PD67_DATA_MASK0 0x01 /* Data mask 0 */ +#define PD67_DATA_MASK1 0x02 /* Data mask 1 */ +#define PD67_DMA_CTL 0x03 /* DMA control */ + +/* PD6730 extension registers -- indexed in PD67_EXT_INDEX */ +#define PD67_EXT_CTL_1 0x03 /* Extension control 1 */ +#define PD67_MEM_PAGE(n) ((n)+5) /* PCI window bits 31:24 */ +#define PD67_EXTERN_DATA 0x0a +#define PD67_MISC_CTL_3 0x25 +#define PD67_SMB_PWR_CTL 0x26 + +/* I/O window address offset */ +#define PD67_IO_OFF(w) (0x36+((w)<<1)) + +/* Timing register sets */ +#define PD67_TIME_SETUP(n) (0x3a + 3*(n)) +#define PD67_TIME_CMD(n) (0x3b + 3*(n)) +#define PD67_TIME_RECOV(n) (0x3c + 3*(n)) + +/* Flags for PD67_MISC_CTL_1 */ +#define PD67_MC1_5V_DET 0x01 /* 5v detect */ +#define PD67_MC1_MEDIA_ENA 0x01 /* 6730: Multimedia enable */ +#define PD67_MC1_VCC_3V 0x02 /* 3.3v Vcc */ +#define PD67_MC1_PULSE_MGMT 0x04 +#define PD67_MC1_PULSE_IRQ 0x08 +#define PD67_MC1_SPKR_ENA 0x10 +#define PD67_MC1_INPACK_ENA 0x80 + +/* Flags for PD67_FIFO_CTL */ +#define PD67_FIFO_EMPTY 0x80 + +/* Flags for PD67_MISC_CTL_2 */ +#define PD67_MC2_FREQ_BYPASS 0x01 +#define PD67_MC2_DYNAMIC_MODE 0x02 +#define PD67_MC2_SUSPEND 0x04 +#define PD67_MC2_5V_CORE 0x08 +#define PD67_MC2_LED_ENA 0x10 /* IRQ 12 is LED enable */ +#define PD67_MC2_FAST_PCI 0x10 /* 6729: PCI bus > 25 MHz */ +#define PD67_MC2_3STATE_BIT7 0x20 /* Floppy change bit */ +#define PD67_MC2_DMA_MODE 0x40 +#define PD67_MC2_IRQ15_RI 0x80 /* IRQ 15 is ring enable */ + +/* Flags for PD67_CHIP_INFO */ +#define PD67_INFO_SLOTS 0x20 /* 0 = 1 slot, 1 = 2 slots */ +#define PD67_INFO_CHIP_ID 0xc0 +#define PD67_INFO_REV 0x1c + +/* Fields in PD67_TIME_* registers */ +#define PD67_TIME_SCALE 0xc0 +#define PD67_TIME_SCALE_1 0x00 +#define PD67_TIME_SCALE_16 0x40 +#define PD67_TIME_SCALE_256 0x80 +#define PD67_TIME_SCALE_4096 0xc0 +#define PD67_TIME_MULT 0x3f + +/* Fields in PD67_DMA_CTL */ +#define PD67_DMA_MODE 0xc0 +#define PD67_DMA_OFF 0x00 +#define PD67_DMA_DREQ_INPACK 0x40 +#define PD67_DMA_DREQ_WP 0x80 +#define PD67_DMA_DREQ_BVD2 0xc0 +#define PD67_DMA_PULLUP 0x20 /* Disable socket pullups? */ + +/* Fields in PD67_EXT_CTL_1 */ +#define PD67_EC1_VCC_PWR_LOCK 0x01 +#define PD67_EC1_AUTO_PWR_CLEAR 0x02 +#define PD67_EC1_LED_ENA 0x04 +#define PD67_EC1_INV_CARD_IRQ 0x08 +#define PD67_EC1_INV_MGMT_IRQ 0x10 +#define PD67_EC1_PULLUP_CTL 0x20 + +/* Fields in PD67_MISC_CTL_3 */ +#define PD67_MC3_IRQ_MASK 0x03 +#define PD67_MC3_IRQ_PCPCI 0x00 +#define PD67_MC3_IRQ_EXTERN 0x01 +#define PD67_MC3_IRQ_PCIWAY 0x02 +#define PD67_MC3_IRQ_PCI 0x03 +#define PD67_MC3_PWR_MASK 0x0c +#define PD67_MC3_PWR_SERIAL 0x00 +#define PD67_MC3_PWR_TI2202 0x08 +#define PD67_MC3_PWR_SMB 0x0c + +/* Register definitions for Cirrus PD6832 PCI-to-CardBus bridge */ + +/* PD6832 extension registers -- indexed in PD67_EXT_INDEX */ +#define PD68_EXT_CTL_2 0x0b +#define PD68_PCI_SPACE 0x22 +#define PD68_PCCARD_SPACE 0x23 +#define PD68_WINDOW_TYPE 0x24 +#define PD68_EXT_CSC 0x2e +#define PD68_MISC_CTL_4 0x2f +#define PD68_MISC_CTL_5 0x30 +#define PD68_MISC_CTL_6 0x31 + +/* Extra flags in PD67_MISC_CTL_3 */ +#define PD68_MC3_HW_SUSP 0x10 +#define PD68_MC3_MM_EXPAND 0x40 +#define PD68_MC3_MM_ARM 0x80 + +/* Bridge Control Register */ +#define PD6832_BCR_MGMT_IRQ_ENA 0x0800 + +/* Socket Number Register */ +#define PD6832_SOCKET_NUMBER 0x004c /* 8 bit */ + +#endif /* _LINUX_CIRRUS_H */ diff --git a/drivers/pcmcia/cistpl.c b/drivers/pcmcia/cistpl.c new file mode 100644 index 000000000000..e29a6ddf2fd7 --- /dev/null +++ b/drivers/pcmcia/cistpl.c @@ -0,0 +1,1490 @@ +/* + * cistpl.c -- 16-bit PCMCIA Card Information Structure parser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/major.h> +#include <linux/errno.h> +#include <linux/timer.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/pci.h> +#include <linux/ioport.h> +#include <asm/io.h> +#include <asm/byteorder.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" + +static const u_char mantissa[] = { + 10, 12, 13, 15, 20, 25, 30, 35, + 40, 45, 50, 55, 60, 70, 80, 90 +}; + +static const u_int exponent[] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000 +}; + +/* Convert an extended speed byte to a time in nanoseconds */ +#define SPEED_CVT(v) \ + (mantissa[(((v)>>3)&15)-1] * exponent[(v)&7] / 10) +/* Convert a power byte to a current in 0.1 microamps */ +#define POWER_CVT(v) \ + (mantissa[((v)>>3)&15] * exponent[(v)&7] / 10) +#define POWER_SCALE(v) (exponent[(v)&7]) + +/* Upper limit on reasonable # of tuples */ +#define MAX_TUPLES 200 + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0444) + +INT_MODULE_PARM(cis_width, 0); /* 16-bit CIS? */ + +void release_cis_mem(struct pcmcia_socket *s) +{ + if (s->cis_mem.flags & MAP_ACTIVE) { + s->cis_mem.flags &= ~MAP_ACTIVE; + s->ops->set_mem_map(s, &s->cis_mem); + if (s->cis_mem.res) { + release_resource(s->cis_mem.res); + kfree(s->cis_mem.res); + s->cis_mem.res = NULL; + } + iounmap(s->cis_virt); + s->cis_virt = NULL; + } +} +EXPORT_SYMBOL(release_cis_mem); + +/* + * Map the card memory at "card_offset" into virtual space. + * If flags & MAP_ATTRIB, map the attribute space, otherwise + * map the memory space. + */ +static void __iomem * +set_cis_map(struct pcmcia_socket *s, unsigned int card_offset, unsigned int flags) +{ + pccard_mem_map *mem = &s->cis_mem; + if (!(s->features & SS_CAP_STATIC_MAP) && mem->res == NULL) { + mem->res = find_mem_region(0, s->map_size, s->map_size, 0, s); + if (mem->res == NULL) { + printk(KERN_NOTICE "cs: unable to map card memory!\n"); + return NULL; + } + s->cis_virt = ioremap(mem->res->start, s->map_size); + } + mem->card_start = card_offset; + mem->flags = flags; + s->ops->set_mem_map(s, mem); + if (s->features & SS_CAP_STATIC_MAP) { + if (s->cis_virt) + iounmap(s->cis_virt); + s->cis_virt = ioremap(mem->static_start, s->map_size); + } + return s->cis_virt; +} + +/*====================================================================== + + Low-level functions to read and write CIS memory. I think the + write routine is only useful for writing one-byte registers. + +======================================================================*/ + +/* Bits in attr field */ +#define IS_ATTR 1 +#define IS_INDIRECT 8 + +int read_cis_mem(struct pcmcia_socket *s, int attr, u_int addr, + u_int len, void *ptr) +{ + void __iomem *sys, *end; + unsigned char *buf = ptr; + + cs_dbg(s, 3, "read_cis_mem(%d, %#x, %u)\n", attr, addr, len); + + if (attr & IS_INDIRECT) { + /* Indirect accesses use a bunch of special registers at fixed + locations in common memory */ + u_char flags = ICTRL0_COMMON|ICTRL0_AUTOINC|ICTRL0_BYTEGRAN; + if (attr & IS_ATTR) { + addr *= 2; + flags = ICTRL0_AUTOINC; + } + + sys = set_cis_map(s, 0, MAP_ACTIVE | ((cis_width) ? MAP_16BIT : 0)); + if (!sys) { + memset(ptr, 0xff, len); + return -1; + } + + writeb(flags, sys+CISREG_ICTRL0); + writeb(addr & 0xff, sys+CISREG_IADDR0); + writeb((addr>>8) & 0xff, sys+CISREG_IADDR1); + writeb((addr>>16) & 0xff, sys+CISREG_IADDR2); + writeb((addr>>24) & 0xff, sys+CISREG_IADDR3); + for ( ; len > 0; len--, buf++) + *buf = readb(sys+CISREG_IDATA0); + } else { + u_int inc = 1, card_offset, flags; + + flags = MAP_ACTIVE | ((cis_width) ? MAP_16BIT : 0); + if (attr) { + flags |= MAP_ATTRIB; + inc++; + addr *= 2; + } + + card_offset = addr & ~(s->map_size-1); + while (len) { + sys = set_cis_map(s, card_offset, flags); + if (!sys) { + memset(ptr, 0xff, len); + return -1; + } + end = sys + s->map_size; + sys = sys + (addr & (s->map_size-1)); + for ( ; len > 0; len--, buf++, sys += inc) { + if (sys == end) + break; + *buf = readb(sys); + } + card_offset += s->map_size; + addr = 0; + } + } + cs_dbg(s, 3, " %#2.2x %#2.2x %#2.2x %#2.2x ...\n", + *(u_char *)(ptr+0), *(u_char *)(ptr+1), + *(u_char *)(ptr+2), *(u_char *)(ptr+3)); + return 0; +} + +void write_cis_mem(struct pcmcia_socket *s, int attr, u_int addr, + u_int len, void *ptr) +{ + void __iomem *sys, *end; + unsigned char *buf = ptr; + + cs_dbg(s, 3, "write_cis_mem(%d, %#x, %u)\n", attr, addr, len); + + if (attr & IS_INDIRECT) { + /* Indirect accesses use a bunch of special registers at fixed + locations in common memory */ + u_char flags = ICTRL0_COMMON|ICTRL0_AUTOINC|ICTRL0_BYTEGRAN; + if (attr & IS_ATTR) { + addr *= 2; + flags = ICTRL0_AUTOINC; + } + + sys = set_cis_map(s, 0, MAP_ACTIVE | ((cis_width) ? MAP_16BIT : 0)); + if (!sys) + return; /* FIXME: Error */ + + writeb(flags, sys+CISREG_ICTRL0); + writeb(addr & 0xff, sys+CISREG_IADDR0); + writeb((addr>>8) & 0xff, sys+CISREG_IADDR1); + writeb((addr>>16) & 0xff, sys+CISREG_IADDR2); + writeb((addr>>24) & 0xff, sys+CISREG_IADDR3); + for ( ; len > 0; len--, buf++) + writeb(*buf, sys+CISREG_IDATA0); + } else { + u_int inc = 1, card_offset, flags; + + flags = MAP_ACTIVE | ((cis_width) ? MAP_16BIT : 0); + if (attr & IS_ATTR) { + flags |= MAP_ATTRIB; + inc++; + addr *= 2; + } + + card_offset = addr & ~(s->map_size-1); + while (len) { + sys = set_cis_map(s, card_offset, flags); + if (!sys) + return; /* FIXME: error */ + + end = sys + s->map_size; + sys = sys + (addr & (s->map_size-1)); + for ( ; len > 0; len--, buf++, sys += inc) { + if (sys == end) + break; + writeb(*buf, sys); + } + card_offset += s->map_size; + addr = 0; + } + } +} + +/*====================================================================== + + This is a wrapper around read_cis_mem, with the same interface, + but which caches information, for cards whose CIS may not be + readable all the time. + +======================================================================*/ + +static void read_cis_cache(struct pcmcia_socket *s, int attr, u_int addr, + u_int len, void *ptr) +{ + struct cis_cache_entry *cis; + int ret; + + if (s->fake_cis) { + if (s->fake_cis_len > addr+len) + memcpy(ptr, s->fake_cis+addr, len); + else + memset(ptr, 0xff, len); + return; + } + + list_for_each_entry(cis, &s->cis_cache, node) { + if (cis->addr == addr && cis->len == len && cis->attr == attr) { + memcpy(ptr, cis->cache, len); + return; + } + } + +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) + ret = read_cb_mem(s, attr, addr, len, ptr); + else +#endif + ret = read_cis_mem(s, attr, addr, len, ptr); + + if (ret == 0) { + /* Copy data into the cache */ + cis = kmalloc(sizeof(struct cis_cache_entry) + len, GFP_KERNEL); + if (cis) { + cis->addr = addr; + cis->len = len; + cis->attr = attr; + memcpy(cis->cache, ptr, len); + list_add(&cis->node, &s->cis_cache); + } + } +} + +static void +remove_cis_cache(struct pcmcia_socket *s, int attr, u_int addr, u_int len) +{ + struct cis_cache_entry *cis; + + list_for_each_entry(cis, &s->cis_cache, node) + if (cis->addr == addr && cis->len == len && cis->attr == attr) { + list_del(&cis->node); + kfree(cis); + break; + } +} + +void destroy_cis_cache(struct pcmcia_socket *s) +{ + struct list_head *l, *n; + + list_for_each_safe(l, n, &s->cis_cache) { + struct cis_cache_entry *cis = list_entry(l, struct cis_cache_entry, node); + + list_del(&cis->node); + kfree(cis); + } + + /* + * If there was a fake CIS, destroy that as well. + */ + if (s->fake_cis) { + kfree(s->fake_cis); + s->fake_cis = NULL; + } +} +EXPORT_SYMBOL(destroy_cis_cache); + +/*====================================================================== + + This verifies if the CIS of a card matches what is in the CIS + cache. + +======================================================================*/ + +int verify_cis_cache(struct pcmcia_socket *s) +{ + struct cis_cache_entry *cis; + char *buf; + + buf = kmalloc(256, GFP_KERNEL); + if (buf == NULL) + return -1; + list_for_each_entry(cis, &s->cis_cache, node) { + int len = cis->len; + + if (len > 256) + len = 256; +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) + read_cb_mem(s, cis->attr, cis->addr, len, buf); + else +#endif + read_cis_mem(s, cis->attr, cis->addr, len, buf); + + if (memcmp(buf, cis->cache, len) != 0) { + kfree(buf); + return -1; + } + } + kfree(buf); + return 0; +} + +/*====================================================================== + + For really bad cards, we provide a facility for uploading a + replacement CIS. + +======================================================================*/ + +int pcmcia_replace_cis(struct pcmcia_socket *s, cisdump_t *cis) +{ + if (s->fake_cis != NULL) { + kfree(s->fake_cis); + s->fake_cis = NULL; + } + if (cis->Length > CISTPL_MAX_CIS_SIZE) + return CS_BAD_SIZE; + s->fake_cis = kmalloc(cis->Length, GFP_KERNEL); + if (s->fake_cis == NULL) + return CS_OUT_OF_RESOURCE; + s->fake_cis_len = cis->Length; + memcpy(s->fake_cis, cis->Data, cis->Length); + return CS_SUCCESS; +} + +/*====================================================================== + + The high-level CIS tuple services + +======================================================================*/ + +typedef struct tuple_flags { + u_int link_space:4; + u_int has_link:1; + u_int mfc_fn:3; + u_int space:4; +} tuple_flags; + +#define LINK_SPACE(f) (((tuple_flags *)(&(f)))->link_space) +#define HAS_LINK(f) (((tuple_flags *)(&(f)))->has_link) +#define MFC_FN(f) (((tuple_flags *)(&(f)))->mfc_fn) +#define SPACE(f) (((tuple_flags *)(&(f)))->space) + +int pccard_get_next_tuple(struct pcmcia_socket *s, unsigned int func, tuple_t *tuple); + +int pccard_get_first_tuple(struct pcmcia_socket *s, unsigned int function, tuple_t *tuple) +{ + if (!s) + return CS_BAD_HANDLE; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + tuple->TupleLink = tuple->Flags = 0; +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) { + struct pci_dev *dev = s->cb_dev; + u_int ptr; + pci_bus_read_config_dword(dev->subordinate, 0, PCI_CARDBUS_CIS, &ptr); + tuple->CISOffset = ptr & ~7; + SPACE(tuple->Flags) = (ptr & 7); + } else +#endif + { + /* Assume presence of a LONGLINK_C to address 0 */ + tuple->CISOffset = tuple->LinkOffset = 0; + SPACE(tuple->Flags) = HAS_LINK(tuple->Flags) = 1; + } + if (!(s->state & SOCKET_CARDBUS) && (s->functions > 1) && + !(tuple->Attributes & TUPLE_RETURN_COMMON)) { + cisdata_t req = tuple->DesiredTuple; + tuple->DesiredTuple = CISTPL_LONGLINK_MFC; + if (pccard_get_next_tuple(s, function, tuple) == CS_SUCCESS) { + tuple->DesiredTuple = CISTPL_LINKTARGET; + if (pccard_get_next_tuple(s, function, tuple) != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + } else + tuple->CISOffset = tuple->TupleLink = 0; + tuple->DesiredTuple = req; + } + return pccard_get_next_tuple(s, function, tuple); +} +EXPORT_SYMBOL(pccard_get_first_tuple); + +static int follow_link(struct pcmcia_socket *s, tuple_t *tuple) +{ + u_char link[5]; + u_int ofs; + + if (MFC_FN(tuple->Flags)) { + /* Get indirect link from the MFC tuple */ + read_cis_cache(s, LINK_SPACE(tuple->Flags), + tuple->LinkOffset, 5, link); + ofs = le32_to_cpu(*(u_int *)(link+1)); + SPACE(tuple->Flags) = (link[0] == CISTPL_MFC_ATTR); + /* Move to the next indirect link */ + tuple->LinkOffset += 5; + MFC_FN(tuple->Flags)--; + } else if (HAS_LINK(tuple->Flags)) { + ofs = tuple->LinkOffset; + SPACE(tuple->Flags) = LINK_SPACE(tuple->Flags); + HAS_LINK(tuple->Flags) = 0; + } else { + return -1; + } + if (!(s->state & SOCKET_CARDBUS) && SPACE(tuple->Flags)) { + /* This is ugly, but a common CIS error is to code the long + link offset incorrectly, so we check the right spot... */ + read_cis_cache(s, SPACE(tuple->Flags), ofs, 5, link); + if ((link[0] == CISTPL_LINKTARGET) && (link[1] >= 3) && + (strncmp(link+2, "CIS", 3) == 0)) + return ofs; + remove_cis_cache(s, SPACE(tuple->Flags), ofs, 5); + /* Then, we try the wrong spot... */ + ofs = ofs >> 1; + } + read_cis_cache(s, SPACE(tuple->Flags), ofs, 5, link); + if ((link[0] == CISTPL_LINKTARGET) && (link[1] >= 3) && + (strncmp(link+2, "CIS", 3) == 0)) + return ofs; + remove_cis_cache(s, SPACE(tuple->Flags), ofs, 5); + return -1; +} + +int pccard_get_next_tuple(struct pcmcia_socket *s, unsigned int function, tuple_t *tuple) +{ + u_char link[2], tmp; + int ofs, i, attr; + + if (!s) + return CS_BAD_HANDLE; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + + link[1] = tuple->TupleLink; + ofs = tuple->CISOffset + tuple->TupleLink; + attr = SPACE(tuple->Flags); + + for (i = 0; i < MAX_TUPLES; i++) { + if (link[1] == 0xff) { + link[0] = CISTPL_END; + } else { + read_cis_cache(s, attr, ofs, 2, link); + if (link[0] == CISTPL_NULL) { + ofs++; continue; + } + } + + /* End of chain? Follow long link if possible */ + if (link[0] == CISTPL_END) { + if ((ofs = follow_link(s, tuple)) < 0) + return CS_NO_MORE_ITEMS; + attr = SPACE(tuple->Flags); + read_cis_cache(s, attr, ofs, 2, link); + } + + /* Is this a link tuple? Make a note of it */ + if ((link[0] == CISTPL_LONGLINK_A) || + (link[0] == CISTPL_LONGLINK_C) || + (link[0] == CISTPL_LONGLINK_MFC) || + (link[0] == CISTPL_LINKTARGET) || + (link[0] == CISTPL_INDIRECT) || + (link[0] == CISTPL_NO_LINK)) { + switch (link[0]) { + case CISTPL_LONGLINK_A: + HAS_LINK(tuple->Flags) = 1; + LINK_SPACE(tuple->Flags) = attr | IS_ATTR; + read_cis_cache(s, attr, ofs+2, 4, &tuple->LinkOffset); + break; + case CISTPL_LONGLINK_C: + HAS_LINK(tuple->Flags) = 1; + LINK_SPACE(tuple->Flags) = attr & ~IS_ATTR; + read_cis_cache(s, attr, ofs+2, 4, &tuple->LinkOffset); + break; + case CISTPL_INDIRECT: + HAS_LINK(tuple->Flags) = 1; + LINK_SPACE(tuple->Flags) = IS_ATTR | IS_INDIRECT; + tuple->LinkOffset = 0; + break; + case CISTPL_LONGLINK_MFC: + tuple->LinkOffset = ofs + 3; + LINK_SPACE(tuple->Flags) = attr; + if (function == BIND_FN_ALL) { + /* Follow all the MFC links */ + read_cis_cache(s, attr, ofs+2, 1, &tmp); + MFC_FN(tuple->Flags) = tmp; + } else { + /* Follow exactly one of the links */ + MFC_FN(tuple->Flags) = 1; + tuple->LinkOffset += function * 5; + } + break; + case CISTPL_NO_LINK: + HAS_LINK(tuple->Flags) = 0; + break; + } + if ((tuple->Attributes & TUPLE_RETURN_LINK) && + (tuple->DesiredTuple == RETURN_FIRST_TUPLE)) + break; + } else + if (tuple->DesiredTuple == RETURN_FIRST_TUPLE) + break; + + if (link[0] == tuple->DesiredTuple) + break; + ofs += link[1] + 2; + } + if (i == MAX_TUPLES) { + cs_dbg(s, 1, "cs: overrun in pcmcia_get_next_tuple\n"); + return CS_NO_MORE_ITEMS; + } + + tuple->TupleCode = link[0]; + tuple->TupleLink = link[1]; + tuple->CISOffset = ofs + 2; + return CS_SUCCESS; +} +EXPORT_SYMBOL(pccard_get_next_tuple); + +/*====================================================================*/ + +#define _MIN(a, b) (((a) < (b)) ? (a) : (b)) + +int pccard_get_tuple_data(struct pcmcia_socket *s, tuple_t *tuple) +{ + u_int len; + + if (!s) + return CS_BAD_HANDLE; + + if (tuple->TupleLink < tuple->TupleOffset) + return CS_NO_MORE_ITEMS; + len = tuple->TupleLink - tuple->TupleOffset; + tuple->TupleDataLen = tuple->TupleLink; + if (len == 0) + return CS_SUCCESS; + read_cis_cache(s, SPACE(tuple->Flags), + tuple->CISOffset + tuple->TupleOffset, + _MIN(len, tuple->TupleDataMax), tuple->TupleData); + return CS_SUCCESS; +} +EXPORT_SYMBOL(pccard_get_tuple_data); + + +/*====================================================================== + + Parsing routines for individual tuples + +======================================================================*/ + +static int parse_device(tuple_t *tuple, cistpl_device_t *device) +{ + int i; + u_char scale; + u_char *p, *q; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + device->ndev = 0; + for (i = 0; i < CISTPL_MAX_DEVICES; i++) { + + if (*p == 0xff) break; + device->dev[i].type = (*p >> 4); + device->dev[i].wp = (*p & 0x08) ? 1 : 0; + switch (*p & 0x07) { + case 0: device->dev[i].speed = 0; break; + case 1: device->dev[i].speed = 250; break; + case 2: device->dev[i].speed = 200; break; + case 3: device->dev[i].speed = 150; break; + case 4: device->dev[i].speed = 100; break; + case 7: + if (++p == q) return CS_BAD_TUPLE; + device->dev[i].speed = SPEED_CVT(*p); + while (*p & 0x80) + if (++p == q) return CS_BAD_TUPLE; + break; + default: + return CS_BAD_TUPLE; + } + + if (++p == q) return CS_BAD_TUPLE; + if (*p == 0xff) break; + scale = *p & 7; + if (scale == 7) return CS_BAD_TUPLE; + device->dev[i].size = ((*p >> 3) + 1) * (512 << (scale*2)); + device->ndev++; + if (++p == q) break; + } + + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_checksum(tuple_t *tuple, cistpl_checksum_t *csum) +{ + u_char *p; + if (tuple->TupleDataLen < 5) + return CS_BAD_TUPLE; + p = (u_char *)tuple->TupleData; + csum->addr = tuple->CISOffset+(short)le16_to_cpu(*(u_short *)p)-2; + csum->len = le16_to_cpu(*(u_short *)(p + 2)); + csum->sum = *(p+4); + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_longlink(tuple_t *tuple, cistpl_longlink_t *link) +{ + if (tuple->TupleDataLen < 4) + return CS_BAD_TUPLE; + link->addr = le32_to_cpu(*(u_int *)tuple->TupleData); + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_longlink_mfc(tuple_t *tuple, + cistpl_longlink_mfc_t *link) +{ + u_char *p; + int i; + + p = (u_char *)tuple->TupleData; + + link->nfn = *p; p++; + if (tuple->TupleDataLen <= link->nfn*5) + return CS_BAD_TUPLE; + for (i = 0; i < link->nfn; i++) { + link->fn[i].space = *p; p++; + link->fn[i].addr = le32_to_cpu(*(u_int *)p); p += 4; + } + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_strings(u_char *p, u_char *q, int max, + char *s, u_char *ofs, u_char *found) +{ + int i, j, ns; + + if (p == q) return CS_BAD_TUPLE; + ns = 0; j = 0; + for (i = 0; i < max; i++) { + if (*p == 0xff) break; + ofs[i] = j; + ns++; + for (;;) { + s[j++] = (*p == 0xff) ? '\0' : *p; + if ((*p == '\0') || (*p == 0xff)) break; + if (++p == q) return CS_BAD_TUPLE; + } + if ((*p == 0xff) || (++p == q)) break; + } + if (found) { + *found = ns; + return CS_SUCCESS; + } else { + return (ns == max) ? CS_SUCCESS : CS_BAD_TUPLE; + } +} + +/*====================================================================*/ + +static int parse_vers_1(tuple_t *tuple, cistpl_vers_1_t *vers_1) +{ + u_char *p, *q; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + vers_1->major = *p; p++; + vers_1->minor = *p; p++; + if (p >= q) return CS_BAD_TUPLE; + + return parse_strings(p, q, CISTPL_VERS_1_MAX_PROD_STRINGS, + vers_1->str, vers_1->ofs, &vers_1->ns); +} + +/*====================================================================*/ + +static int parse_altstr(tuple_t *tuple, cistpl_altstr_t *altstr) +{ + u_char *p, *q; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + return parse_strings(p, q, CISTPL_MAX_ALTSTR_STRINGS, + altstr->str, altstr->ofs, &altstr->ns); +} + +/*====================================================================*/ + +static int parse_jedec(tuple_t *tuple, cistpl_jedec_t *jedec) +{ + u_char *p, *q; + int nid; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + for (nid = 0; nid < CISTPL_MAX_DEVICES; nid++) { + if (p > q-2) break; + jedec->id[nid].mfr = p[0]; + jedec->id[nid].info = p[1]; + p += 2; + } + jedec->nid = nid; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_manfid(tuple_t *tuple, cistpl_manfid_t *m) +{ + u_short *p; + if (tuple->TupleDataLen < 4) + return CS_BAD_TUPLE; + p = (u_short *)tuple->TupleData; + m->manf = le16_to_cpu(p[0]); + m->card = le16_to_cpu(p[1]); + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_funcid(tuple_t *tuple, cistpl_funcid_t *f) +{ + u_char *p; + if (tuple->TupleDataLen < 2) + return CS_BAD_TUPLE; + p = (u_char *)tuple->TupleData; + f->func = p[0]; + f->sysinit = p[1]; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_funce(tuple_t *tuple, cistpl_funce_t *f) +{ + u_char *p; + int i; + if (tuple->TupleDataLen < 1) + return CS_BAD_TUPLE; + p = (u_char *)tuple->TupleData; + f->type = p[0]; + for (i = 1; i < tuple->TupleDataLen; i++) + f->data[i-1] = p[i]; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_config(tuple_t *tuple, cistpl_config_t *config) +{ + int rasz, rmsz, i; + u_char *p; + + p = (u_char *)tuple->TupleData; + rasz = *p & 0x03; + rmsz = (*p & 0x3c) >> 2; + if (tuple->TupleDataLen < rasz+rmsz+4) + return CS_BAD_TUPLE; + config->last_idx = *(++p); + p++; + config->base = 0; + for (i = 0; i <= rasz; i++) + config->base += p[i] << (8*i); + p += rasz+1; + for (i = 0; i < 4; i++) + config->rmask[i] = 0; + for (i = 0; i <= rmsz; i++) + config->rmask[i>>2] += p[i] << (8*(i%4)); + config->subtuples = tuple->TupleDataLen - (rasz+rmsz+4); + return CS_SUCCESS; +} + +/*====================================================================== + + The following routines are all used to parse the nightmarish + config table entries. + +======================================================================*/ + +static u_char *parse_power(u_char *p, u_char *q, + cistpl_power_t *pwr) +{ + int i; + u_int scale; + + if (p == q) return NULL; + pwr->present = *p; + pwr->flags = 0; + p++; + for (i = 0; i < 7; i++) + if (pwr->present & (1<<i)) { + if (p == q) return NULL; + pwr->param[i] = POWER_CVT(*p); + scale = POWER_SCALE(*p); + while (*p & 0x80) { + if (++p == q) return NULL; + if ((*p & 0x7f) < 100) + pwr->param[i] += (*p & 0x7f) * scale / 100; + else if (*p == 0x7d) + pwr->flags |= CISTPL_POWER_HIGHZ_OK; + else if (*p == 0x7e) + pwr->param[i] = 0; + else if (*p == 0x7f) + pwr->flags |= CISTPL_POWER_HIGHZ_REQ; + else + return NULL; + } + p++; + } + return p; +} + +/*====================================================================*/ + +static u_char *parse_timing(u_char *p, u_char *q, + cistpl_timing_t *timing) +{ + u_char scale; + + if (p == q) return NULL; + scale = *p; + if ((scale & 3) != 3) { + if (++p == q) return NULL; + timing->wait = SPEED_CVT(*p); + timing->waitscale = exponent[scale & 3]; + } else + timing->wait = 0; + scale >>= 2; + if ((scale & 7) != 7) { + if (++p == q) return NULL; + timing->ready = SPEED_CVT(*p); + timing->rdyscale = exponent[scale & 7]; + } else + timing->ready = 0; + scale >>= 3; + if (scale != 7) { + if (++p == q) return NULL; + timing->reserved = SPEED_CVT(*p); + timing->rsvscale = exponent[scale]; + } else + timing->reserved = 0; + p++; + return p; +} + +/*====================================================================*/ + +static u_char *parse_io(u_char *p, u_char *q, cistpl_io_t *io) +{ + int i, j, bsz, lsz; + + if (p == q) return NULL; + io->flags = *p; + + if (!(*p & 0x80)) { + io->nwin = 1; + io->win[0].base = 0; + io->win[0].len = (1 << (io->flags & CISTPL_IO_LINES_MASK)); + return p+1; + } + + if (++p == q) return NULL; + io->nwin = (*p & 0x0f) + 1; + bsz = (*p & 0x30) >> 4; + if (bsz == 3) bsz++; + lsz = (*p & 0xc0) >> 6; + if (lsz == 3) lsz++; + p++; + + for (i = 0; i < io->nwin; i++) { + io->win[i].base = 0; + io->win[i].len = 1; + for (j = 0; j < bsz; j++, p++) { + if (p == q) return NULL; + io->win[i].base += *p << (j*8); + } + for (j = 0; j < lsz; j++, p++) { + if (p == q) return NULL; + io->win[i].len += *p << (j*8); + } + } + return p; +} + +/*====================================================================*/ + +static u_char *parse_mem(u_char *p, u_char *q, cistpl_mem_t *mem) +{ + int i, j, asz, lsz, has_ha; + u_int len, ca, ha; + + if (p == q) return NULL; + + mem->nwin = (*p & 0x07) + 1; + lsz = (*p & 0x18) >> 3; + asz = (*p & 0x60) >> 5; + has_ha = (*p & 0x80); + if (++p == q) return NULL; + + for (i = 0; i < mem->nwin; i++) { + len = ca = ha = 0; + for (j = 0; j < lsz; j++, p++) { + if (p == q) return NULL; + len += *p << (j*8); + } + for (j = 0; j < asz; j++, p++) { + if (p == q) return NULL; + ca += *p << (j*8); + } + if (has_ha) + for (j = 0; j < asz; j++, p++) { + if (p == q) return NULL; + ha += *p << (j*8); + } + mem->win[i].len = len << 8; + mem->win[i].card_addr = ca << 8; + mem->win[i].host_addr = ha << 8; + } + return p; +} + +/*====================================================================*/ + +static u_char *parse_irq(u_char *p, u_char *q, cistpl_irq_t *irq) +{ + if (p == q) return NULL; + irq->IRQInfo1 = *p; p++; + if (irq->IRQInfo1 & IRQ_INFO2_VALID) { + if (p+2 > q) return NULL; + irq->IRQInfo2 = (p[1]<<8) + p[0]; + p += 2; + } + return p; +} + +/*====================================================================*/ + +static int parse_cftable_entry(tuple_t *tuple, + cistpl_cftable_entry_t *entry) +{ + u_char *p, *q, features; + + p = tuple->TupleData; + q = p + tuple->TupleDataLen; + entry->index = *p & 0x3f; + entry->flags = 0; + if (*p & 0x40) + entry->flags |= CISTPL_CFTABLE_DEFAULT; + if (*p & 0x80) { + if (++p == q) return CS_BAD_TUPLE; + if (*p & 0x10) + entry->flags |= CISTPL_CFTABLE_BVDS; + if (*p & 0x20) + entry->flags |= CISTPL_CFTABLE_WP; + if (*p & 0x40) + entry->flags |= CISTPL_CFTABLE_RDYBSY; + if (*p & 0x80) + entry->flags |= CISTPL_CFTABLE_MWAIT; + entry->interface = *p & 0x0f; + } else + entry->interface = 0; + + /* Process optional features */ + if (++p == q) return CS_BAD_TUPLE; + features = *p; p++; + + /* Power options */ + if ((features & 3) > 0) { + p = parse_power(p, q, &entry->vcc); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vcc.present = 0; + if ((features & 3) > 1) { + p = parse_power(p, q, &entry->vpp1); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vpp1.present = 0; + if ((features & 3) > 2) { + p = parse_power(p, q, &entry->vpp2); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vpp2.present = 0; + + /* Timing options */ + if (features & 0x04) { + p = parse_timing(p, q, &entry->timing); + if (p == NULL) return CS_BAD_TUPLE; + } else { + entry->timing.wait = 0; + entry->timing.ready = 0; + entry->timing.reserved = 0; + } + + /* I/O window options */ + if (features & 0x08) { + p = parse_io(p, q, &entry->io); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->io.nwin = 0; + + /* Interrupt options */ + if (features & 0x10) { + p = parse_irq(p, q, &entry->irq); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->irq.IRQInfo1 = 0; + + switch (features & 0x60) { + case 0x00: + entry->mem.nwin = 0; + break; + case 0x20: + entry->mem.nwin = 1; + entry->mem.win[0].len = le16_to_cpu(*(u_short *)p) << 8; + entry->mem.win[0].card_addr = 0; + entry->mem.win[0].host_addr = 0; + p += 2; + if (p > q) return CS_BAD_TUPLE; + break; + case 0x40: + entry->mem.nwin = 1; + entry->mem.win[0].len = le16_to_cpu(*(u_short *)p) << 8; + entry->mem.win[0].card_addr = + le16_to_cpu(*(u_short *)(p+2)) << 8; + entry->mem.win[0].host_addr = 0; + p += 4; + if (p > q) return CS_BAD_TUPLE; + break; + case 0x60: + p = parse_mem(p, q, &entry->mem); + if (p == NULL) return CS_BAD_TUPLE; + break; + } + + /* Misc features */ + if (features & 0x80) { + if (p == q) return CS_BAD_TUPLE; + entry->flags |= (*p << 8); + while (*p & 0x80) + if (++p == q) return CS_BAD_TUPLE; + p++; + } + + entry->subtuples = q-p; + + return CS_SUCCESS; +} + +/*====================================================================*/ + +#ifdef CONFIG_CARDBUS + +static int parse_bar(tuple_t *tuple, cistpl_bar_t *bar) +{ + u_char *p; + if (tuple->TupleDataLen < 6) + return CS_BAD_TUPLE; + p = (u_char *)tuple->TupleData; + bar->attr = *p; + p += 2; + bar->size = le32_to_cpu(*(u_int *)p); + return CS_SUCCESS; +} + +static int parse_config_cb(tuple_t *tuple, cistpl_config_t *config) +{ + u_char *p; + + p = (u_char *)tuple->TupleData; + if ((*p != 3) || (tuple->TupleDataLen < 6)) + return CS_BAD_TUPLE; + config->last_idx = *(++p); + p++; + config->base = le32_to_cpu(*(u_int *)p); + config->subtuples = tuple->TupleDataLen - 6; + return CS_SUCCESS; +} + +static int parse_cftable_entry_cb(tuple_t *tuple, + cistpl_cftable_entry_cb_t *entry) +{ + u_char *p, *q, features; + + p = tuple->TupleData; + q = p + tuple->TupleDataLen; + entry->index = *p & 0x3f; + entry->flags = 0; + if (*p & 0x40) + entry->flags |= CISTPL_CFTABLE_DEFAULT; + + /* Process optional features */ + if (++p == q) return CS_BAD_TUPLE; + features = *p; p++; + + /* Power options */ + if ((features & 3) > 0) { + p = parse_power(p, q, &entry->vcc); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vcc.present = 0; + if ((features & 3) > 1) { + p = parse_power(p, q, &entry->vpp1); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vpp1.present = 0; + if ((features & 3) > 2) { + p = parse_power(p, q, &entry->vpp2); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vpp2.present = 0; + + /* I/O window options */ + if (features & 0x08) { + if (p == q) return CS_BAD_TUPLE; + entry->io = *p; p++; + } else + entry->io = 0; + + /* Interrupt options */ + if (features & 0x10) { + p = parse_irq(p, q, &entry->irq); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->irq.IRQInfo1 = 0; + + if (features & 0x20) { + if (p == q) return CS_BAD_TUPLE; + entry->mem = *p; p++; + } else + entry->mem = 0; + + /* Misc features */ + if (features & 0x80) { + if (p == q) return CS_BAD_TUPLE; + entry->flags |= (*p << 8); + if (*p & 0x80) { + if (++p == q) return CS_BAD_TUPLE; + entry->flags |= (*p << 16); + } + while (*p & 0x80) + if (++p == q) return CS_BAD_TUPLE; + p++; + } + + entry->subtuples = q-p; + + return CS_SUCCESS; +} + +#endif + +/*====================================================================*/ + +static int parse_device_geo(tuple_t *tuple, cistpl_device_geo_t *geo) +{ + u_char *p, *q; + int n; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + for (n = 0; n < CISTPL_MAX_DEVICES; n++) { + if (p > q-6) break; + geo->geo[n].buswidth = p[0]; + geo->geo[n].erase_block = 1 << (p[1]-1); + geo->geo[n].read_block = 1 << (p[2]-1); + geo->geo[n].write_block = 1 << (p[3]-1); + geo->geo[n].partition = 1 << (p[4]-1); + geo->geo[n].interleave = 1 << (p[5]-1); + p += 6; + } + geo->ngeo = n; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_vers_2(tuple_t *tuple, cistpl_vers_2_t *v2) +{ + u_char *p, *q; + + if (tuple->TupleDataLen < 10) + return CS_BAD_TUPLE; + + p = tuple->TupleData; + q = p + tuple->TupleDataLen; + + v2->vers = p[0]; + v2->comply = p[1]; + v2->dindex = le16_to_cpu(*(u_short *)(p+2)); + v2->vspec8 = p[6]; + v2->vspec9 = p[7]; + v2->nhdr = p[8]; + p += 9; + return parse_strings(p, q, 2, v2->str, &v2->vendor, NULL); +} + +/*====================================================================*/ + +static int parse_org(tuple_t *tuple, cistpl_org_t *org) +{ + u_char *p, *q; + int i; + + p = tuple->TupleData; + q = p + tuple->TupleDataLen; + if (p == q) return CS_BAD_TUPLE; + org->data_org = *p; + if (++p == q) return CS_BAD_TUPLE; + for (i = 0; i < 30; i++) { + org->desc[i] = *p; + if (*p == '\0') break; + if (++p == q) return CS_BAD_TUPLE; + } + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_format(tuple_t *tuple, cistpl_format_t *fmt) +{ + u_char *p; + + if (tuple->TupleDataLen < 10) + return CS_BAD_TUPLE; + + p = tuple->TupleData; + + fmt->type = p[0]; + fmt->edc = p[1]; + fmt->offset = le32_to_cpu(*(u_int *)(p+2)); + fmt->length = le32_to_cpu(*(u_int *)(p+6)); + + return CS_SUCCESS; +} + +/*====================================================================*/ + +int pccard_parse_tuple(tuple_t *tuple, cisparse_t *parse) +{ + int ret = CS_SUCCESS; + + if (tuple->TupleDataLen > tuple->TupleDataMax) + return CS_BAD_TUPLE; + switch (tuple->TupleCode) { + case CISTPL_DEVICE: + case CISTPL_DEVICE_A: + ret = parse_device(tuple, &parse->device); + break; +#ifdef CONFIG_CARDBUS + case CISTPL_BAR: + ret = parse_bar(tuple, &parse->bar); + break; + case CISTPL_CONFIG_CB: + ret = parse_config_cb(tuple, &parse->config); + break; + case CISTPL_CFTABLE_ENTRY_CB: + ret = parse_cftable_entry_cb(tuple, &parse->cftable_entry_cb); + break; +#endif + case CISTPL_CHECKSUM: + ret = parse_checksum(tuple, &parse->checksum); + break; + case CISTPL_LONGLINK_A: + case CISTPL_LONGLINK_C: + ret = parse_longlink(tuple, &parse->longlink); + break; + case CISTPL_LONGLINK_MFC: + ret = parse_longlink_mfc(tuple, &parse->longlink_mfc); + break; + case CISTPL_VERS_1: + ret = parse_vers_1(tuple, &parse->version_1); + break; + case CISTPL_ALTSTR: + ret = parse_altstr(tuple, &parse->altstr); + break; + case CISTPL_JEDEC_A: + case CISTPL_JEDEC_C: + ret = parse_jedec(tuple, &parse->jedec); + break; + case CISTPL_MANFID: + ret = parse_manfid(tuple, &parse->manfid); + break; + case CISTPL_FUNCID: + ret = parse_funcid(tuple, &parse->funcid); + break; + case CISTPL_FUNCE: + ret = parse_funce(tuple, &parse->funce); + break; + case CISTPL_CONFIG: + ret = parse_config(tuple, &parse->config); + break; + case CISTPL_CFTABLE_ENTRY: + ret = parse_cftable_entry(tuple, &parse->cftable_entry); + break; + case CISTPL_DEVICE_GEO: + case CISTPL_DEVICE_GEO_A: + ret = parse_device_geo(tuple, &parse->device_geo); + break; + case CISTPL_VERS_2: + ret = parse_vers_2(tuple, &parse->vers_2); + break; + case CISTPL_ORG: + ret = parse_org(tuple, &parse->org); + break; + case CISTPL_FORMAT: + case CISTPL_FORMAT_A: + ret = parse_format(tuple, &parse->format); + break; + case CISTPL_NO_LINK: + case CISTPL_LINKTARGET: + ret = CS_SUCCESS; + break; + default: + ret = CS_UNSUPPORTED_FUNCTION; + break; + } + return ret; +} +EXPORT_SYMBOL(pccard_parse_tuple); + +/*====================================================================== + + This is used internally by Card Services to look up CIS stuff. + +======================================================================*/ + +int pccard_read_tuple(struct pcmcia_socket *s, unsigned int function, cisdata_t code, void *parse) +{ + tuple_t tuple; + cisdata_t *buf; + int ret; + + buf = kmalloc(256, GFP_KERNEL); + if (buf == NULL) + return CS_OUT_OF_RESOURCE; + tuple.DesiredTuple = code; + tuple.Attributes = TUPLE_RETURN_COMMON; + ret = pccard_get_first_tuple(s, function, &tuple); + if (ret != CS_SUCCESS) goto done; + tuple.TupleData = buf; + tuple.TupleOffset = 0; + tuple.TupleDataMax = 255; + ret = pccard_get_tuple_data(s, &tuple); + if (ret != CS_SUCCESS) goto done; + ret = pccard_parse_tuple(&tuple, parse); +done: + kfree(buf); + return ret; +} +EXPORT_SYMBOL(pccard_read_tuple); + +/*====================================================================== + + This tries to determine if a card has a sensible CIS. It returns + the number of tuples in the CIS, or 0 if the CIS looks bad. The + checks include making sure several critical tuples are present and + valid; seeing if the total number of tuples is reasonable; and + looking for tuples that use reserved codes. + +======================================================================*/ + +int pccard_validate_cis(struct pcmcia_socket *s, unsigned int function, cisinfo_t *info) +{ + tuple_t *tuple; + cisparse_t *p; + int ret, reserved, dev_ok = 0, ident_ok = 0; + + if (!s) + return CS_BAD_HANDLE; + + tuple = kmalloc(sizeof(*tuple), GFP_KERNEL); + if (tuple == NULL) + return CS_OUT_OF_RESOURCE; + p = kmalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) { + kfree(tuple); + return CS_OUT_OF_RESOURCE; + } + + info->Chains = reserved = 0; + tuple->DesiredTuple = RETURN_FIRST_TUPLE; + tuple->Attributes = TUPLE_RETURN_COMMON; + ret = pccard_get_first_tuple(s, function, tuple); + if (ret != CS_SUCCESS) + goto done; + + /* First tuple should be DEVICE; we should really have either that + or a CFTABLE_ENTRY of some sort */ + if ((tuple->TupleCode == CISTPL_DEVICE) || + (pccard_read_tuple(s, function, CISTPL_CFTABLE_ENTRY, p) == CS_SUCCESS) || + (pccard_read_tuple(s, function, CISTPL_CFTABLE_ENTRY_CB, p) == CS_SUCCESS)) + dev_ok++; + + /* All cards should have a MANFID tuple, and/or a VERS_1 or VERS_2 + tuple, for card identification. Certain old D-Link and Linksys + cards have only a broken VERS_2 tuple; hence the bogus test. */ + if ((pccard_read_tuple(s, function, CISTPL_MANFID, p) == CS_SUCCESS) || + (pccard_read_tuple(s, function, CISTPL_VERS_1, p) == CS_SUCCESS) || + (pccard_read_tuple(s, function, CISTPL_VERS_2, p) != CS_NO_MORE_ITEMS)) + ident_ok++; + + if (!dev_ok && !ident_ok) + goto done; + + for (info->Chains = 1; info->Chains < MAX_TUPLES; info->Chains++) { + ret = pccard_get_next_tuple(s, function, tuple); + if (ret != CS_SUCCESS) break; + if (((tuple->TupleCode > 0x23) && (tuple->TupleCode < 0x40)) || + ((tuple->TupleCode > 0x47) && (tuple->TupleCode < 0x80)) || + ((tuple->TupleCode > 0x90) && (tuple->TupleCode < 0xff))) + reserved++; + } + if ((info->Chains == MAX_TUPLES) || (reserved > 5) || + ((!dev_ok || !ident_ok) && (info->Chains > 10))) + info->Chains = 0; + +done: + kfree(tuple); + kfree(p); + return CS_SUCCESS; +} +EXPORT_SYMBOL(pccard_validate_cis); diff --git a/drivers/pcmcia/cs.c b/drivers/pcmcia/cs.c new file mode 100644 index 000000000000..03fc885db1c5 --- /dev/null +++ b/drivers/pcmcia/cs.c @@ -0,0 +1,1917 @@ +/* + * cs.c -- Kernel Card Services - core services + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/config.h> +#include <linux/string.h> +#include <linux/major.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/pci.h> +#include <linux/device.h> +#include <asm/system.h> +#include <asm/irq.h> + +#define IN_CARD_SERVICES +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ds.h> +#include "cs_internal.h" + +#ifdef CONFIG_PCI +#define PCI_OPT " [pci]" +#else +#define PCI_OPT "" +#endif +#ifdef CONFIG_CARDBUS +#define CB_OPT " [cardbus]" +#else +#define CB_OPT "" +#endif +#ifdef CONFIG_PM +#define PM_OPT " [pm]" +#else +#define PM_OPT "" +#endif +#if !defined(CONFIG_CARDBUS) && !defined(CONFIG_PCI) && !defined(CONFIG_PM) +#define OPTIONS " none" +#else +#define OPTIONS PCI_OPT CB_OPT PM_OPT +#endif + +static const char *release = "Linux Kernel Card Services"; +static const char *options = "options: " OPTIONS; + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("Linux Kernel Card Services\noptions:" OPTIONS); +MODULE_LICENSE("GPL"); + +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0444) + +INT_MODULE_PARM(setup_delay, 10); /* centiseconds */ +INT_MODULE_PARM(resume_delay, 20); /* centiseconds */ +INT_MODULE_PARM(shutdown_delay, 3); /* centiseconds */ +INT_MODULE_PARM(vcc_settle, 40); /* centiseconds */ +INT_MODULE_PARM(reset_time, 10); /* usecs */ +INT_MODULE_PARM(unreset_delay, 10); /* centiseconds */ +INT_MODULE_PARM(unreset_check, 10); /* centiseconds */ +INT_MODULE_PARM(unreset_limit, 30); /* unreset_check's */ + +/* Access speed for attribute memory windows */ +INT_MODULE_PARM(cis_speed, 300); /* ns */ + +/* Access speed for IO windows */ +INT_MODULE_PARM(io_speed, 0); /* ns */ + +#ifdef DEBUG +static int pc_debug; + +module_param(pc_debug, int, 0644); + +int cs_debug_level(int level) +{ + return pc_debug > level; +} +#endif + +/*====================================================================*/ + +socket_state_t dead_socket = { + .csc_mask = SS_DETECT, +}; + + +/* List of all sockets, protected by a rwsem */ +LIST_HEAD(pcmcia_socket_list); +DECLARE_RWSEM(pcmcia_socket_list_rwsem); +EXPORT_SYMBOL(pcmcia_socket_list); +EXPORT_SYMBOL(pcmcia_socket_list_rwsem); + + +#ifdef CONFIG_PCMCIA_PROBE +/* mask ofIRQs already reserved by other cards, we should avoid using them */ +static u8 pcmcia_used_irq[NR_IRQS]; +#endif + +/*==================================================================== + + Low-level PC Card interface drivers need to register with Card + Services using these calls. + +======================================================================*/ + +/** + * socket drivers are expected to use the following callbacks in their + * .drv struct: + * - pcmcia_socket_dev_suspend + * - pcmcia_socket_dev_resume + * These functions check for the appropriate struct pcmcia_soket arrays, + * and pass them to the low-level functions pcmcia_{suspend,resume}_socket + */ +static int socket_resume(struct pcmcia_socket *skt); +static int socket_suspend(struct pcmcia_socket *skt); + +int pcmcia_socket_dev_suspend(struct device *dev, pm_message_t state) +{ + struct pcmcia_socket *socket; + + down_read(&pcmcia_socket_list_rwsem); + list_for_each_entry(socket, &pcmcia_socket_list, socket_list) { + if (socket->dev.dev != dev) + continue; + down(&socket->skt_sem); + socket_suspend(socket); + up(&socket->skt_sem); + } + up_read(&pcmcia_socket_list_rwsem); + + return 0; +} +EXPORT_SYMBOL(pcmcia_socket_dev_suspend); + +int pcmcia_socket_dev_resume(struct device *dev) +{ + struct pcmcia_socket *socket; + + down_read(&pcmcia_socket_list_rwsem); + list_for_each_entry(socket, &pcmcia_socket_list, socket_list) { + if (socket->dev.dev != dev) + continue; + down(&socket->skt_sem); + socket_resume(socket); + up(&socket->skt_sem); + } + up_read(&pcmcia_socket_list_rwsem); + + return 0; +} +EXPORT_SYMBOL(pcmcia_socket_dev_resume); + + +struct pcmcia_socket * pcmcia_get_socket(struct pcmcia_socket *skt) +{ + struct class_device *cl_dev = class_device_get(&skt->dev); + if (!cl_dev) + return NULL; + skt = class_get_devdata(cl_dev); + if (!try_module_get(skt->owner)) { + class_device_put(&skt->dev); + return NULL; + } + return (skt); +} +EXPORT_SYMBOL(pcmcia_get_socket); + + +void pcmcia_put_socket(struct pcmcia_socket *skt) +{ + module_put(skt->owner); + class_device_put(&skt->dev); +} +EXPORT_SYMBOL(pcmcia_put_socket); + + +static void pcmcia_release_socket(struct class_device *class_dev) +{ + struct pcmcia_socket *socket = class_get_devdata(class_dev); + + complete(&socket->socket_released); +} + +static int pccardd(void *__skt); + +/** + * pcmcia_register_socket - add a new pcmcia socket device + */ +int pcmcia_register_socket(struct pcmcia_socket *socket) +{ + int ret; + + if (!socket || !socket->ops || !socket->dev.dev || !socket->resource_ops) + return -EINVAL; + + cs_dbg(socket, 0, "pcmcia_register_socket(0x%p)\n", socket->ops); + + spin_lock_init(&socket->lock); + + if (socket->resource_ops->init) { + ret = socket->resource_ops->init(socket); + if (ret) + return (ret); + } + + /* try to obtain a socket number [yes, it gets ugly if we + * register more than 2^sizeof(unsigned int) pcmcia + * sockets... but the socket number is deprecated + * anyways, so I don't care] */ + down_write(&pcmcia_socket_list_rwsem); + if (list_empty(&pcmcia_socket_list)) + socket->sock = 0; + else { + unsigned int found, i = 1; + struct pcmcia_socket *tmp; + do { + found = 1; + list_for_each_entry(tmp, &pcmcia_socket_list, socket_list) { + if (tmp->sock == i) + found = 0; + } + i++; + } while (!found); + socket->sock = i - 1; + } + list_add_tail(&socket->socket_list, &pcmcia_socket_list); + up_write(&pcmcia_socket_list_rwsem); + + + /* set proper values in socket->dev */ + socket->dev.class_data = socket; + socket->dev.class = &pcmcia_socket_class; + snprintf(socket->dev.class_id, BUS_ID_SIZE, "pcmcia_socket%u", socket->sock); + + /* base address = 0, map = 0 */ + socket->cis_mem.flags = 0; + socket->cis_mem.speed = cis_speed; + + INIT_LIST_HEAD(&socket->cis_cache); + + init_completion(&socket->socket_released); + init_completion(&socket->thread_done); + init_waitqueue_head(&socket->thread_wait); + init_MUTEX(&socket->skt_sem); + spin_lock_init(&socket->thread_lock); + + ret = kernel_thread(pccardd, socket, CLONE_KERNEL); + if (ret < 0) + goto err; + + wait_for_completion(&socket->thread_done); + if(!socket->thread) { + printk(KERN_WARNING "PCMCIA: warning: socket thread for socket %p did not start\n", socket); + return -EIO; + } + pcmcia_parse_events(socket, SS_DETECT); + + return 0; + + err: + down_write(&pcmcia_socket_list_rwsem); + list_del(&socket->socket_list); + up_write(&pcmcia_socket_list_rwsem); + return ret; +} /* pcmcia_register_socket */ +EXPORT_SYMBOL(pcmcia_register_socket); + + +/** + * pcmcia_unregister_socket - remove a pcmcia socket device + */ +void pcmcia_unregister_socket(struct pcmcia_socket *socket) +{ + if (!socket) + return; + + cs_dbg(socket, 0, "pcmcia_unregister_socket(0x%p)\n", socket->ops); + + if (socket->thread) { + init_completion(&socket->thread_done); + socket->thread = NULL; + wake_up(&socket->thread_wait); + wait_for_completion(&socket->thread_done); + } + release_cis_mem(socket); + + /* remove from our own list */ + down_write(&pcmcia_socket_list_rwsem); + list_del(&socket->socket_list); + up_write(&pcmcia_socket_list_rwsem); + + /* wait for sysfs to drop all references */ + release_resource_db(socket); + wait_for_completion(&socket->socket_released); +} /* pcmcia_unregister_socket */ +EXPORT_SYMBOL(pcmcia_unregister_socket); + + +struct pcmcia_socket * pcmcia_get_socket_by_nr(unsigned int nr) +{ + struct pcmcia_socket *s; + + down_read(&pcmcia_socket_list_rwsem); + list_for_each_entry(s, &pcmcia_socket_list, socket_list) + if (s->sock == nr) { + up_read(&pcmcia_socket_list_rwsem); + return s; + } + up_read(&pcmcia_socket_list_rwsem); + + return NULL; + +} +EXPORT_SYMBOL(pcmcia_get_socket_by_nr); + + +/*====================================================================== + + socket_setup() and shutdown_socket() are called by the main event + handler when card insertion and removal events are received. + socket_setup() turns on socket power and resets the socket, in two stages. + shutdown_socket() unconfigures a socket and turns off socket power. + +======================================================================*/ + +static void shutdown_socket(struct pcmcia_socket *s) +{ + cs_dbg(s, 1, "shutdown_socket\n"); + + /* Blank out the socket state */ + s->socket = dead_socket; + s->ops->init(s); + s->ops->set_socket(s, &s->socket); + s->irq.AssignedIRQ = s->irq.Config = 0; + s->lock_count = 0; + destroy_cis_cache(s); +#ifdef CONFIG_CARDBUS + cb_free(s); +#endif + s->functions = 0; + if (s->config) { + kfree(s->config); + s->config = NULL; + } + + { + int status; + s->ops->get_status(s, &status); + if (status & SS_POWERON) { + printk(KERN_ERR "PCMCIA: socket %p: *** DANGER *** unable to remove socket power\n", s); + } + } +} /* shutdown_socket */ + +/*====================================================================== + + The central event handler. Send_event() sends an event to the + 16-bit subsystem, which then calls the relevant device drivers. + Parse_events() interprets the event bits from + a card status change report. Do_shutdown() handles the high + priority stuff associated with a card removal. + +======================================================================*/ + + +/* NOTE: send_event needs to be called with skt->sem held. */ + +static int send_event(struct pcmcia_socket *s, event_t event, int priority) +{ + int ret; + + if (s->state & SOCKET_CARDBUS) + return 0; + + cs_dbg(s, 1, "send_event(event %d, pri %d, callback 0x%p)\n", + event, priority, s->callback); + + if (!s->callback) + return 0; + if (!try_module_get(s->callback->owner)) + return 0; + + ret = s->callback->event(s, event, priority); + + module_put(s->callback->owner); + + return ret; +} + +static void socket_remove_drivers(struct pcmcia_socket *skt) +{ + cs_dbg(skt, 4, "remove_drivers\n"); + + send_event(skt, CS_EVENT_CARD_REMOVAL, CS_EVENT_PRI_HIGH); +} + +static void socket_shutdown(struct pcmcia_socket *skt) +{ + cs_dbg(skt, 4, "shutdown\n"); + + socket_remove_drivers(skt); + skt->state &= SOCKET_INUSE|SOCKET_PRESENT; + msleep(shutdown_delay * 10); + skt->state &= SOCKET_INUSE; + shutdown_socket(skt); +} + +static int socket_reset(struct pcmcia_socket *skt) +{ + int status, i; + + cs_dbg(skt, 4, "reset\n"); + + skt->socket.flags |= SS_OUTPUT_ENA | SS_RESET; + skt->ops->set_socket(skt, &skt->socket); + udelay((long)reset_time); + + skt->socket.flags &= ~SS_RESET; + skt->ops->set_socket(skt, &skt->socket); + + msleep(unreset_delay * 10); + for (i = 0; i < unreset_limit; i++) { + skt->ops->get_status(skt, &status); + + if (!(status & SS_DETECT)) + return CS_NO_CARD; + + if (status & SS_READY) + return CS_SUCCESS; + + msleep(unreset_check * 10); + } + + cs_err(skt, "time out after reset.\n"); + return CS_GENERAL_FAILURE; +} + +static int socket_setup(struct pcmcia_socket *skt, int initial_delay) +{ + int status, i; + + cs_dbg(skt, 4, "setup\n"); + + skt->ops->get_status(skt, &status); + if (!(status & SS_DETECT)) + return CS_NO_CARD; + + msleep(initial_delay * 10); + + for (i = 0; i < 100; i++) { + skt->ops->get_status(skt, &status); + if (!(status & SS_DETECT)) + return CS_NO_CARD; + + if (!(status & SS_PENDING)) + break; + + msleep(100); + } + + if (status & SS_PENDING) { + cs_err(skt, "voltage interrogation timed out.\n"); + return CS_GENERAL_FAILURE; + } + + if (status & SS_CARDBUS) { + skt->state |= SOCKET_CARDBUS; +#ifndef CONFIG_CARDBUS + cs_err(skt, "cardbus cards are not supported.\n"); + return CS_BAD_TYPE; +#endif + } + + /* + * Decode the card voltage requirements, and apply power to the card. + */ + if (status & SS_3VCARD) + skt->socket.Vcc = skt->socket.Vpp = 33; + else if (!(status & SS_XVCARD)) + skt->socket.Vcc = skt->socket.Vpp = 50; + else { + cs_err(skt, "unsupported voltage key.\n"); + return CS_BAD_TYPE; + } + skt->socket.flags = 0; + skt->ops->set_socket(skt, &skt->socket); + + /* + * Wait "vcc_settle" for the supply to stabilise. + */ + msleep(vcc_settle * 10); + + skt->ops->get_status(skt, &status); + if (!(status & SS_POWERON)) { + cs_err(skt, "unable to apply power.\n"); + return CS_BAD_TYPE; + } + + return socket_reset(skt); +} + +/* + * Handle card insertion. Setup the socket, reset the card, + * and then tell the rest of PCMCIA that a card is present. + */ +static int socket_insert(struct pcmcia_socket *skt) +{ + int ret; + + cs_dbg(skt, 4, "insert\n"); + + if (!cs_socket_get(skt)) + return CS_NO_CARD; + + ret = socket_setup(skt, setup_delay); + if (ret == CS_SUCCESS) { + skt->state |= SOCKET_PRESENT; +#ifdef CONFIG_CARDBUS + if (skt->state & SOCKET_CARDBUS) { + cb_alloc(skt); + skt->state |= SOCKET_CARDBUS_CONFIG; + } +#endif + cs_dbg(skt, 4, "insert done\n"); + + send_event(skt, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW); + } else { + socket_shutdown(skt); + cs_socket_put(skt); + } + + return ret; +} + +static int socket_suspend(struct pcmcia_socket *skt) +{ + if (skt->state & SOCKET_SUSPEND) + return CS_IN_USE; + + send_event(skt, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW); + skt->socket = dead_socket; + skt->ops->set_socket(skt, &skt->socket); + if (skt->ops->suspend) + skt->ops->suspend(skt); + skt->state |= SOCKET_SUSPEND; + + return CS_SUCCESS; +} + +/* + * Resume a socket. If a card is present, verify its CIS against + * our cached copy. If they are different, the card has been + * replaced, and we need to tell the drivers. + */ +static int socket_resume(struct pcmcia_socket *skt) +{ + int ret; + + if (!(skt->state & SOCKET_SUSPEND)) + return CS_IN_USE; + + skt->socket = dead_socket; + skt->ops->init(skt); + skt->ops->set_socket(skt, &skt->socket); + + if (!(skt->state & SOCKET_PRESENT)) { + skt->state &= ~SOCKET_SUSPEND; + return socket_insert(skt); + } + + ret = socket_setup(skt, resume_delay); + if (ret == CS_SUCCESS) { + /* + * FIXME: need a better check here for cardbus cards. + */ + if (verify_cis_cache(skt) != 0) { + cs_dbg(skt, 4, "cis mismatch - different card\n"); + socket_remove_drivers(skt); + destroy_cis_cache(skt); + /* + * Workaround: give DS time to schedule removal. + * Remove me once the 100ms delay is eliminated + * in ds.c + */ + msleep(200); + send_event(skt, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW); + } else { + cs_dbg(skt, 4, "cis matches cache\n"); + send_event(skt, CS_EVENT_PM_RESUME, CS_EVENT_PRI_LOW); + } + } else { + socket_shutdown(skt); + cs_socket_put(skt); + } + + skt->state &= ~SOCKET_SUSPEND; + + return CS_SUCCESS; +} + +static void socket_remove(struct pcmcia_socket *skt) +{ + socket_shutdown(skt); + cs_socket_put(skt); +} + +/* + * Process a socket card detect status change. + * + * If we don't have a card already present, delay the detect event for + * about 20ms (to be on the safe side) before reading the socket status. + * + * Some i82365-based systems send multiple SS_DETECT events during card + * insertion, and the "card present" status bit seems to bounce. This + * will probably be true with GPIO-based card detection systems after + * the product has aged. + */ +static void socket_detect_change(struct pcmcia_socket *skt) +{ + if (!(skt->state & SOCKET_SUSPEND)) { + int status; + + if (!(skt->state & SOCKET_PRESENT)) + msleep(20); + + skt->ops->get_status(skt, &status); + if ((skt->state & SOCKET_PRESENT) && + !(status & SS_DETECT)) + socket_remove(skt); + if (!(skt->state & SOCKET_PRESENT) && + (status & SS_DETECT)) + socket_insert(skt); + } +} + +static int pccardd(void *__skt) +{ + struct pcmcia_socket *skt = __skt; + DECLARE_WAITQUEUE(wait, current); + int ret; + + daemonize("pccardd"); + + skt->thread = current; + skt->socket = dead_socket; + skt->ops->init(skt); + skt->ops->set_socket(skt, &skt->socket); + + /* register with the device core */ + ret = class_device_register(&skt->dev); + if (ret) { + printk(KERN_WARNING "PCMCIA: unable to register socket 0x%p\n", + skt); + skt->thread = NULL; + complete_and_exit(&skt->thread_done, 0); + } + complete(&skt->thread_done); + + add_wait_queue(&skt->thread_wait, &wait); + for (;;) { + unsigned long flags; + unsigned int events; + + set_current_state(TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&skt->thread_lock, flags); + events = skt->thread_events; + skt->thread_events = 0; + spin_unlock_irqrestore(&skt->thread_lock, flags); + + if (events) { + down(&skt->skt_sem); + if (events & SS_DETECT) + socket_detect_change(skt); + if (events & SS_BATDEAD) + send_event(skt, CS_EVENT_BATTERY_DEAD, CS_EVENT_PRI_LOW); + if (events & SS_BATWARN) + send_event(skt, CS_EVENT_BATTERY_LOW, CS_EVENT_PRI_LOW); + if (events & SS_READY) + send_event(skt, CS_EVENT_READY_CHANGE, CS_EVENT_PRI_LOW); + up(&skt->skt_sem); + continue; + } + + schedule(); + try_to_freeze(PF_FREEZE); + + if (!skt->thread) + break; + } + remove_wait_queue(&skt->thread_wait, &wait); + + /* remove from the device core */ + class_device_unregister(&skt->dev); + + complete_and_exit(&skt->thread_done, 0); +} + +/* + * Yenta (at least) probes interrupts before registering the socket and + * starting the handler thread. + */ +void pcmcia_parse_events(struct pcmcia_socket *s, u_int events) +{ + cs_dbg(s, 4, "parse_events: events %08x\n", events); + if (s->thread) { + spin_lock(&s->thread_lock); + s->thread_events |= events; + spin_unlock(&s->thread_lock); + + wake_up(&s->thread_wait); + } +} /* pcmcia_parse_events */ + + +/*====================================================================== + + Special stuff for managing IO windows, because they are scarce. + +======================================================================*/ + +static int alloc_io_space(struct pcmcia_socket *s, u_int attr, ioaddr_t *base, + ioaddr_t num, u_int lines) +{ + int i; + kio_addr_t try, align; + + align = (*base) ? (lines ? 1<<lines : 0) : 1; + if (align && (align < num)) { + if (*base) { + cs_dbg(s, 0, "odd IO request: num %#x align %#lx\n", + num, align); + align = 0; + } else + while (align && (align < num)) align <<= 1; + } + if (*base & ~(align-1)) { + cs_dbg(s, 0, "odd IO request: base %#x align %#lx\n", + *base, align); + align = 0; + } + if ((s->features & SS_CAP_STATIC_MAP) && s->io_offset) { + *base = s->io_offset | (*base & 0x0fff); + return 0; + } + /* Check for an already-allocated window that must conflict with + what was asked for. It is a hack because it does not catch all + potential conflicts, just the most obvious ones. */ + for (i = 0; i < MAX_IO_WIN; i++) + if ((s->io[i].NumPorts != 0) && + ((s->io[i].BasePort & (align-1)) == *base)) + return 1; + for (i = 0; i < MAX_IO_WIN; i++) { + if (s->io[i].NumPorts == 0) { + s->io[i].res = find_io_region(*base, num, align, s); + if (s->io[i].res) { + s->io[i].Attributes = attr; + s->io[i].BasePort = *base = s->io[i].res->start; + s->io[i].NumPorts = s->io[i].InUse = num; + break; + } else + return 1; + } else if (s->io[i].Attributes != attr) + continue; + /* Try to extend top of window */ + try = s->io[i].BasePort + s->io[i].NumPorts; + if ((*base == 0) || (*base == try)) + if (adjust_io_region(s->io[i].res, s->io[i].res->start, + s->io[i].res->end + num, s) == 0) { + *base = try; + s->io[i].NumPorts += num; + s->io[i].InUse += num; + break; + } + /* Try to extend bottom of window */ + try = s->io[i].BasePort - num; + if ((*base == 0) || (*base == try)) + if (adjust_io_region(s->io[i].res, s->io[i].res->start - num, + s->io[i].res->end, s) == 0) { + s->io[i].BasePort = *base = try; + s->io[i].NumPorts += num; + s->io[i].InUse += num; + break; + } + } + return (i == MAX_IO_WIN); +} /* alloc_io_space */ + +static void release_io_space(struct pcmcia_socket *s, ioaddr_t base, + ioaddr_t num) +{ + int i; + + for (i = 0; i < MAX_IO_WIN; i++) { + if ((s->io[i].BasePort <= base) && + (s->io[i].BasePort+s->io[i].NumPorts >= base+num)) { + s->io[i].InUse -= num; + /* Free the window if no one else is using it */ + if (s->io[i].InUse == 0) { + s->io[i].NumPorts = 0; + release_resource(s->io[i].res); + kfree(s->io[i].res); + s->io[i].res = NULL; + } + } + } +} + +/*====================================================================== + + Access_configuration_register() reads and writes configuration + registers in attribute memory. Memory window 0 is reserved for + this and the tuple reading services. + +======================================================================*/ + +int pccard_access_configuration_register(struct pcmcia_socket *s, + unsigned int function, + conf_reg_t *reg) +{ + config_t *c; + int addr; + u_char val; + + if (!s || !s->config) + return CS_NO_CARD; + + c = &s->config[function]; + + if (c == NULL) + return CS_NO_CARD; + + if (!(c->state & CONFIG_LOCKED)) + return CS_CONFIGURATION_LOCKED; + + addr = (c->ConfigBase + reg->Offset) >> 1; + + switch (reg->Action) { + case CS_READ: + read_cis_mem(s, 1, addr, 1, &val); + reg->Value = val; + break; + case CS_WRITE: + val = reg->Value; + write_cis_mem(s, 1, addr, 1, &val); + break; + default: + return CS_BAD_ARGS; + break; + } + return CS_SUCCESS; +} /* access_configuration_register */ +EXPORT_SYMBOL(pccard_access_configuration_register); + + +/*====================================================================*/ + +int pccard_get_configuration_info(struct pcmcia_socket *s, + unsigned int function, + config_info_t *config) +{ + config_t *c; + + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + + config->Function = function; + +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) { + memset(config, 0, sizeof(config_info_t)); + config->Vcc = s->socket.Vcc; + config->Vpp1 = config->Vpp2 = s->socket.Vpp; + config->Option = s->cb_dev->subordinate->number; + if (s->state & SOCKET_CARDBUS_CONFIG) { + config->Attributes = CONF_VALID_CLIENT; + config->IntType = INT_CARDBUS; + config->AssignedIRQ = s->irq.AssignedIRQ; + if (config->AssignedIRQ) + config->Attributes |= CONF_ENABLE_IRQ; + config->BasePort1 = s->io[0].BasePort; + config->NumPorts1 = s->io[0].NumPorts; + } + return CS_SUCCESS; + } +#endif + + c = (s->config != NULL) ? &s->config[function] : NULL; + + if ((c == NULL) || !(c->state & CONFIG_LOCKED)) { + config->Attributes = 0; + config->Vcc = s->socket.Vcc; + config->Vpp1 = config->Vpp2 = s->socket.Vpp; + return CS_SUCCESS; + } + + /* !!! This is a hack !!! */ + memcpy(&config->Attributes, &c->Attributes, sizeof(config_t)); + config->Attributes |= CONF_VALID_CLIENT; + config->CardValues = c->CardValues; + config->IRQAttributes = c->irq.Attributes; + config->AssignedIRQ = s->irq.AssignedIRQ; + config->BasePort1 = c->io.BasePort1; + config->NumPorts1 = c->io.NumPorts1; + config->Attributes1 = c->io.Attributes1; + config->BasePort2 = c->io.BasePort2; + config->NumPorts2 = c->io.NumPorts2; + config->Attributes2 = c->io.Attributes2; + config->IOAddrLines = c->io.IOAddrLines; + + return CS_SUCCESS; +} /* get_configuration_info */ +EXPORT_SYMBOL(pccard_get_configuration_info); + +/*====================================================================== + + Return information about this version of Card Services. + +======================================================================*/ + +int pcmcia_get_card_services_info(servinfo_t *info) +{ + unsigned int socket_count = 0; + struct list_head *tmp; + info->Signature[0] = 'C'; + info->Signature[1] = 'S'; + down_read(&pcmcia_socket_list_rwsem); + list_for_each(tmp, &pcmcia_socket_list) + socket_count++; + up_read(&pcmcia_socket_list_rwsem); + info->Count = socket_count; + info->Revision = CS_RELEASE_CODE; + info->CSLevel = 0x0210; + info->VendorString = (char *)release; + return CS_SUCCESS; +} /* get_card_services_info */ + + +/*====================================================================*/ + +int pcmcia_get_window(struct pcmcia_socket *s, window_handle_t *handle, int idx, win_req_t *req) +{ + window_t *win; + int w; + + if (!s || !(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + for (w = idx; w < MAX_WIN; w++) + if (s->state & SOCKET_WIN_REQ(w)) break; + if (w == MAX_WIN) + return CS_NO_MORE_ITEMS; + win = &s->win[w]; + req->Base = win->ctl.res->start; + req->Size = win->ctl.res->end - win->ctl.res->start + 1; + req->AccessSpeed = win->ctl.speed; + req->Attributes = 0; + if (win->ctl.flags & MAP_ATTRIB) + req->Attributes |= WIN_MEMORY_TYPE_AM; + if (win->ctl.flags & MAP_ACTIVE) + req->Attributes |= WIN_ENABLE; + if (win->ctl.flags & MAP_16BIT) + req->Attributes |= WIN_DATA_WIDTH_16; + if (win->ctl.flags & MAP_USE_WAIT) + req->Attributes |= WIN_USE_WAIT; + *handle = win; + return CS_SUCCESS; +} /* get_window */ +EXPORT_SYMBOL(pcmcia_get_window); + +/*===================================================================== + + Return the PCI device associated with a card.. + +======================================================================*/ + +#ifdef CONFIG_CARDBUS + +struct pci_bus *pcmcia_lookup_bus(struct pcmcia_socket *s) +{ + if (!s || !(s->state & SOCKET_CARDBUS)) + return NULL; + + return s->cb_dev->subordinate; +} + +EXPORT_SYMBOL(pcmcia_lookup_bus); + +#endif + +/*====================================================================== + + Get the current socket state bits. We don't support the latched + SocketState yet: I haven't seen any point for it. + +======================================================================*/ + +int pccard_get_status(struct pcmcia_socket *s, unsigned int function, cs_status_t *status) +{ + config_t *c; + int val; + + s->ops->get_status(s, &val); + status->CardState = status->SocketState = 0; + status->CardState |= (val & SS_DETECT) ? CS_EVENT_CARD_DETECT : 0; + status->CardState |= (val & SS_CARDBUS) ? CS_EVENT_CB_DETECT : 0; + status->CardState |= (val & SS_3VCARD) ? CS_EVENT_3VCARD : 0; + status->CardState |= (val & SS_XVCARD) ? CS_EVENT_XVCARD : 0; + if (s->state & SOCKET_SUSPEND) + status->CardState |= CS_EVENT_PM_SUSPEND; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + + c = (s->config != NULL) ? &s->config[function] : NULL; + if ((c != NULL) && (c->state & CONFIG_LOCKED) && + (c->IntType & (INT_MEMORY_AND_IO | INT_ZOOMED_VIDEO))) { + u_char reg; + if (c->Present & PRESENT_PIN_REPLACE) { + read_cis_mem(s, 1, (c->ConfigBase+CISREG_PRR)>>1, 1, ®); + status->CardState |= + (reg & PRR_WP_STATUS) ? CS_EVENT_WRITE_PROTECT : 0; + status->CardState |= + (reg & PRR_READY_STATUS) ? CS_EVENT_READY_CHANGE : 0; + status->CardState |= + (reg & PRR_BVD2_STATUS) ? CS_EVENT_BATTERY_LOW : 0; + status->CardState |= + (reg & PRR_BVD1_STATUS) ? CS_EVENT_BATTERY_DEAD : 0; + } else { + /* No PRR? Then assume we're always ready */ + status->CardState |= CS_EVENT_READY_CHANGE; + } + if (c->Present & PRESENT_EXT_STATUS) { + read_cis_mem(s, 1, (c->ConfigBase+CISREG_ESR)>>1, 1, ®); + status->CardState |= + (reg & ESR_REQ_ATTN) ? CS_EVENT_REQUEST_ATTENTION : 0; + } + return CS_SUCCESS; + } + status->CardState |= + (val & SS_WRPROT) ? CS_EVENT_WRITE_PROTECT : 0; + status->CardState |= + (val & SS_BATDEAD) ? CS_EVENT_BATTERY_DEAD : 0; + status->CardState |= + (val & SS_BATWARN) ? CS_EVENT_BATTERY_LOW : 0; + status->CardState |= + (val & SS_READY) ? CS_EVENT_READY_CHANGE : 0; + return CS_SUCCESS; +} /* get_status */ +EXPORT_SYMBOL(pccard_get_status); + +/*====================================================================== + + Change the card address of an already open memory window. + +======================================================================*/ + +int pcmcia_get_mem_page(window_handle_t win, memreq_t *req) +{ + if ((win == NULL) || (win->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + req->Page = 0; + req->CardOffset = win->ctl.card_start; + return CS_SUCCESS; +} /* get_mem_page */ + +int pcmcia_map_mem_page(window_handle_t win, memreq_t *req) +{ + struct pcmcia_socket *s; + if ((win == NULL) || (win->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + if (req->Page != 0) + return CS_BAD_PAGE; + s = win->sock; + win->ctl.card_start = req->CardOffset; + if (s->ops->set_mem_map(s, &win->ctl) != 0) + return CS_BAD_OFFSET; + return CS_SUCCESS; +} /* map_mem_page */ + +/*====================================================================== + + Modify a locked socket configuration + +======================================================================*/ + +int pcmcia_modify_configuration(client_handle_t handle, + modconf_t *mod) +{ + struct pcmcia_socket *s; + config_t *c; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); c = CONFIG(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (!(c->state & CONFIG_LOCKED)) + return CS_CONFIGURATION_LOCKED; + + if (mod->Attributes & CONF_IRQ_CHANGE_VALID) { + if (mod->Attributes & CONF_ENABLE_IRQ) { + c->Attributes |= CONF_ENABLE_IRQ; + s->socket.io_irq = s->irq.AssignedIRQ; + } else { + c->Attributes &= ~CONF_ENABLE_IRQ; + s->socket.io_irq = 0; + } + s->ops->set_socket(s, &s->socket); + } + + if (mod->Attributes & CONF_VCC_CHANGE_VALID) + return CS_BAD_VCC; + + /* We only allow changing Vpp1 and Vpp2 to the same value */ + if ((mod->Attributes & CONF_VPP1_CHANGE_VALID) && + (mod->Attributes & CONF_VPP2_CHANGE_VALID)) { + if (mod->Vpp1 != mod->Vpp2) + return CS_BAD_VPP; + c->Vpp1 = c->Vpp2 = s->socket.Vpp = mod->Vpp1; + if (s->ops->set_socket(s, &s->socket)) + return CS_BAD_VPP; + } else if ((mod->Attributes & CONF_VPP1_CHANGE_VALID) || + (mod->Attributes & CONF_VPP2_CHANGE_VALID)) + return CS_BAD_VPP; + + return CS_SUCCESS; +} /* modify_configuration */ + +/* register pcmcia_callback */ +int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c) +{ + int ret = 0; + + /* s->skt_sem also protects s->callback */ + down(&s->skt_sem); + + if (c) { + /* registration */ + if (s->callback) { + ret = -EBUSY; + goto err; + } + + s->callback = c; + + if ((s->state & (SOCKET_PRESENT|SOCKET_CARDBUS)) == SOCKET_PRESENT) + send_event(s, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW); + } else + s->callback = NULL; + err: + up(&s->skt_sem); + + return ret; +} +EXPORT_SYMBOL(pccard_register_pcmcia); + +/*====================================================================*/ + +int pcmcia_release_configuration(client_handle_t handle) +{ + pccard_io_map io = { 0, 0, 0, 0, 1 }; + struct pcmcia_socket *s; + int i; + + if (CHECK_HANDLE(handle) || + !(handle->state & CLIENT_CONFIG_LOCKED)) + return CS_BAD_HANDLE; + handle->state &= ~CLIENT_CONFIG_LOCKED; + s = SOCKET(handle); + +#ifdef CONFIG_CARDBUS + if (handle->state & CLIENT_CARDBUS) + return CS_SUCCESS; +#endif + + if (!(handle->state & CLIENT_STALE)) { + config_t *c = CONFIG(handle); + if (--(s->lock_count) == 0) { + s->socket.flags = SS_OUTPUT_ENA; /* Is this correct? */ + s->socket.Vpp = 0; + s->socket.io_irq = 0; + s->ops->set_socket(s, &s->socket); + } + if (c->state & CONFIG_IO_REQ) + for (i = 0; i < MAX_IO_WIN; i++) { + if (s->io[i].NumPorts == 0) + continue; + s->io[i].Config--; + if (s->io[i].Config != 0) + continue; + io.map = i; + s->ops->set_io_map(s, &io); + } + c->state &= ~CONFIG_LOCKED; + } + + return CS_SUCCESS; +} /* release_configuration */ + +/*====================================================================== + + Release_io() releases the I/O ranges allocated by a client. This + may be invoked some time after a card ejection has already dumped + the actual socket configuration, so if the client is "stale", we + don't bother checking the port ranges against the current socket + values. + +======================================================================*/ + +int pcmcia_release_io(client_handle_t handle, io_req_t *req) +{ + struct pcmcia_socket *s; + + if (CHECK_HANDLE(handle) || !(handle->state & CLIENT_IO_REQ)) + return CS_BAD_HANDLE; + handle->state &= ~CLIENT_IO_REQ; + s = SOCKET(handle); + +#ifdef CONFIG_CARDBUS + if (handle->state & CLIENT_CARDBUS) + return CS_SUCCESS; +#endif + + if (!(handle->state & CLIENT_STALE)) { + config_t *c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + if ((c->io.BasePort1 != req->BasePort1) || + (c->io.NumPorts1 != req->NumPorts1) || + (c->io.BasePort2 != req->BasePort2) || + (c->io.NumPorts2 != req->NumPorts2)) + return CS_BAD_ARGS; + c->state &= ~CONFIG_IO_REQ; + } + + release_io_space(s, req->BasePort1, req->NumPorts1); + if (req->NumPorts2) + release_io_space(s, req->BasePort2, req->NumPorts2); + + return CS_SUCCESS; +} /* release_io */ + +/*====================================================================*/ + +int pcmcia_release_irq(client_handle_t handle, irq_req_t *req) +{ + struct pcmcia_socket *s; + if (CHECK_HANDLE(handle) || !(handle->state & CLIENT_IRQ_REQ)) + return CS_BAD_HANDLE; + handle->state &= ~CLIENT_IRQ_REQ; + s = SOCKET(handle); + + if (!(handle->state & CLIENT_STALE)) { + config_t *c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + if (c->irq.Attributes != req->Attributes) + return CS_BAD_ATTRIBUTE; + if (s->irq.AssignedIRQ != req->AssignedIRQ) + return CS_BAD_IRQ; + if (--s->irq.Config == 0) { + c->state &= ~CONFIG_IRQ_REQ; + s->irq.AssignedIRQ = 0; + } + } + + if (req->Attributes & IRQ_HANDLE_PRESENT) { + free_irq(req->AssignedIRQ, req->Instance); + } + +#ifdef CONFIG_PCMCIA_PROBE + pcmcia_used_irq[req->AssignedIRQ]--; +#endif + + return CS_SUCCESS; +} /* cs_release_irq */ + +/*====================================================================*/ + +int pcmcia_release_window(window_handle_t win) +{ + struct pcmcia_socket *s; + + if ((win == NULL) || (win->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + s = win->sock; + if (!(win->handle->state & CLIENT_WIN_REQ(win->index))) + return CS_BAD_HANDLE; + + /* Shut down memory window */ + win->ctl.flags &= ~MAP_ACTIVE; + s->ops->set_mem_map(s, &win->ctl); + s->state &= ~SOCKET_WIN_REQ(win->index); + + /* Release system memory */ + if (win->ctl.res) { + release_resource(win->ctl.res); + kfree(win->ctl.res); + win->ctl.res = NULL; + } + win->handle->state &= ~CLIENT_WIN_REQ(win->index); + + win->magic = 0; + + return CS_SUCCESS; +} /* release_window */ + +/*====================================================================*/ + +int pcmcia_request_configuration(client_handle_t handle, + config_req_t *req) +{ + int i; + u_int base; + struct pcmcia_socket *s; + config_t *c; + pccard_io_map iomap; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + +#ifdef CONFIG_CARDBUS + if (handle->state & CLIENT_CARDBUS) + return CS_UNSUPPORTED_MODE; +#endif + + if (req->IntType & INT_CARDBUS) + return CS_UNSUPPORTED_MODE; + c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + + /* Do power control. We don't allow changes in Vcc. */ + if (s->socket.Vcc != req->Vcc) + return CS_BAD_VCC; + if (req->Vpp1 != req->Vpp2) + return CS_BAD_VPP; + s->socket.Vpp = req->Vpp1; + if (s->ops->set_socket(s, &s->socket)) + return CS_BAD_VPP; + + c->Vcc = req->Vcc; c->Vpp1 = c->Vpp2 = req->Vpp1; + + /* Pick memory or I/O card, DMA mode, interrupt */ + c->IntType = req->IntType; + c->Attributes = req->Attributes; + if (req->IntType & INT_MEMORY_AND_IO) + s->socket.flags |= SS_IOCARD; + if (req->IntType & INT_ZOOMED_VIDEO) + s->socket.flags |= SS_ZVCARD | SS_IOCARD; + if (req->Attributes & CONF_ENABLE_DMA) + s->socket.flags |= SS_DMA_MODE; + if (req->Attributes & CONF_ENABLE_SPKR) + s->socket.flags |= SS_SPKR_ENA; + if (req->Attributes & CONF_ENABLE_IRQ) + s->socket.io_irq = s->irq.AssignedIRQ; + else + s->socket.io_irq = 0; + s->ops->set_socket(s, &s->socket); + s->lock_count++; + + /* Set up CIS configuration registers */ + base = c->ConfigBase = req->ConfigBase; + c->Present = c->CardValues = req->Present; + if (req->Present & PRESENT_COPY) { + c->Copy = req->Copy; + write_cis_mem(s, 1, (base + CISREG_SCR)>>1, 1, &c->Copy); + } + if (req->Present & PRESENT_OPTION) { + if (s->functions == 1) { + c->Option = req->ConfigIndex & COR_CONFIG_MASK; + } else { + c->Option = req->ConfigIndex & COR_MFC_CONFIG_MASK; + c->Option |= COR_FUNC_ENA|COR_IREQ_ENA; + if (req->Present & PRESENT_IOBASE_0) + c->Option |= COR_ADDR_DECODE; + } + if (c->state & CONFIG_IRQ_REQ) + if (!(c->irq.Attributes & IRQ_FORCED_PULSE)) + c->Option |= COR_LEVEL_REQ; + write_cis_mem(s, 1, (base + CISREG_COR)>>1, 1, &c->Option); + mdelay(40); + } + if (req->Present & PRESENT_STATUS) { + c->Status = req->Status; + write_cis_mem(s, 1, (base + CISREG_CCSR)>>1, 1, &c->Status); + } + if (req->Present & PRESENT_PIN_REPLACE) { + c->Pin = req->Pin; + write_cis_mem(s, 1, (base + CISREG_PRR)>>1, 1, &c->Pin); + } + if (req->Present & PRESENT_EXT_STATUS) { + c->ExtStatus = req->ExtStatus; + write_cis_mem(s, 1, (base + CISREG_ESR)>>1, 1, &c->ExtStatus); + } + if (req->Present & PRESENT_IOBASE_0) { + u_char b = c->io.BasePort1 & 0xff; + write_cis_mem(s, 1, (base + CISREG_IOBASE_0)>>1, 1, &b); + b = (c->io.BasePort1 >> 8) & 0xff; + write_cis_mem(s, 1, (base + CISREG_IOBASE_1)>>1, 1, &b); + } + if (req->Present & PRESENT_IOSIZE) { + u_char b = c->io.NumPorts1 + c->io.NumPorts2 - 1; + write_cis_mem(s, 1, (base + CISREG_IOSIZE)>>1, 1, &b); + } + + /* Configure I/O windows */ + if (c->state & CONFIG_IO_REQ) { + iomap.speed = io_speed; + for (i = 0; i < MAX_IO_WIN; i++) + if (s->io[i].NumPorts != 0) { + iomap.map = i; + iomap.flags = MAP_ACTIVE; + switch (s->io[i].Attributes & IO_DATA_PATH_WIDTH) { + case IO_DATA_PATH_WIDTH_16: + iomap.flags |= MAP_16BIT; break; + case IO_DATA_PATH_WIDTH_AUTO: + iomap.flags |= MAP_AUTOSZ; break; + default: + break; + } + iomap.start = s->io[i].BasePort; + iomap.stop = iomap.start + s->io[i].NumPorts - 1; + s->ops->set_io_map(s, &iomap); + s->io[i].Config++; + } + } + + c->state |= CONFIG_LOCKED; + handle->state |= CLIENT_CONFIG_LOCKED; + return CS_SUCCESS; +} /* request_configuration */ + +/*====================================================================== + + Request_io() reserves ranges of port addresses for a socket. + I have not implemented range sharing or alias addressing. + +======================================================================*/ + +int pcmcia_request_io(client_handle_t handle, io_req_t *req) +{ + struct pcmcia_socket *s; + config_t *c; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + + if (handle->state & CLIENT_CARDBUS) { +#ifdef CONFIG_CARDBUS + handle->state |= CLIENT_IO_REQ; + return CS_SUCCESS; +#else + return CS_UNSUPPORTED_FUNCTION; +#endif + } + + if (!req) + return CS_UNSUPPORTED_MODE; + c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + if (c->state & CONFIG_IO_REQ) + return CS_IN_USE; + if (req->Attributes1 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS)) + return CS_BAD_ATTRIBUTE; + if ((req->NumPorts2 > 0) && + (req->Attributes2 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS))) + return CS_BAD_ATTRIBUTE; + + if (alloc_io_space(s, req->Attributes1, &req->BasePort1, + req->NumPorts1, req->IOAddrLines)) + return CS_IN_USE; + + if (req->NumPorts2) { + if (alloc_io_space(s, req->Attributes2, &req->BasePort2, + req->NumPorts2, req->IOAddrLines)) { + release_io_space(s, req->BasePort1, req->NumPorts1); + return CS_IN_USE; + } + } + + c->io = *req; + c->state |= CONFIG_IO_REQ; + handle->state |= CLIENT_IO_REQ; + return CS_SUCCESS; +} /* request_io */ + +/*====================================================================== + + Request_irq() reserves an irq for this client. + + Also, since Linux only reserves irq's when they are actually + hooked, we don't guarantee that an irq will still be available + when the configuration is locked. Now that I think about it, + there might be a way to fix this using a dummy handler. + +======================================================================*/ + +#ifdef CONFIG_PCMCIA_PROBE +static irqreturn_t test_action(int cpl, void *dev_id, struct pt_regs *regs) +{ + return IRQ_NONE; +} +#endif + +int pcmcia_request_irq(client_handle_t handle, irq_req_t *req) +{ + struct pcmcia_socket *s; + config_t *c; + int ret = CS_IN_USE, irq = 0; + struct pcmcia_device *p_dev = handle_to_pdev(handle); + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + if (c->state & CONFIG_IRQ_REQ) + return CS_IN_USE; + +#ifdef CONFIG_PCMCIA_PROBE + if (s->irq.AssignedIRQ != 0) { + /* If the interrupt is already assigned, it must be the same */ + irq = s->irq.AssignedIRQ; + } else { + int try; + u32 mask = s->irq_mask; + void *data = NULL; + + for (try = 0; try < 64; try++) { + irq = try % 32; + + /* marked as available by driver, and not blocked by userspace? */ + if (!((mask >> irq) & 1)) + continue; + + /* avoid an IRQ which is already used by a PCMCIA card */ + if ((try < 32) && pcmcia_used_irq[irq]) + continue; + + /* register the correct driver, if possible, of check whether + * registering a dummy handle works, i.e. if the IRQ isn't + * marked as used by the kernel resource management core */ + ret = request_irq(irq, + (req->Attributes & IRQ_HANDLE_PRESENT) ? req->Handler : test_action, + ((req->Attributes & IRQ_TYPE_DYNAMIC_SHARING) || + (s->functions > 1) || + (irq == s->pci_irq)) ? SA_SHIRQ : 0, + p_dev->dev.bus_id, + (req->Attributes & IRQ_HANDLE_PRESENT) ? req->Instance : data); + if (!ret) { + if (!(req->Attributes & IRQ_HANDLE_PRESENT)) + free_irq(irq, data); + break; + } + } + } +#endif + if (ret) { + if (!s->pci_irq) + return ret; + irq = s->pci_irq; + } + + if (ret && req->Attributes & IRQ_HANDLE_PRESENT) { + if (request_irq(irq, req->Handler, + ((req->Attributes & IRQ_TYPE_DYNAMIC_SHARING) || + (s->functions > 1) || + (irq == s->pci_irq)) ? SA_SHIRQ : 0, + p_dev->dev.bus_id, req->Instance)) + return CS_IN_USE; + } + + c->irq.Attributes = req->Attributes; + s->irq.AssignedIRQ = req->AssignedIRQ = irq; + s->irq.Config++; + + c->state |= CONFIG_IRQ_REQ; + handle->state |= CLIENT_IRQ_REQ; + +#ifdef CONFIG_PCMCIA_PROBE + pcmcia_used_irq[irq]++; +#endif + + return CS_SUCCESS; +} /* pcmcia_request_irq */ + +/*====================================================================== + + Request_window() establishes a mapping between card memory space + and system memory space. + +======================================================================*/ + +int pcmcia_request_window(client_handle_t *handle, win_req_t *req, window_handle_t *wh) +{ + struct pcmcia_socket *s; + window_t *win; + u_long align; + int w; + + if (CHECK_HANDLE(*handle)) + return CS_BAD_HANDLE; + s = (*handle)->Socket; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (req->Attributes & (WIN_PAGED | WIN_SHARED)) + return CS_BAD_ATTRIBUTE; + + /* Window size defaults to smallest available */ + if (req->Size == 0) + req->Size = s->map_size; + align = (((s->features & SS_CAP_MEM_ALIGN) || + (req->Attributes & WIN_STRICT_ALIGN)) ? + req->Size : s->map_size); + if (req->Size & (s->map_size-1)) + return CS_BAD_SIZE; + if ((req->Base && (s->features & SS_CAP_STATIC_MAP)) || + (req->Base & (align-1))) + return CS_BAD_BASE; + if (req->Base) + align = 0; + + /* Allocate system memory window */ + for (w = 0; w < MAX_WIN; w++) + if (!(s->state & SOCKET_WIN_REQ(w))) break; + if (w == MAX_WIN) + return CS_OUT_OF_RESOURCE; + + win = &s->win[w]; + win->magic = WINDOW_MAGIC; + win->index = w; + win->handle = *handle; + win->sock = s; + + if (!(s->features & SS_CAP_STATIC_MAP)) { + win->ctl.res = find_mem_region(req->Base, req->Size, align, + (req->Attributes & WIN_MAP_BELOW_1MB), s); + if (!win->ctl.res) + return CS_IN_USE; + } + (*handle)->state |= CLIENT_WIN_REQ(w); + + /* Configure the socket controller */ + win->ctl.map = w+1; + win->ctl.flags = 0; + win->ctl.speed = req->AccessSpeed; + if (req->Attributes & WIN_MEMORY_TYPE) + win->ctl.flags |= MAP_ATTRIB; + if (req->Attributes & WIN_ENABLE) + win->ctl.flags |= MAP_ACTIVE; + if (req->Attributes & WIN_DATA_WIDTH_16) + win->ctl.flags |= MAP_16BIT; + if (req->Attributes & WIN_USE_WAIT) + win->ctl.flags |= MAP_USE_WAIT; + win->ctl.card_start = 0; + if (s->ops->set_mem_map(s, &win->ctl) != 0) + return CS_BAD_ARGS; + s->state |= SOCKET_WIN_REQ(w); + + /* Return window handle */ + if (s->features & SS_CAP_STATIC_MAP) { + req->Base = win->ctl.static_start; + } else { + req->Base = win->ctl.res->start; + } + *wh = win; + + return CS_SUCCESS; +} /* request_window */ + +/*====================================================================== + + I'm not sure which "reset" function this is supposed to use, + but for now, it uses the low-level interface's reset, not the + CIS register. + +======================================================================*/ + +int pccard_reset_card(struct pcmcia_socket *skt) +{ + int ret; + + cs_dbg(skt, 1, "resetting socket\n"); + + down(&skt->skt_sem); + do { + if (!(skt->state & SOCKET_PRESENT)) { + ret = CS_NO_CARD; + break; + } + if (skt->state & SOCKET_SUSPEND) { + ret = CS_IN_USE; + break; + } + if (skt->state & SOCKET_CARDBUS) { + ret = CS_UNSUPPORTED_FUNCTION; + break; + } + + ret = send_event(skt, CS_EVENT_RESET_REQUEST, CS_EVENT_PRI_LOW); + if (ret == 0) { + send_event(skt, CS_EVENT_RESET_PHYSICAL, CS_EVENT_PRI_LOW); + if (socket_reset(skt) == CS_SUCCESS) + send_event(skt, CS_EVENT_CARD_RESET, CS_EVENT_PRI_LOW); + } + + ret = CS_SUCCESS; + } while (0); + up(&skt->skt_sem); + + return ret; +} /* reset_card */ +EXPORT_SYMBOL(pccard_reset_card); + +/*====================================================================== + + These shut down or wake up a socket. They are sort of user + initiated versions of the APM suspend and resume actions. + +======================================================================*/ + +int pcmcia_suspend_card(struct pcmcia_socket *skt) +{ + int ret; + + cs_dbg(skt, 1, "suspending socket\n"); + + down(&skt->skt_sem); + do { + if (!(skt->state & SOCKET_PRESENT)) { + ret = CS_NO_CARD; + break; + } + if (skt->state & SOCKET_CARDBUS) { + ret = CS_UNSUPPORTED_FUNCTION; + break; + } + ret = socket_suspend(skt); + } while (0); + up(&skt->skt_sem); + + return ret; +} /* suspend_card */ + +int pcmcia_resume_card(struct pcmcia_socket *skt) +{ + int ret; + + cs_dbg(skt, 1, "waking up socket\n"); + + down(&skt->skt_sem); + do { + if (!(skt->state & SOCKET_PRESENT)) { + ret = CS_NO_CARD; + break; + } + if (skt->state & SOCKET_CARDBUS) { + ret = CS_UNSUPPORTED_FUNCTION; + break; + } + ret = socket_resume(skt); + } while (0); + up(&skt->skt_sem); + + return ret; +} /* resume_card */ + +/*====================================================================== + + These handle user requests to eject or insert a card. + +======================================================================*/ + +int pcmcia_eject_card(struct pcmcia_socket *skt) +{ + int ret; + + cs_dbg(skt, 1, "user eject request\n"); + + down(&skt->skt_sem); + do { + if (!(skt->state & SOCKET_PRESENT)) { + ret = -ENODEV; + break; + } + + ret = send_event(skt, CS_EVENT_EJECTION_REQUEST, CS_EVENT_PRI_LOW); + if (ret != 0) { + ret = -EINVAL; + break; + } + + socket_remove(skt); + ret = 0; + } while (0); + up(&skt->skt_sem); + + return ret; +} /* eject_card */ + +int pcmcia_insert_card(struct pcmcia_socket *skt) +{ + int ret; + + cs_dbg(skt, 1, "user insert request\n"); + + down(&skt->skt_sem); + do { + if (skt->state & SOCKET_PRESENT) { + ret = -EBUSY; + break; + } + if (socket_insert(skt) == CS_NO_CARD) { + ret = -ENODEV; + break; + } + ret = 0; + } while (0); + up(&skt->skt_sem); + + return ret; +} /* insert_card */ + +/*====================================================================== + + OS-specific module glue goes here + +======================================================================*/ +/* in alpha order */ +EXPORT_SYMBOL(pcmcia_eject_card); +EXPORT_SYMBOL(pcmcia_get_card_services_info); +EXPORT_SYMBOL(pcmcia_get_mem_page); +EXPORT_SYMBOL(pcmcia_insert_card); +EXPORT_SYMBOL(pcmcia_map_mem_page); +EXPORT_SYMBOL(pcmcia_modify_configuration); +EXPORT_SYMBOL(pcmcia_release_configuration); +EXPORT_SYMBOL(pcmcia_release_io); +EXPORT_SYMBOL(pcmcia_release_irq); +EXPORT_SYMBOL(pcmcia_release_window); +EXPORT_SYMBOL(pcmcia_replace_cis); +EXPORT_SYMBOL(pcmcia_request_configuration); +EXPORT_SYMBOL(pcmcia_request_io); +EXPORT_SYMBOL(pcmcia_request_irq); +EXPORT_SYMBOL(pcmcia_request_window); +EXPORT_SYMBOL(pcmcia_resume_card); +EXPORT_SYMBOL(pcmcia_suspend_card); + +EXPORT_SYMBOL(dead_socket); +EXPORT_SYMBOL(pcmcia_parse_events); + +struct class pcmcia_socket_class = { + .name = "pcmcia_socket", + .release = pcmcia_release_socket, +}; +EXPORT_SYMBOL(pcmcia_socket_class); + + +static int __init init_pcmcia_cs(void) +{ + int ret; + printk(KERN_INFO "%s\n", release); + printk(KERN_INFO " %s\n", options); + + ret = class_register(&pcmcia_socket_class); + if (ret) + return (ret); + return class_interface_register(&pccard_sysfs_interface); +} + +static void __exit exit_pcmcia_cs(void) +{ + printk(KERN_INFO "unloading Kernel Card Services\n"); + class_interface_unregister(&pccard_sysfs_interface); + class_unregister(&pcmcia_socket_class); +} + +subsys_initcall(init_pcmcia_cs); +module_exit(exit_pcmcia_cs); + +/*====================================================================*/ + diff --git a/drivers/pcmcia/cs_internal.h b/drivers/pcmcia/cs_internal.h new file mode 100644 index 000000000000..7933a7db49d3 --- /dev/null +++ b/drivers/pcmcia/cs_internal.h @@ -0,0 +1,185 @@ +/* + * cs_internal.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + */ + +#ifndef _LINUX_CS_INTERNAL_H +#define _LINUX_CS_INTERNAL_H + +#include <linux/config.h> + +#define CLIENT_MAGIC 0x51E6 +typedef struct client_t client_t; + +/* Flags in client state */ +#define CLIENT_CONFIG_LOCKED 0x0001 +#define CLIENT_IRQ_REQ 0x0002 +#define CLIENT_IO_REQ 0x0004 +#define CLIENT_UNBOUND 0x0008 +#define CLIENT_STALE 0x0010 +#define CLIENT_WIN_REQ(i) (0x20<<(i)) +#define CLIENT_CARDBUS 0x8000 + +#define REGION_MAGIC 0xE3C9 +typedef struct region_t { + u_short region_magic; + u_short state; + dev_info_t dev_info; + client_handle_t mtd; + u_int MediaID; + region_info_t info; +} region_t; + +#define REGION_STALE 0x01 + +/* Each card function gets one of these guys */ +typedef struct config_t { + u_int state; + u_int Attributes; + u_int Vcc, Vpp1, Vpp2; + u_int IntType; + u_int ConfigBase; + u_char Status, Pin, Copy, Option, ExtStatus; + u_int Present; + u_int CardValues; + io_req_t io; + struct { + u_int Attributes; + } irq; +} config_t; + +struct cis_cache_entry { + struct list_head node; + unsigned int addr; + unsigned int len; + unsigned int attr; + unsigned char cache[0]; +}; + +/* Flags in config state */ +#define CONFIG_LOCKED 0x01 +#define CONFIG_IRQ_REQ 0x02 +#define CONFIG_IO_REQ 0x04 + +/* Flags in socket state */ +#define SOCKET_PRESENT 0x0008 +#define SOCKET_INUSE 0x0010 +#define SOCKET_SUSPEND 0x0080 +#define SOCKET_WIN_REQ(i) (0x0100<<(i)) +#define SOCKET_REGION_INFO 0x4000 +#define SOCKET_CARDBUS 0x8000 +#define SOCKET_CARDBUS_CONFIG 0x10000 + +static inline int cs_socket_get(struct pcmcia_socket *skt) +{ + int ret; + + WARN_ON(skt->state & SOCKET_INUSE); + + ret = try_module_get(skt->owner); + if (ret) + skt->state |= SOCKET_INUSE; + return ret; +} + +static inline void cs_socket_put(struct pcmcia_socket *skt) +{ + if (skt->state & SOCKET_INUSE) { + skt->state &= ~SOCKET_INUSE; + module_put(skt->owner); + } +} + +#define CHECK_HANDLE(h) \ + (((h) == NULL) || ((h)->client_magic != CLIENT_MAGIC)) + +#define CHECK_SOCKET(s) \ + (((s) >= sockets) || (socket_table[s]->ops == NULL)) + +#define SOCKET(h) (h->Socket) +#define CONFIG(h) (&SOCKET(h)->config[(h)->Function]) + +#define CHECK_REGION(r) \ + (((r) == NULL) || ((r)->region_magic != REGION_MAGIC)) + +#define CHECK_ERASEQ(q) \ + (((q) == NULL) || ((q)->eraseq_magic != ERASEQ_MAGIC)) + +#define EVENT(h, e, p) \ + ((h)->event_handler((e), (p), &(h)->event_callback_args)) + +/* In cardbus.c */ +int cb_alloc(struct pcmcia_socket *s); +void cb_free(struct pcmcia_socket *s); +int read_cb_mem(struct pcmcia_socket *s, int space, u_int addr, u_int len, void *ptr); + +/* In cistpl.c */ +int read_cis_mem(struct pcmcia_socket *s, int attr, + u_int addr, u_int len, void *ptr); +void write_cis_mem(struct pcmcia_socket *s, int attr, + u_int addr, u_int len, void *ptr); +void release_cis_mem(struct pcmcia_socket *s); +void destroy_cis_cache(struct pcmcia_socket *s); +int verify_cis_cache(struct pcmcia_socket *s); +int pccard_read_tuple(struct pcmcia_socket *s, unsigned int function, cisdata_t code, void *parse); + +/* In rsrc_mgr */ +void pcmcia_validate_mem(struct pcmcia_socket *s); +struct resource *find_io_region(unsigned long base, int num, unsigned long align, + struct pcmcia_socket *s); +int adjust_io_region(struct resource *res, unsigned long r_start, + unsigned long r_end, struct pcmcia_socket *s); +struct resource *find_mem_region(u_long base, u_long num, u_long align, + int low, struct pcmcia_socket *s); +int adjust_resource_info(client_handle_t handle, adjust_t *adj); +void release_resource_db(struct pcmcia_socket *s); + +/* In socket_sysfs.c */ +extern struct class_interface pccard_sysfs_interface; + +/* In cs.c */ +extern struct rw_semaphore pcmcia_socket_list_rwsem; +extern struct list_head pcmcia_socket_list; +int pcmcia_get_window(struct pcmcia_socket *s, window_handle_t *handle, int idx, win_req_t *req); +int pccard_get_configuration_info(struct pcmcia_socket *s, unsigned int function, config_info_t *config); +int pccard_reset_card(struct pcmcia_socket *skt); +int pccard_get_status(struct pcmcia_socket *s, unsigned int function, cs_status_t *status); +int pccard_access_configuration_register(struct pcmcia_socket *s, unsigned int function, conf_reg_t *reg); + + +struct pcmcia_callback{ + struct module *owner; + int (*event) (struct pcmcia_socket *s, event_t event, int priority); + int (*resources_done) (struct pcmcia_socket *s); +}; + +int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c); + +#define cs_socket_name(skt) ((skt)->dev.class_id) + +#ifdef DEBUG +extern int cs_debug_level(int); + +#define cs_dbg(skt, lvl, fmt, arg...) do { \ + if (cs_debug_level(lvl)) \ + printk(KERN_DEBUG "cs: %s: " fmt, \ + cs_socket_name(skt) , ## arg); \ +} while (0) + +#else +#define cs_dbg(skt, lvl, fmt, arg...) do { } while (0) +#endif + +#define cs_err(skt, fmt, arg...) \ + printk(KERN_ERR "cs: %s: " fmt, (skt)->dev.class_id , ## arg) + +#endif /* _LINUX_CS_INTERNAL_H */ diff --git a/drivers/pcmcia/ds.c b/drivers/pcmcia/ds.c new file mode 100644 index 000000000000..66150d08b5c7 --- /dev/null +++ b/drivers/pcmcia/ds.c @@ -0,0 +1,1659 @@ +/* + * ds.c -- 16-bit PCMCIA core support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + * (C) 2003 - 2004 Dominik Brodowski + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/major.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/fcntl.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/timer.h> +#include <linux/ioctl.h> +#include <linux/proc_fs.h> +#include <linux/poll.h> +#include <linux/pci.h> +#include <linux/list.h> +#include <linux/delay.h> +#include <linux/kref.h> +#include <linux/workqueue.h> + +#include <asm/atomic.h> + +#define IN_CARD_SERVICES +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> +#include <pcmcia/ss.h> + +#include "cs_internal.h" + +/*====================================================================*/ + +/* Module parameters */ + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("PCMCIA Driver Services"); +MODULE_LICENSE("GPL"); + +#ifdef DEBUG +int ds_pc_debug; + +module_param_named(pc_debug, ds_pc_debug, int, 0644); + +#define ds_dbg(lvl, fmt, arg...) do { \ + if (ds_pc_debug > (lvl)) \ + printk(KERN_DEBUG "ds: " fmt , ## arg); \ +} while (0) +#else +#define ds_dbg(lvl, fmt, arg...) do { } while (0) +#endif + +/*====================================================================*/ + +/* Device user information */ +#define MAX_EVENTS 32 +#define USER_MAGIC 0x7ea4 +#define CHECK_USER(u) \ + (((u) == NULL) || ((u)->user_magic != USER_MAGIC)) +typedef struct user_info_t { + u_int user_magic; + int event_head, event_tail; + event_t event[MAX_EVENTS]; + struct user_info_t *next; + struct pcmcia_bus_socket *socket; +} user_info_t; + +/* Socket state information */ +struct pcmcia_bus_socket { + struct kref refcount; + struct pcmcia_callback callback; + int state; + user_info_t *user; + wait_queue_head_t queue; + struct pcmcia_socket *parent; + + /* the PCMCIA devices connected to this socket (normally one, more + * for multifunction devices: */ + struct list_head devices_list; + u8 device_count; /* the number of devices, used + * only internally and subject + * to incorrectness and change */ +}; +static spinlock_t pcmcia_dev_list_lock; + +#define DS_SOCKET_PRESENT 0x01 +#define DS_SOCKET_BUSY 0x02 +#define DS_SOCKET_REMOVAL_PENDING 0x10 +#define DS_SOCKET_DEAD 0x80 + +/*====================================================================*/ + +static int major_dev = -1; + +static int unbind_request(struct pcmcia_bus_socket *s); + +/*====================================================================*/ + +/* code which was in cs.c before */ + +/* String tables for error messages */ + +typedef struct lookup_t { + int key; + char *msg; +} lookup_t; + +static const lookup_t error_table[] = { + { CS_SUCCESS, "Operation succeeded" }, + { CS_BAD_ADAPTER, "Bad adapter" }, + { CS_BAD_ATTRIBUTE, "Bad attribute", }, + { CS_BAD_BASE, "Bad base address" }, + { CS_BAD_EDC, "Bad EDC" }, + { CS_BAD_IRQ, "Bad IRQ" }, + { CS_BAD_OFFSET, "Bad offset" }, + { CS_BAD_PAGE, "Bad page number" }, + { CS_READ_FAILURE, "Read failure" }, + { CS_BAD_SIZE, "Bad size" }, + { CS_BAD_SOCKET, "Bad socket" }, + { CS_BAD_TYPE, "Bad type" }, + { CS_BAD_VCC, "Bad Vcc" }, + { CS_BAD_VPP, "Bad Vpp" }, + { CS_BAD_WINDOW, "Bad window" }, + { CS_WRITE_FAILURE, "Write failure" }, + { CS_NO_CARD, "No card present" }, + { CS_UNSUPPORTED_FUNCTION, "Usupported function" }, + { CS_UNSUPPORTED_MODE, "Unsupported mode" }, + { CS_BAD_SPEED, "Bad speed" }, + { CS_BUSY, "Resource busy" }, + { CS_GENERAL_FAILURE, "General failure" }, + { CS_WRITE_PROTECTED, "Write protected" }, + { CS_BAD_ARG_LENGTH, "Bad argument length" }, + { CS_BAD_ARGS, "Bad arguments" }, + { CS_CONFIGURATION_LOCKED, "Configuration locked" }, + { CS_IN_USE, "Resource in use" }, + { CS_NO_MORE_ITEMS, "No more items" }, + { CS_OUT_OF_RESOURCE, "Out of resource" }, + { CS_BAD_HANDLE, "Bad handle" }, + { CS_BAD_TUPLE, "Bad CIS tuple" } +}; + + +static const lookup_t service_table[] = { + { AccessConfigurationRegister, "AccessConfigurationRegister" }, + { AddSocketServices, "AddSocketServices" }, + { AdjustResourceInfo, "AdjustResourceInfo" }, + { CheckEraseQueue, "CheckEraseQueue" }, + { CloseMemory, "CloseMemory" }, + { DeregisterClient, "DeregisterClient" }, + { DeregisterEraseQueue, "DeregisterEraseQueue" }, + { GetCardServicesInfo, "GetCardServicesInfo" }, + { GetClientInfo, "GetClientInfo" }, + { GetConfigurationInfo, "GetConfigurationInfo" }, + { GetEventMask, "GetEventMask" }, + { GetFirstClient, "GetFirstClient" }, + { GetFirstRegion, "GetFirstRegion" }, + { GetFirstTuple, "GetFirstTuple" }, + { GetNextClient, "GetNextClient" }, + { GetNextRegion, "GetNextRegion" }, + { GetNextTuple, "GetNextTuple" }, + { GetStatus, "GetStatus" }, + { GetTupleData, "GetTupleData" }, + { MapMemPage, "MapMemPage" }, + { ModifyConfiguration, "ModifyConfiguration" }, + { ModifyWindow, "ModifyWindow" }, + { OpenMemory, "OpenMemory" }, + { ParseTuple, "ParseTuple" }, + { ReadMemory, "ReadMemory" }, + { RegisterClient, "RegisterClient" }, + { RegisterEraseQueue, "RegisterEraseQueue" }, + { RegisterMTD, "RegisterMTD" }, + { ReleaseConfiguration, "ReleaseConfiguration" }, + { ReleaseIO, "ReleaseIO" }, + { ReleaseIRQ, "ReleaseIRQ" }, + { ReleaseWindow, "ReleaseWindow" }, + { RequestConfiguration, "RequestConfiguration" }, + { RequestIO, "RequestIO" }, + { RequestIRQ, "RequestIRQ" }, + { RequestSocketMask, "RequestSocketMask" }, + { RequestWindow, "RequestWindow" }, + { ResetCard, "ResetCard" }, + { SetEventMask, "SetEventMask" }, + { ValidateCIS, "ValidateCIS" }, + { WriteMemory, "WriteMemory" }, + { BindDevice, "BindDevice" }, + { BindMTD, "BindMTD" }, + { ReportError, "ReportError" }, + { SuspendCard, "SuspendCard" }, + { ResumeCard, "ResumeCard" }, + { EjectCard, "EjectCard" }, + { InsertCard, "InsertCard" }, + { ReplaceCIS, "ReplaceCIS" } +}; + + +int pcmcia_report_error(client_handle_t handle, error_info_t *err) +{ + int i; + char *serv; + + if (CHECK_HANDLE(handle)) + printk(KERN_NOTICE); + else { + struct pcmcia_device *p_dev = handle_to_pdev(handle); + printk(KERN_NOTICE "%s: ", p_dev->dev.bus_id); + } + + for (i = 0; i < ARRAY_SIZE(service_table); i++) + if (service_table[i].key == err->func) + break; + if (i < ARRAY_SIZE(service_table)) + serv = service_table[i].msg; + else + serv = "Unknown service number"; + + for (i = 0; i < ARRAY_SIZE(error_table); i++) + if (error_table[i].key == err->retcode) + break; + if (i < ARRAY_SIZE(error_table)) + printk("%s: %s\n", serv, error_table[i].msg); + else + printk("%s: Unknown error code %#x\n", serv, err->retcode); + + return CS_SUCCESS; +} /* report_error */ +EXPORT_SYMBOL(pcmcia_report_error); + +/* end of code which was in cs.c before */ + +/*======================================================================*/ + +void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + pcmcia_report_error(handle, &err); +} +EXPORT_SYMBOL(cs_error); + +/*======================================================================*/ + +static struct pcmcia_driver * get_pcmcia_driver (dev_info_t *dev_info); +static struct pcmcia_bus_socket * get_socket_info_by_nr(unsigned int nr); + +static void pcmcia_release_bus_socket(struct kref *refcount) +{ + struct pcmcia_bus_socket *s = container_of(refcount, struct pcmcia_bus_socket, refcount); + pcmcia_put_socket(s->parent); + kfree(s); +} + +static void pcmcia_put_bus_socket(struct pcmcia_bus_socket *s) +{ + kref_put(&s->refcount, pcmcia_release_bus_socket); +} + +static struct pcmcia_bus_socket *pcmcia_get_bus_socket(struct pcmcia_bus_socket *s) +{ + kref_get(&s->refcount); + return (s); +} + +/** + * pcmcia_register_driver - register a PCMCIA driver with the bus core + * + * Registers a PCMCIA driver with the PCMCIA bus core. + */ +static int pcmcia_device_probe(struct device *dev); +static int pcmcia_device_remove(struct device * dev); + +int pcmcia_register_driver(struct pcmcia_driver *driver) +{ + if (!driver) + return -EINVAL; + + /* initialize common fields */ + driver->drv.bus = &pcmcia_bus_type; + driver->drv.owner = driver->owner; + driver->drv.probe = pcmcia_device_probe; + driver->drv.remove = pcmcia_device_remove; + + return driver_register(&driver->drv); +} +EXPORT_SYMBOL(pcmcia_register_driver); + +/** + * pcmcia_unregister_driver - unregister a PCMCIA driver with the bus core + */ +void pcmcia_unregister_driver(struct pcmcia_driver *driver) +{ + driver_unregister(&driver->drv); +} +EXPORT_SYMBOL(pcmcia_unregister_driver); + +#ifdef CONFIG_PROC_FS +static struct proc_dir_entry *proc_pccard = NULL; + +static int proc_read_drivers_callback(struct device_driver *driver, void *d) +{ + char **p = d; + struct pcmcia_driver *p_drv = container_of(driver, + struct pcmcia_driver, drv); + + *p += sprintf(*p, "%-24.24s 1 %d\n", p_drv->drv.name, +#ifdef CONFIG_MODULE_UNLOAD + (p_drv->owner) ? module_refcount(p_drv->owner) : 1 +#else + 1 +#endif + ); + d = (void *) p; + + return 0; +} + +static int proc_read_drivers(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + char *p = buf; + + bus_for_each_drv(&pcmcia_bus_type, NULL, + (void *) &p, proc_read_drivers_callback); + + return (p - buf); +} +#endif + +/* pcmcia_device handling */ + +static struct pcmcia_device * pcmcia_get_dev(struct pcmcia_device *p_dev) +{ + struct device *tmp_dev; + tmp_dev = get_device(&p_dev->dev); + if (!tmp_dev) + return NULL; + return to_pcmcia_dev(tmp_dev); +} + +static void pcmcia_put_dev(struct pcmcia_device *p_dev) +{ + if (p_dev) + put_device(&p_dev->dev); +} + +static void pcmcia_release_dev(struct device *dev) +{ + struct pcmcia_device *p_dev = to_pcmcia_dev(dev); + ds_dbg(1, "releasing dev %p\n", p_dev); + pcmcia_put_bus_socket(p_dev->socket->pcmcia); + kfree(p_dev); +} + + +static int pcmcia_device_probe(struct device * dev) +{ + struct pcmcia_device *p_dev; + struct pcmcia_driver *p_drv; + int ret = 0; + + dev = get_device(dev); + if (!dev) + return -ENODEV; + + p_dev = to_pcmcia_dev(dev); + p_drv = to_pcmcia_drv(dev->driver); + + if (!try_module_get(p_drv->owner)) { + ret = -EINVAL; + goto put_dev; + } + + if (p_drv->attach) { + p_dev->instance = p_drv->attach(); + if ((!p_dev->instance) || (p_dev->client.state & CLIENT_UNBOUND)) { + printk(KERN_NOTICE "ds: unable to create instance " + "of '%s'!\n", p_drv->drv.name); + ret = -EINVAL; + } + } + + if (ret) + module_put(p_drv->owner); + put_dev: + if ((ret) || !(p_drv->attach)) + put_device(dev); + return (ret); +} + + +static int pcmcia_device_remove(struct device * dev) +{ + struct pcmcia_device *p_dev; + struct pcmcia_driver *p_drv; + + /* detach the "instance" */ + p_dev = to_pcmcia_dev(dev); + p_drv = to_pcmcia_drv(dev->driver); + + if (p_drv) { + if ((p_drv->detach) && (p_dev->instance)) { + p_drv->detach(p_dev->instance); + /* from pcmcia_probe_device */ + put_device(&p_dev->dev); + } + module_put(p_drv->owner); + } + + return 0; +} + + + +/* + * pcmcia_device_query -- determine information about a pcmcia device + */ +static int pcmcia_device_query(struct pcmcia_device *p_dev) +{ + cistpl_manfid_t manf_id; + cistpl_funcid_t func_id; + cistpl_vers_1_t vers1; + unsigned int i; + + if (!pccard_read_tuple(p_dev->socket, p_dev->func, + CISTPL_MANFID, &manf_id)) { + p_dev->manf_id = manf_id.manf; + p_dev->card_id = manf_id.card; + p_dev->has_manf_id = 1; + p_dev->has_card_id = 1; + } + + if (!pccard_read_tuple(p_dev->socket, p_dev->func, + CISTPL_FUNCID, &func_id)) { + p_dev->func_id = func_id.func; + p_dev->has_func_id = 1; + } else { + /* rule of thumb: cards with no FUNCID, but with + * common memory device geometry information, are + * probably memory cards (from pcmcia-cs) */ + cistpl_device_geo_t devgeo; + if (!pccard_read_tuple(p_dev->socket, p_dev->func, + CISTPL_DEVICE_GEO, &devgeo)) { + ds_dbg(0, "mem device geometry probably means " + "FUNCID_MEMORY\n"); + p_dev->func_id = CISTPL_FUNCID_MEMORY; + p_dev->has_func_id = 1; + } + } + + if (!pccard_read_tuple(p_dev->socket, p_dev->func, CISTPL_VERS_1, + &vers1)) { + for (i=0; i < vers1.ns; i++) { + char *tmp; + unsigned int length; + + tmp = vers1.str + vers1.ofs[i]; + + length = strlen(tmp) + 1; + if ((length < 3) || (length > 255)) + continue; + + p_dev->prod_id[i] = kmalloc(sizeof(char) * length, + GFP_KERNEL); + if (!p_dev->prod_id[i]) + continue; + + p_dev->prod_id[i] = strncpy(p_dev->prod_id[i], + tmp, length); + } + } + + return 0; +} + + +/* device_add_lock is needed to avoid double registration by cardmgr and kernel. + * Serializes pcmcia_device_add; will most likely be removed in future. + * + * While it has the caveat that adding new PCMCIA devices inside(!) device_register() + * won't work, this doesn't matter much at the moment: the driver core doesn't + * support it either. + */ +static DECLARE_MUTEX(device_add_lock); + +static struct pcmcia_device * pcmcia_device_add(struct pcmcia_bus_socket *s, unsigned int function) +{ + struct pcmcia_device *p_dev; + unsigned long flags; + + s = pcmcia_get_bus_socket(s); + if (!s) + return NULL; + + down(&device_add_lock); + + p_dev = kmalloc(sizeof(struct pcmcia_device), GFP_KERNEL); + if (!p_dev) + goto err_put; + memset(p_dev, 0, sizeof(struct pcmcia_device)); + + p_dev->socket = s->parent; + p_dev->device_no = (s->device_count++); + p_dev->func = function; + + p_dev->dev.bus = &pcmcia_bus_type; + p_dev->dev.parent = s->parent->dev.dev; + p_dev->dev.release = pcmcia_release_dev; + sprintf (p_dev->dev.bus_id, "%d.%d", p_dev->socket->sock, p_dev->device_no); + + /* compat */ + p_dev->client.client_magic = CLIENT_MAGIC; + p_dev->client.Socket = s->parent; + p_dev->client.Function = function; + p_dev->client.state = CLIENT_UNBOUND; + + /* Add to the list in pcmcia_bus_socket */ + spin_lock_irqsave(&pcmcia_dev_list_lock, flags); + list_add_tail(&p_dev->socket_device_list, &s->devices_list); + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + + if (device_register(&p_dev->dev)) { + spin_lock_irqsave(&pcmcia_dev_list_lock, flags); + list_del(&p_dev->socket_device_list); + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + + goto err_free; + } + + up(&device_add_lock); + + return p_dev; + + err_free: + kfree(p_dev); + s->device_count--; + err_put: + up(&device_add_lock); + pcmcia_put_bus_socket(s); + + return NULL; +} + + +static int pcmcia_card_add(struct pcmcia_socket *s) +{ + cisinfo_t cisinfo; + cistpl_longlink_mfc_t mfc; + unsigned int no_funcs, i; + int ret = 0; + + if (!(s->resource_setup_done)) + return -EAGAIN; /* try again, but later... */ + + pcmcia_validate_mem(s); + ret = pccard_validate_cis(s, BIND_FN_ALL, &cisinfo); + if (ret || !cisinfo.Chains) { + ds_dbg(0, "invalid CIS or invalid resources\n"); + return -ENODEV; + } + + if (!pccard_read_tuple(s, BIND_FN_ALL, CISTPL_LONGLINK_MFC, &mfc)) + no_funcs = mfc.nfn; + else + no_funcs = 1; + + /* this doesn't handle multifunction devices on one pcmcia function + * yet. */ + for (i=0; i < no_funcs; i++) + pcmcia_device_add(s->pcmcia, i); + + return (ret); +} + + +static int pcmcia_bus_match(struct device * dev, struct device_driver * drv) { + struct pcmcia_device * p_dev = to_pcmcia_dev(dev); + struct pcmcia_driver * p_drv = to_pcmcia_drv(drv); + + /* matching by cardmgr */ + if (p_dev->cardmgr == p_drv) + return 1; + + return 0; +} + +/************************ per-device sysfs output ***************************/ + +#define pcmcia_device_attr(field, test, format) \ +static ssize_t field##_show (struct device *dev, char *buf) \ +{ \ + struct pcmcia_device *p_dev = to_pcmcia_dev(dev); \ + return p_dev->test ? sprintf (buf, format, p_dev->field) : -ENODEV; \ +} + +#define pcmcia_device_stringattr(name, field) \ +static ssize_t name##_show (struct device *dev, char *buf) \ +{ \ + struct pcmcia_device *p_dev = to_pcmcia_dev(dev); \ + return p_dev->field ? sprintf (buf, "%s\n", p_dev->field) : -ENODEV; \ +} + +pcmcia_device_attr(func, socket, "0x%02x\n"); +pcmcia_device_attr(func_id, has_func_id, "0x%02x\n"); +pcmcia_device_attr(manf_id, has_manf_id, "0x%04x\n"); +pcmcia_device_attr(card_id, has_card_id, "0x%04x\n"); +pcmcia_device_stringattr(prod_id1, prod_id[0]); +pcmcia_device_stringattr(prod_id2, prod_id[1]); +pcmcia_device_stringattr(prod_id3, prod_id[2]); +pcmcia_device_stringattr(prod_id4, prod_id[3]); + +static struct device_attribute pcmcia_dev_attrs[] = { + __ATTR(function, 0444, func_show, NULL), + __ATTR_RO(func_id), + __ATTR_RO(manf_id), + __ATTR_RO(card_id), + __ATTR_RO(prod_id1), + __ATTR_RO(prod_id2), + __ATTR_RO(prod_id3), + __ATTR_RO(prod_id4), + __ATTR_NULL, +}; + + +/*====================================================================== + + These manage a ring buffer of events pending for one user process + +======================================================================*/ + +static int queue_empty(user_info_t *user) +{ + return (user->event_head == user->event_tail); +} + +static event_t get_queued_event(user_info_t *user) +{ + user->event_tail = (user->event_tail+1) % MAX_EVENTS; + return user->event[user->event_tail]; +} + +static void queue_event(user_info_t *user, event_t event) +{ + user->event_head = (user->event_head+1) % MAX_EVENTS; + if (user->event_head == user->event_tail) + user->event_tail = (user->event_tail+1) % MAX_EVENTS; + user->event[user->event_head] = event; +} + +static void handle_event(struct pcmcia_bus_socket *s, event_t event) +{ + user_info_t *user; + for (user = s->user; user; user = user->next) + queue_event(user, event); + wake_up_interruptible(&s->queue); +} + + +/*====================================================================== + + The card status event handler. + +======================================================================*/ + +struct send_event_data { + struct pcmcia_socket *skt; + event_t event; + int priority; +}; + +static int send_event_callback(struct device *dev, void * _data) +{ + struct pcmcia_device *p_dev = to_pcmcia_dev(dev); + struct send_event_data *data = _data; + + /* we get called for all sockets, but may only pass the event + * for drivers _on the affected socket_ */ + if (p_dev->socket != data->skt) + return 0; + + if (p_dev->client.state & (CLIENT_UNBOUND|CLIENT_STALE)) + return 0; + + if (p_dev->client.EventMask & data->event) + return EVENT(&p_dev->client, data->event, data->priority); + + return 0; +} + +static int send_event(struct pcmcia_socket *s, event_t event, int priority) +{ + int ret = 0; + struct send_event_data private; + struct pcmcia_bus_socket *skt = pcmcia_get_bus_socket(s->pcmcia); + + if (!skt) + return 0; + + private.skt = s; + private.event = event; + private.priority = priority; + + ret = bus_for_each_dev(&pcmcia_bus_type, NULL, &private, send_event_callback); + + pcmcia_put_bus_socket(skt); + return ret; +} /* send_event */ + + +/* Normally, the event is passed to individual drivers after + * informing userspace. Only for CS_EVENT_CARD_REMOVAL this + * is inversed to maintain historic compatibility. + */ + +static int ds_event(struct pcmcia_socket *skt, event_t event, int priority) +{ + struct pcmcia_bus_socket *s = skt->pcmcia; + int ret = 0; + + ds_dbg(1, "ds_event(0x%06x, %d, 0x%p)\n", + event, priority, s); + + switch (event) { + + case CS_EVENT_CARD_REMOVAL: + s->state &= ~DS_SOCKET_PRESENT; + send_event(skt, event, priority); + unbind_request(s); + handle_event(s, event); + break; + + case CS_EVENT_CARD_INSERTION: + s->state |= DS_SOCKET_PRESENT; + pcmcia_card_add(skt); + handle_event(s, event); + break; + + case CS_EVENT_EJECTION_REQUEST: + ret = send_event(skt, event, priority); + break; + + default: + handle_event(s, event); + send_event(skt, event, priority); + break; + } + + return 0; +} /* ds_event */ + + +/*====================================================================== + + bind_request() and bind_device() are merged by now. Register_client() + is called right at the end of bind_request(), during the driver's + ->attach() call. Individual descriptions: + + bind_request() connects a socket to a particular client driver. + It looks up the specified device ID in the list of registered + drivers, binds it to the socket, and tries to create an instance + of the device. unbind_request() deletes a driver instance. + + Bind_device() associates a device driver with a particular socket. + It is normally called by Driver Services after it has identified + a newly inserted card. An instance of that driver will then be + eligible to register as a client of this socket. + + Register_client() uses the dev_info_t handle to match the + caller with a socket. The driver must have already been bound + to a socket with bind_device() -- in fact, bind_device() + allocates the client structure that will be used. + +======================================================================*/ + +static int bind_request(struct pcmcia_bus_socket *s, bind_info_t *bind_info) +{ + struct pcmcia_driver *p_drv; + struct pcmcia_device *p_dev; + int ret = 0; + unsigned long flags; + + s = pcmcia_get_bus_socket(s); + if (!s) + return -EINVAL; + + ds_dbg(2, "bind_request(%d, '%s')\n", s->parent->sock, + (char *)bind_info->dev_info); + + p_drv = get_pcmcia_driver(&bind_info->dev_info); + if (!p_drv) { + ret = -EINVAL; + goto err_put; + } + + if (!try_module_get(p_drv->owner)) { + ret = -EINVAL; + goto err_put_driver; + } + + spin_lock_irqsave(&pcmcia_dev_list_lock, flags); + list_for_each_entry(p_dev, &s->devices_list, socket_device_list) { + if (p_dev->func == bind_info->function) { + if ((p_dev->dev.driver == &p_drv->drv)) { + if (p_dev->cardmgr) { + /* if there's already a device + * registered, and it was registered + * by userspace before, we need to + * return the "instance". */ + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + bind_info->instance = p_dev->instance; + ret = -EBUSY; + goto err_put_module; + } else { + /* the correct driver managed to bind + * itself magically to the correct + * device. */ + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + p_dev->cardmgr = p_drv; + ret = 0; + goto err_put_module; + } + } else if (!p_dev->dev.driver) { + /* there's already a device available where + * no device has been bound to yet. So we don't + * need to register a device! */ + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + goto rescan; + } + } + } + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + + p_dev = pcmcia_device_add(s, bind_info->function); + if (!p_dev) { + ret = -EIO; + goto err_put_module; + } + +rescan: + p_dev->cardmgr = p_drv; + + pcmcia_device_query(p_dev); + + /* + * Prevent this racing with a card insertion. + */ + down(&s->parent->skt_sem); + bus_rescan_devices(&pcmcia_bus_type); + up(&s->parent->skt_sem); + + /* check whether the driver indeed matched. I don't care if this + * is racy or not, because it can only happen on cardmgr access + * paths... + */ + if (!(p_dev->dev.driver == &p_drv->drv)) + p_dev->cardmgr = NULL; + + err_put_module: + module_put(p_drv->owner); + err_put_driver: + put_driver(&p_drv->drv); + err_put: + pcmcia_put_bus_socket(s); + + return (ret); +} /* bind_request */ + + +int pcmcia_register_client(client_handle_t *handle, client_reg_t *req) +{ + client_t *client = NULL; + struct pcmcia_socket *s; + struct pcmcia_bus_socket *skt = NULL; + struct pcmcia_device *p_dev = NULL; + + /* Look for unbound client with matching dev_info */ + down_read(&pcmcia_socket_list_rwsem); + list_for_each_entry(s, &pcmcia_socket_list, socket_list) { + unsigned long flags; + + if (s->state & SOCKET_CARDBUS) + continue; + + skt = s->pcmcia; + if (!skt) + continue; + skt = pcmcia_get_bus_socket(skt); + if (!skt) + continue; + spin_lock_irqsave(&pcmcia_dev_list_lock, flags); + list_for_each_entry(p_dev, &skt->devices_list, socket_device_list) { + struct pcmcia_driver *p_drv; + p_dev = pcmcia_get_dev(p_dev); + if (!p_dev) + continue; + if (!(p_dev->client.state & CLIENT_UNBOUND) || + (!p_dev->dev.driver)) { + pcmcia_put_dev(p_dev); + continue; + } + p_drv = to_pcmcia_drv(p_dev->dev.driver); + if (!strncmp(p_drv->drv.name, (char *)req->dev_info, DEV_NAME_LEN)) { + client = &p_dev->client; + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + goto found; + } + pcmcia_put_dev(p_dev); + } + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + pcmcia_put_bus_socket(skt); + } + found: + up_read(&pcmcia_socket_list_rwsem); + if (!p_dev || !client) + return -ENODEV; + + pcmcia_put_bus_socket(skt); /* safe, as we already hold a reference from bind_device */ + + *handle = client; + client->state &= ~CLIENT_UNBOUND; + client->Socket = s; + client->EventMask = req->EventMask; + client->event_handler = req->event_handler; + client->event_callback_args = req->event_callback_args; + client->event_callback_args.client_handle = client; + + if (s->state & SOCKET_CARDBUS) + client->state |= CLIENT_CARDBUS; + + if ((!(s->state & SOCKET_CARDBUS)) && (s->functions == 0) && + (client->Function != BIND_FN_ALL)) { + cistpl_longlink_mfc_t mfc; + if (pccard_read_tuple(s, client->Function, CISTPL_LONGLINK_MFC, &mfc) + == CS_SUCCESS) + s->functions = mfc.nfn; + else + s->functions = 1; + s->config = kmalloc(sizeof(config_t) * s->functions, + GFP_KERNEL); + if (!s->config) + goto out_no_resource; + memset(s->config, 0, sizeof(config_t) * s->functions); + } + + ds_dbg(1, "register_client(): client 0x%p, dev %s\n", + client, p_dev->dev.bus_id); + if (client->EventMask & CS_EVENT_REGISTRATION_COMPLETE) + EVENT(client, CS_EVENT_REGISTRATION_COMPLETE, CS_EVENT_PRI_LOW); + + if ((s->state & (SOCKET_PRESENT|SOCKET_CARDBUS)) == SOCKET_PRESENT) { + if (client->EventMask & CS_EVENT_CARD_INSERTION) + EVENT(client, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW); + } + + return CS_SUCCESS; + + out_no_resource: + pcmcia_put_dev(p_dev); + return CS_OUT_OF_RESOURCE; +} /* register_client */ +EXPORT_SYMBOL(pcmcia_register_client); + + +/*====================================================================*/ + +extern struct pci_bus *pcmcia_lookup_bus(struct pcmcia_socket *s); + +static int get_device_info(struct pcmcia_bus_socket *s, bind_info_t *bind_info, int first) +{ + dev_node_t *node; + struct pcmcia_device *p_dev; + unsigned long flags; + int ret = 0; + +#ifdef CONFIG_CARDBUS + /* + * Some unbelievably ugly code to associate the PCI cardbus + * device and its driver with the PCMCIA "bind" information. + */ + { + struct pci_bus *bus; + + bus = pcmcia_lookup_bus(s->parent); + if (bus) { + struct list_head *list; + struct pci_dev *dev = NULL; + + list = bus->devices.next; + while (list != &bus->devices) { + struct pci_dev *pdev = pci_dev_b(list); + list = list->next; + + if (first) { + dev = pdev; + break; + } + + /* Try to handle "next" here some way? */ + } + if (dev && dev->driver) { + strlcpy(bind_info->name, dev->driver->name, DEV_NAME_LEN); + bind_info->major = 0; + bind_info->minor = 0; + bind_info->next = NULL; + return 0; + } + } + } +#endif + + spin_lock_irqsave(&pcmcia_dev_list_lock, flags); + list_for_each_entry(p_dev, &s->devices_list, socket_device_list) { + if (p_dev->func == bind_info->function) { + p_dev = pcmcia_get_dev(p_dev); + if (!p_dev) + continue; + goto found; + } + } + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + return -ENODEV; + + found: + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + + if ((!p_dev->instance) || + (p_dev->instance->state & DEV_CONFIG_PENDING)) { + ret = -EAGAIN; + goto err_put; + } + + if (first) + node = p_dev->instance->dev; + else + for (node = p_dev->instance->dev; node; node = node->next) + if (node == bind_info->next) + break; + if (!node) { + ret = -ENODEV; + goto err_put; + } + + strlcpy(bind_info->name, node->dev_name, DEV_NAME_LEN); + bind_info->major = node->major; + bind_info->minor = node->minor; + bind_info->next = node->next; + + err_put: + pcmcia_put_dev(p_dev); + return (ret); +} /* get_device_info */ + +/*====================================================================*/ + +/* unbind _all_ devices attached to a given pcmcia_bus_socket. The + * drivers have been called with EVENT_CARD_REMOVAL before. + */ +static int unbind_request(struct pcmcia_bus_socket *s) +{ + struct pcmcia_device *p_dev; + unsigned long flags; + + ds_dbg(2, "unbind_request(%d)\n", s->parent->sock); + + s->device_count = 0; + + for (;;) { + /* unregister all pcmcia_devices registered with this socket*/ + spin_lock_irqsave(&pcmcia_dev_list_lock, flags); + if (list_empty(&s->devices_list)) { + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + return 0; + } + p_dev = list_entry((&s->devices_list)->next, struct pcmcia_device, socket_device_list); + list_del(&p_dev->socket_device_list); + p_dev->client.state |= CLIENT_STALE; + spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags); + + device_unregister(&p_dev->dev); + } + + return 0; +} /* unbind_request */ + +int pcmcia_deregister_client(client_handle_t handle) +{ + struct pcmcia_socket *s; + int i; + struct pcmcia_device *p_dev = handle_to_pdev(handle); + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + + s = SOCKET(handle); + ds_dbg(1, "deregister_client(%p)\n", handle); + + if (handle->state & (CLIENT_IRQ_REQ|CLIENT_IO_REQ|CLIENT_CONFIG_LOCKED)) + goto warn_out; + for (i = 0; i < MAX_WIN; i++) + if (handle->state & CLIENT_WIN_REQ(i)) + goto warn_out; + + if (handle->state & CLIENT_STALE) { + handle->client_magic = 0; + handle->state &= ~CLIENT_STALE; + pcmcia_put_dev(p_dev); + } else { + handle->state = CLIENT_UNBOUND; + handle->event_handler = NULL; + } + + return CS_SUCCESS; + warn_out: + printk(KERN_WARNING "ds: deregister_client was called too early.\n"); + return CS_IN_USE; +} /* deregister_client */ +EXPORT_SYMBOL(pcmcia_deregister_client); + + +/*====================================================================== + + The user-mode PC Card device interface + +======================================================================*/ + +static int ds_open(struct inode *inode, struct file *file) +{ + socket_t i = iminor(inode); + struct pcmcia_bus_socket *s; + user_info_t *user; + + ds_dbg(0, "ds_open(socket %d)\n", i); + + s = get_socket_info_by_nr(i); + if (!s) + return -ENODEV; + s = pcmcia_get_bus_socket(s); + if (!s) + return -ENODEV; + + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + if (s->state & DS_SOCKET_BUSY) { + pcmcia_put_bus_socket(s); + return -EBUSY; + } + else + s->state |= DS_SOCKET_BUSY; + } + + user = kmalloc(sizeof(user_info_t), GFP_KERNEL); + if (!user) { + pcmcia_put_bus_socket(s); + return -ENOMEM; + } + user->event_tail = user->event_head = 0; + user->next = s->user; + user->user_magic = USER_MAGIC; + user->socket = s; + s->user = user; + file->private_data = user; + + if (s->state & DS_SOCKET_PRESENT) + queue_event(user, CS_EVENT_CARD_INSERTION); + return 0; +} /* ds_open */ + +/*====================================================================*/ + +static int ds_release(struct inode *inode, struct file *file) +{ + struct pcmcia_bus_socket *s; + user_info_t *user, **link; + + ds_dbg(0, "ds_release(socket %d)\n", iminor(inode)); + + user = file->private_data; + if (CHECK_USER(user)) + goto out; + + s = user->socket; + + /* Unlink user data structure */ + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + s->state &= ~DS_SOCKET_BUSY; + } + file->private_data = NULL; + for (link = &s->user; *link; link = &(*link)->next) + if (*link == user) break; + if (link == NULL) + goto out; + *link = user->next; + user->user_magic = 0; + kfree(user); + pcmcia_put_bus_socket(s); +out: + return 0; +} /* ds_release */ + +/*====================================================================*/ + +static ssize_t ds_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct pcmcia_bus_socket *s; + user_info_t *user; + int ret; + + ds_dbg(2, "ds_read(socket %d)\n", iminor(file->f_dentry->d_inode)); + + if (count < 4) + return -EINVAL; + + user = file->private_data; + if (CHECK_USER(user)) + return -EIO; + + s = user->socket; + if (s->state & DS_SOCKET_DEAD) + return -EIO; + + ret = wait_event_interruptible(s->queue, !queue_empty(user)); + if (ret == 0) + ret = put_user(get_queued_event(user), (int __user *)buf) ? -EFAULT : 4; + + return ret; +} /* ds_read */ + +/*====================================================================*/ + +static ssize_t ds_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + ds_dbg(2, "ds_write(socket %d)\n", iminor(file->f_dentry->d_inode)); + + if (count != 4) + return -EINVAL; + if ((file->f_flags & O_ACCMODE) == O_RDONLY) + return -EBADF; + + return -EIO; +} /* ds_write */ + +/*====================================================================*/ + +/* No kernel lock - fine */ +static u_int ds_poll(struct file *file, poll_table *wait) +{ + struct pcmcia_bus_socket *s; + user_info_t *user; + + ds_dbg(2, "ds_poll(socket %d)\n", iminor(file->f_dentry->d_inode)); + + user = file->private_data; + if (CHECK_USER(user)) + return POLLERR; + s = user->socket; + /* + * We don't check for a dead socket here since that + * will send cardmgr into an endless spin. + */ + poll_wait(file, &s->queue, wait); + if (!queue_empty(user)) + return POLLIN | POLLRDNORM; + return 0; +} /* ds_poll */ + +/*====================================================================*/ + +extern int pcmcia_adjust_resource_info(adjust_t *adj); + +static int ds_ioctl(struct inode * inode, struct file * file, + u_int cmd, u_long arg) +{ + struct pcmcia_bus_socket *s; + void __user *uarg = (char __user *)arg; + u_int size; + int ret, err; + ds_ioctl_arg_t *buf; + user_info_t *user; + + ds_dbg(2, "ds_ioctl(socket %d, %#x, %#lx)\n", iminor(inode), cmd, arg); + + user = file->private_data; + if (CHECK_USER(user)) + return -EIO; + + s = user->socket; + if (s->state & DS_SOCKET_DEAD) + return -EIO; + + size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT; + if (size > sizeof(ds_ioctl_arg_t)) return -EINVAL; + + /* Permission check */ + if (!(cmd & IOC_OUT) && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (cmd & IOC_IN) { + if (!access_ok(VERIFY_READ, uarg, size)) { + ds_dbg(3, "ds_ioctl(): verify_read = %d\n", -EFAULT); + return -EFAULT; + } + } + if (cmd & IOC_OUT) { + if (!access_ok(VERIFY_WRITE, uarg, size)) { + ds_dbg(3, "ds_ioctl(): verify_write = %d\n", -EFAULT); + return -EFAULT; + } + } + buf = kmalloc(sizeof(ds_ioctl_arg_t), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + err = ret = 0; + + if (cmd & IOC_IN) __copy_from_user((char *)buf, uarg, size); + + switch (cmd) { + case DS_ADJUST_RESOURCE_INFO: + ret = pcmcia_adjust_resource_info(&buf->adjust); + break; + case DS_GET_CARD_SERVICES_INFO: + ret = pcmcia_get_card_services_info(&buf->servinfo); + break; + case DS_GET_CONFIGURATION_INFO: + if (buf->config.Function && + (buf->config.Function >= s->parent->functions)) + ret = CS_BAD_ARGS; + else + ret = pccard_get_configuration_info(s->parent, + buf->config.Function, &buf->config); + break; + case DS_GET_FIRST_TUPLE: + down(&s->parent->skt_sem); + pcmcia_validate_mem(s->parent); + up(&s->parent->skt_sem); + ret = pccard_get_first_tuple(s->parent, BIND_FN_ALL, &buf->tuple); + break; + case DS_GET_NEXT_TUPLE: + ret = pccard_get_next_tuple(s->parent, BIND_FN_ALL, &buf->tuple); + break; + case DS_GET_TUPLE_DATA: + buf->tuple.TupleData = buf->tuple_parse.data; + buf->tuple.TupleDataMax = sizeof(buf->tuple_parse.data); + ret = pccard_get_tuple_data(s->parent, &buf->tuple); + break; + case DS_PARSE_TUPLE: + buf->tuple.TupleData = buf->tuple_parse.data; + ret = pccard_parse_tuple(&buf->tuple, &buf->tuple_parse.parse); + break; + case DS_RESET_CARD: + ret = pccard_reset_card(s->parent); + break; + case DS_GET_STATUS: + if (buf->status.Function && + (buf->status.Function >= s->parent->functions)) + ret = CS_BAD_ARGS; + else + ret = pccard_get_status(s->parent, buf->status.Function, &buf->status); + break; + case DS_VALIDATE_CIS: + down(&s->parent->skt_sem); + pcmcia_validate_mem(s->parent); + up(&s->parent->skt_sem); + ret = pccard_validate_cis(s->parent, BIND_FN_ALL, &buf->cisinfo); + break; + case DS_SUSPEND_CARD: + ret = pcmcia_suspend_card(s->parent); + break; + case DS_RESUME_CARD: + ret = pcmcia_resume_card(s->parent); + break; + case DS_EJECT_CARD: + err = pcmcia_eject_card(s->parent); + break; + case DS_INSERT_CARD: + err = pcmcia_insert_card(s->parent); + break; + case DS_ACCESS_CONFIGURATION_REGISTER: + if ((buf->conf_reg.Action == CS_WRITE) && !capable(CAP_SYS_ADMIN)) { + err = -EPERM; + goto free_out; + } + if (buf->conf_reg.Function && + (buf->conf_reg.Function >= s->parent->functions)) + ret = CS_BAD_ARGS; + else + ret = pccard_access_configuration_register(s->parent, + buf->conf_reg.Function, &buf->conf_reg); + break; + case DS_GET_FIRST_REGION: + case DS_GET_NEXT_REGION: + case DS_BIND_MTD: + if (!capable(CAP_SYS_ADMIN)) { + err = -EPERM; + goto free_out; + } else { + static int printed = 0; + if (!printed) { + printk(KERN_WARNING "2.6. kernels use pcmciamtd instead of memory_cs.c and do not require special\n"); + printk(KERN_WARNING "MTD handling any more.\n"); + printed++; + } + } + err = -EINVAL; + goto free_out; + break; + case DS_GET_FIRST_WINDOW: + ret = pcmcia_get_window(s->parent, &buf->win_info.handle, 0, + &buf->win_info.window); + break; + case DS_GET_NEXT_WINDOW: + ret = pcmcia_get_window(s->parent, &buf->win_info.handle, + buf->win_info.handle->index + 1, &buf->win_info.window); + break; + case DS_GET_MEM_PAGE: + ret = pcmcia_get_mem_page(buf->win_info.handle, + &buf->win_info.map); + break; + case DS_REPLACE_CIS: + ret = pcmcia_replace_cis(s->parent, &buf->cisdump); + break; + case DS_BIND_REQUEST: + if (!capable(CAP_SYS_ADMIN)) { + err = -EPERM; + goto free_out; + } + err = bind_request(s, &buf->bind_info); + break; + case DS_GET_DEVICE_INFO: + err = get_device_info(s, &buf->bind_info, 1); + break; + case DS_GET_NEXT_DEVICE: + err = get_device_info(s, &buf->bind_info, 0); + break; + case DS_UNBIND_REQUEST: + err = 0; + break; + default: + err = -EINVAL; + } + + if ((err == 0) && (ret != CS_SUCCESS)) { + ds_dbg(2, "ds_ioctl: ret = %d\n", ret); + switch (ret) { + case CS_BAD_SOCKET: case CS_NO_CARD: + err = -ENODEV; break; + case CS_BAD_ARGS: case CS_BAD_ATTRIBUTE: case CS_BAD_IRQ: + case CS_BAD_TUPLE: + err = -EINVAL; break; + case CS_IN_USE: + err = -EBUSY; break; + case CS_OUT_OF_RESOURCE: + err = -ENOSPC; break; + case CS_NO_MORE_ITEMS: + err = -ENODATA; break; + case CS_UNSUPPORTED_FUNCTION: + err = -ENOSYS; break; + default: + err = -EIO; break; + } + } + + if (cmd & IOC_OUT) { + if (__copy_to_user(uarg, (char *)buf, size)) + err = -EFAULT; + } + +free_out: + kfree(buf); + return err; +} /* ds_ioctl */ + +/*====================================================================*/ + +static struct file_operations ds_fops = { + .owner = THIS_MODULE, + .open = ds_open, + .release = ds_release, + .ioctl = ds_ioctl, + .read = ds_read, + .write = ds_write, + .poll = ds_poll, +}; + +static int __devinit pcmcia_bus_add_socket(struct class_device *class_dev) +{ + struct pcmcia_socket *socket = class_get_devdata(class_dev); + struct pcmcia_bus_socket *s; + int ret; + + s = kmalloc(sizeof(struct pcmcia_bus_socket), GFP_KERNEL); + if(!s) + return -ENOMEM; + memset(s, 0, sizeof(struct pcmcia_bus_socket)); + + /* get reference to parent socket */ + s->parent = pcmcia_get_socket(socket); + if (!s->parent) { + printk(KERN_ERR "PCMCIA obtaining reference to socket %p failed\n", socket); + kfree (s); + return -ENODEV; + } + + kref_init(&s->refcount); + + /* + * Ugly. But we want to wait for the socket threads to have started up. + * We really should let the drivers themselves drive some of this.. + */ + msleep(250); + + init_waitqueue_head(&s->queue); + INIT_LIST_HEAD(&s->devices_list); + + /* Set up hotline to Card Services */ + s->callback.owner = THIS_MODULE; + s->callback.event = &ds_event; + s->callback.resources_done = &pcmcia_card_add; + socket->pcmcia = s; + + ret = pccard_register_pcmcia(socket, &s->callback); + if (ret) { + printk(KERN_ERR "PCMCIA registration PCCard core failed for socket %p\n", socket); + pcmcia_put_bus_socket(s); + socket->pcmcia = NULL; + return (ret); + } + + return 0; +} + + +static void pcmcia_bus_remove_socket(struct class_device *class_dev) +{ + struct pcmcia_socket *socket = class_get_devdata(class_dev); + + if (!socket || !socket->pcmcia) + return; + + pccard_register_pcmcia(socket, NULL); + + socket->pcmcia->state |= DS_SOCKET_DEAD; + pcmcia_put_bus_socket(socket->pcmcia); + socket->pcmcia = NULL; + + return; +} + + +/* the pcmcia_bus_interface is used to handle pcmcia socket devices */ +static struct class_interface pcmcia_bus_interface = { + .class = &pcmcia_socket_class, + .add = &pcmcia_bus_add_socket, + .remove = &pcmcia_bus_remove_socket, +}; + + +struct bus_type pcmcia_bus_type = { + .name = "pcmcia", + .match = pcmcia_bus_match, + .dev_attrs = pcmcia_dev_attrs, +}; +EXPORT_SYMBOL(pcmcia_bus_type); + + +static int __init init_pcmcia_bus(void) +{ + int i; + + spin_lock_init(&pcmcia_dev_list_lock); + + bus_register(&pcmcia_bus_type); + class_interface_register(&pcmcia_bus_interface); + + /* Set up character device for user mode clients */ + i = register_chrdev(0, "pcmcia", &ds_fops); + if (i == -EBUSY) + printk(KERN_NOTICE "unable to find a free device # for " + "Driver Services\n"); + else + major_dev = i; + +#ifdef CONFIG_PROC_FS + proc_pccard = proc_mkdir("pccard", proc_bus); + if (proc_pccard) + create_proc_read_entry("drivers",0,proc_pccard,proc_read_drivers,NULL); +#endif + + return 0; +} +fs_initcall(init_pcmcia_bus); /* one level after subsys_initcall so that + * pcmcia_socket_class is already registered */ + + +static void __exit exit_pcmcia_bus(void) +{ + class_interface_unregister(&pcmcia_bus_interface); + +#ifdef CONFIG_PROC_FS + if (proc_pccard) { + remove_proc_entry("drivers", proc_pccard); + remove_proc_entry("pccard", proc_bus); + } +#endif + if (major_dev != -1) + unregister_chrdev(major_dev, "pcmcia"); + + bus_unregister(&pcmcia_bus_type); +} +module_exit(exit_pcmcia_bus); + + + +/* helpers for backwards-compatible functions */ + +static struct pcmcia_bus_socket * get_socket_info_by_nr(unsigned int nr) +{ + struct pcmcia_socket * s = pcmcia_get_socket_by_nr(nr); + if (s && s->pcmcia) + return s->pcmcia; + else + return NULL; +} + +/* backwards-compatible accessing of driver --- by name! */ + +static struct pcmcia_driver * get_pcmcia_driver (dev_info_t *dev_info) +{ + struct device_driver *drv; + struct pcmcia_driver *p_drv; + + drv = driver_find((char *) dev_info, &pcmcia_bus_type); + if (!drv) + return NULL; + + p_drv = container_of(drv, struct pcmcia_driver, drv); + + return (p_drv); +} + +MODULE_ALIAS("ds"); diff --git a/drivers/pcmcia/hd64465_ss.c b/drivers/pcmcia/hd64465_ss.c new file mode 100644 index 000000000000..5e6c4ba7d995 --- /dev/null +++ b/drivers/pcmcia/hd64465_ss.c @@ -0,0 +1,968 @@ +/* + * $Id: hd64465_ss.c,v 1.7 2003/07/06 14:42:50 lethal Exp $ + * + * Device driver for the PCMCIA controller module of the + * Hitachi HD64465 handheld companion chip. + * + * Note that the HD64465 provides a very thin PCMCIA host bridge + * layer, requiring a lot of the work of supporting cards to be + * performed by the processor. For example: mapping of card + * interrupts to processor IRQs is done by IRQ demuxing software; + * IO and memory mappings are fixed; setting voltages according + * to card Voltage Select pins etc is done in software. + * + * Note also that this driver uses only the simple, fixed, + * 16MB, 16-bit wide mappings to PCMCIA spaces defined by the + * HD64465. Larger mappings, smaller mappings, or mappings of + * different width to the same socket, are all possible only by + * involving the SH7750's MMU, which is considered unnecessary here. + * The downside is that it may be possible for some drivers to + * break because they need or expect 8-bit mappings. + * + * This driver currently supports only the following configuration: + * SH7750 CPU, HD64465, TPS2206 voltage control chip. + * + * by Greg Banks <gbanks@pocketpenguins.com> + * (c) 2000 PocketPenguins Inc + */ + +#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 <asm/errno.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/device.h> + +#include <asm/io.h> +#include <asm/hd64465/hd64465.h> +#include <asm/hd64465/io.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> +#include <pcmcia/ss.h> +#include <pcmcia/bulkmem.h> +#include "cs_internal.h" + +#define MODNAME "hd64465_ss" + +/* #define HD64465_DEBUG 1 */ + +#if HD64465_DEBUG +#define DPRINTK(args...) printk(MODNAME ": " args) +#else +#define DPRINTK(args...) +#endif + +extern int hd64465_io_debug; +extern void * p3_ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags); +extern void p3_iounmap(void *addr); + +/*============================================================*/ + +#define HS_IO_MAP_SIZE (64*1024) + +typedef struct hs_socket_t +{ + unsigned int number; + u_int irq; + u_long mem_base; + void *io_base; + u_long mem_length; + u_int ctrl_base; + socket_state_t state; + pccard_io_map io_maps[MAX_IO_WIN]; + pccard_mem_map mem_maps[MAX_WIN]; + struct pcmcia_socket socket; +} hs_socket_t; + + + +#define HS_MAX_SOCKETS 2 +static hs_socket_t hs_sockets[HS_MAX_SOCKETS]; + +#define hs_in(sp, r) inb((sp)->ctrl_base + (r)) +#define hs_out(sp, v, r) outb(v, (sp)->ctrl_base + (r)) + + +/* translate a boolean value to a bit in a register */ +#define bool_to_regbit(sp, r, bi, bo) \ + do { \ + unsigned short v = hs_in(sp, r); \ + if (bo) \ + v |= (bi); \ + else \ + v &= ~(bi); \ + hs_out(sp, v, r); \ + } while(0) + +/* register offsets from HD64465_REG_PCC[01]ISR */ +#define ISR 0x0 +#define GCR 0x2 +#define CSCR 0x4 +#define CSCIER 0x6 +#define SCR 0x8 + + +/* Mask and values for CSCIER register */ +#define IER_MASK 0x80 +#define IER_ON 0x3f /* interrupts on */ +#define IER_OFF 0x00 /* interrupts off */ + +/*============================================================*/ + +#if HD64465_DEBUG > 10 + +static void cis_hex_dump(const unsigned char *x, int len) +{ + int i; + + for (i=0 ; i<len ; i++) + { + if (!(i & 0xf)) + printk("\n%08x", (unsigned)(x + i)); + printk(" %02x", *(volatile unsigned short*)x); + x += 2; + } + printk("\n"); +} + +#endif +/*============================================================*/ + +/* + * This code helps create the illusion that the IREQ line from + * the PC card is mapped to one of the CPU's IRQ lines by the + * host bridge hardware (which is how every host bridge *except* + * the HD64465 works). In particular, it supports enabling + * and disabling the IREQ line by code which knows nothing + * about the host bridge (e.g. device drivers, IDE code) using + * the request_irq(), free_irq(), probe_irq_on() and probe_irq_off() + * functions. Also, it supports sharing the mapped IRQ with + * real hardware IRQs from the -IRL0-3 lines. + */ + +#define HS_NUM_MAPPED_IRQS 16 /* Limitation of the PCMCIA code */ +static struct +{ + /* index is mapped irq number */ + hs_socket_t *sock; + hw_irq_controller *old_handler; +} hs_mapped_irq[HS_NUM_MAPPED_IRQS]; + +static void hs_socket_enable_ireq(hs_socket_t *sp) +{ + unsigned short cscier; + + DPRINTK("hs_socket_enable_ireq(sock=%d)\n", sp->number); + + cscier = hs_in(sp, CSCIER); + cscier &= ~HD64465_PCCCSCIER_PIREQE_MASK; + cscier |= HD64465_PCCCSCIER_PIREQE_LEVEL; + hs_out(sp, cscier, CSCIER); +} + +static void hs_socket_disable_ireq(hs_socket_t *sp) +{ + unsigned short cscier; + + DPRINTK("hs_socket_disable_ireq(sock=%d)\n", sp->number); + + cscier = hs_in(sp, CSCIER); + cscier &= ~HD64465_PCCCSCIER_PIREQE_MASK; + hs_out(sp, cscier, CSCIER); +} + +static unsigned int hs_startup_irq(unsigned int irq) +{ + hs_socket_enable_ireq(hs_mapped_irq[irq].sock); + hs_mapped_irq[irq].old_handler->startup(irq); + return 0; +} + +static void hs_shutdown_irq(unsigned int irq) +{ + hs_socket_disable_ireq(hs_mapped_irq[irq].sock); + hs_mapped_irq[irq].old_handler->shutdown(irq); +} + +static void hs_enable_irq(unsigned int irq) +{ + hs_socket_enable_ireq(hs_mapped_irq[irq].sock); + hs_mapped_irq[irq].old_handler->enable(irq); +} + +static void hs_disable_irq(unsigned int irq) +{ + hs_socket_disable_ireq(hs_mapped_irq[irq].sock); + hs_mapped_irq[irq].old_handler->disable(irq); +} + +extern struct hw_interrupt_type no_irq_type; + +static void hs_mask_and_ack_irq(unsigned int irq) +{ + hs_socket_disable_ireq(hs_mapped_irq[irq].sock); + /* ack_none() spuriously complains about an unexpected IRQ */ + if (hs_mapped_irq[irq].old_handler != &no_irq_type) + hs_mapped_irq[irq].old_handler->ack(irq); +} + +static void hs_end_irq(unsigned int irq) +{ + hs_socket_enable_ireq(hs_mapped_irq[irq].sock); + hs_mapped_irq[irq].old_handler->end(irq); +} + + +static struct hw_interrupt_type hd64465_ss_irq_type = { + .typename = "PCMCIA-IRQ", + .startup = hs_startup_irq, + .shutdown = hs_shutdown_irq, + .enable = hs_enable_irq, + .disable = hs_disable_irq, + .ack = hs_mask_and_ack_irq, + .end = hs_end_irq +}; + +/* + * This function should only ever be called with interrupts disabled. + */ +static void hs_map_irq(hs_socket_t *sp, unsigned int irq) +{ + DPRINTK("hs_map_irq(sock=%d irq=%d)\n", sp->number, irq); + + if (irq >= HS_NUM_MAPPED_IRQS) + return; + + hs_mapped_irq[irq].sock = sp; + /* insert ourselves as the irq controller */ + hs_mapped_irq[irq].old_handler = irq_desc[irq].handler; + irq_desc[irq].handler = &hd64465_ss_irq_type; +} + + +/* + * This function should only ever be called with interrupts disabled. + */ +static void hs_unmap_irq(hs_socket_t *sp, unsigned int irq) +{ + DPRINTK("hs_unmap_irq(sock=%d irq=%d)\n", sp->number, irq); + + if (irq >= HS_NUM_MAPPED_IRQS) + return; + + /* restore the original irq controller */ + irq_desc[irq].handler = hs_mapped_irq[irq].old_handler; +} + +/*============================================================*/ + + +/* + * Set Vpp and Vcc (in tenths of a Volt). Does not + * support the hi-Z state. + * + * Note, this assumes the board uses a TPS2206 chip to control + * the Vcc and Vpp voltages to the hs_sockets. If your board + * uses the MIC2563 (also supported by the HD64465) then you + * will have to modify this function. + */ + /* 0V 3.3V 5.5V */ +static const u_char hs_tps2206_avcc[3] = { 0x00, 0x04, 0x08 }; +static const u_char hs_tps2206_bvcc[3] = { 0x00, 0x80, 0x40 }; + +static int hs_set_voltages(hs_socket_t *sp, int Vcc, int Vpp) +{ + u_int psr; + u_int vcci = 0; + u_int sock = sp->number; + + DPRINTK("hs_set_voltage(%d, %d, %d)\n", sock, Vcc, Vpp); + + switch (Vcc) + { + case 0: vcci = 0; break; + case 33: vcci = 1; break; + case 50: vcci = 2; break; + default: return 0; + } + + /* Note: Vpp = 120 not supported -- Greg Banks */ + if (Vpp != 0 && Vpp != Vcc) + return 0; + + /* The PSR register holds 8 of the 9 bits which control + * the TPS2206 via its serial interface. + */ + psr = inw(HD64465_REG_PCCPSR); + switch (sock) + { + case 0: + psr &= 0x0f; + psr |= hs_tps2206_avcc[vcci]; + psr |= (Vpp == 0 ? 0x00 : 0x02); + break; + case 1: + psr &= 0xf0; + psr |= hs_tps2206_bvcc[vcci]; + psr |= (Vpp == 0 ? 0x00 : 0x20); + break; + }; + outw(psr, HD64465_REG_PCCPSR); + + return 1; +} + + +/*============================================================*/ + +/* + * Drive the RESET line to the card. + */ +static void hs_reset_socket(hs_socket_t *sp, int on) +{ + unsigned short v; + + v = hs_in(sp, GCR); + if (on) + v |= HD64465_PCCGCR_PCCR; + else + v &= ~HD64465_PCCGCR_PCCR; + hs_out(sp, v, GCR); +} + +/*============================================================*/ + +static int hs_init(struct pcmcia_socket *s) +{ + hs_socket_t *sp = container_of(s, struct hs_socket_t, socket); + + DPRINTK("hs_init(%d)\n", sp->number); + + return 0; +} + +/*============================================================*/ + + +static int hs_get_status(struct pcmcia_socket *s, u_int *value) +{ + hs_socket_t *sp = container_of(s, struct hs_socket_t, socket); + unsigned int isr; + u_int status = 0; + + + isr = hs_in(sp, ISR); + + /* Card is seated and powered when *both* CD pins are low */ + if ((isr & HD64465_PCCISR_PCD_MASK) == 0) + { + status |= SS_DETECT; /* card present */ + + switch (isr & HD64465_PCCISR_PBVD_MASK) + { + case HD64465_PCCISR_PBVD_BATGOOD: + break; + case HD64465_PCCISR_PBVD_BATWARN: + status |= SS_BATWARN; + break; + default: + status |= SS_BATDEAD; + break; + } + + if (isr & HD64465_PCCISR_PREADY) + status |= SS_READY; + + if (isr & HD64465_PCCISR_PMWP) + status |= SS_WRPROT; + + /* Voltage Select pins interpreted as per Table 4-5 of the std. + * Assuming we have the TPS2206, the socket is a "Low Voltage + * key, 3.3V and 5V available, no X.XV available". + */ + switch (isr & (HD64465_PCCISR_PVS2|HD64465_PCCISR_PVS1)) + { + case HD64465_PCCISR_PVS1: + printk(KERN_NOTICE MODNAME ": cannot handle X.XV card, ignored\n"); + status = 0; + break; + case 0: + case HD64465_PCCISR_PVS2: + /* 3.3V */ + status |= SS_3VCARD; + break; + case HD64465_PCCISR_PVS2|HD64465_PCCISR_PVS1: + /* 5V */ + break; + } + + /* TODO: SS_POWERON */ + /* TODO: SS_STSCHG */ + } + + DPRINTK("hs_get_status(%d) = %x\n", sock, status); + + *value = status; + return 0; +} + +/*============================================================*/ + +static int hs_get_socket(struct pcmcia_socket *s, socket_state_t *state) +{ + hs_socket_t *sp = container_of(s, struct hs_socket_t, socket); + + DPRINTK("hs_get_socket(%d)\n", sock); + + *state = sp->state; + return 0; +} + +/*============================================================*/ + +static int hs_set_socket(struct pcmcia_socket *s, socket_state_t *state) +{ + hs_socket_t *sp = container_of(s, struct hs_socket_t, socket); + u_long flags; + u_int changed; + unsigned short cscier; + + DPRINTK("hs_set_socket(sock=%d, flags=%x, csc_mask=%x, Vcc=%d, Vpp=%d, io_irq=%d)\n", + sock, state->flags, state->csc_mask, state->Vcc, state->Vpp, state->io_irq); + + local_irq_save(flags); /* Don't want interrupts happening here */ + + if (state->Vpp != sp->state.Vpp || + state->Vcc != sp->state.Vcc) { + if (!hs_set_voltages(sp, state->Vcc, state->Vpp)) { + local_irq_restore(flags); + return -EINVAL; + } + } + +/* hd64465_io_debug = 1; */ + /* + * Handle changes in the Card Status Change mask, + * by propagating to the CSCR register + */ + changed = sp->state.csc_mask ^ state->csc_mask; + cscier = hs_in(sp, CSCIER); + + if (changed & SS_DETECT) { + if (state->csc_mask & SS_DETECT) + cscier |= HD64465_PCCCSCIER_PCDE; + else + cscier &= ~HD64465_PCCCSCIER_PCDE; + } + + if (changed & SS_READY) { + if (state->csc_mask & SS_READY) + cscier |= HD64465_PCCCSCIER_PRE; + else + cscier &= ~HD64465_PCCCSCIER_PRE; + } + + if (changed & SS_BATDEAD) { + if (state->csc_mask & SS_BATDEAD) + cscier |= HD64465_PCCCSCIER_PBDE; + else + cscier &= ~HD64465_PCCCSCIER_PBDE; + } + + if (changed & SS_BATWARN) { + if (state->csc_mask & SS_BATWARN) + cscier |= HD64465_PCCCSCIER_PBWE; + else + cscier &= ~HD64465_PCCCSCIER_PBWE; + } + + if (changed & SS_STSCHG) { + if (state->csc_mask & SS_STSCHG) + cscier |= HD64465_PCCCSCIER_PSCE; + else + cscier &= ~HD64465_PCCCSCIER_PSCE; + } + + hs_out(sp, cscier, CSCIER); + + if (sp->state.io_irq && !state->io_irq) + hs_unmap_irq(sp, sp->state.io_irq); + else if (!sp->state.io_irq && state->io_irq) + hs_map_irq(sp, state->io_irq); + + + /* + * Handle changes in the flags field, + * by propagating to config registers. + */ + changed = sp->state.flags ^ state->flags; + + if (changed & SS_IOCARD) { + DPRINTK("card type: %s\n", + (state->flags & SS_IOCARD ? "i/o" : "memory" )); + bool_to_regbit(sp, GCR, HD64465_PCCGCR_PCCT, + state->flags & SS_IOCARD); + } + + if (changed & SS_RESET) { + DPRINTK("%s reset card\n", + (state->flags & SS_RESET ? "start" : "stop")); + bool_to_regbit(sp, GCR, HD64465_PCCGCR_PCCR, + state->flags & SS_RESET); + } + + if (changed & SS_OUTPUT_ENA) { + DPRINTK("%sabling card output\n", + (state->flags & SS_OUTPUT_ENA ? "en" : "dis")); + bool_to_regbit(sp, GCR, HD64465_PCCGCR_PDRV, + state->flags & SS_OUTPUT_ENA); + } + + /* TODO: SS_SPKR_ENA */ + +/* hd64465_io_debug = 0; */ + sp->state = *state; + + local_irq_restore(flags); + +#if HD64465_DEBUG > 10 + if (state->flags & SS_OUTPUT_ENA) + cis_hex_dump((const unsigned char*)sp->mem_base, 0x100); +#endif + return 0; +} + +/*============================================================*/ + +static int hs_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io) +{ + hs_socket_t *sp = container_of(s, struct hs_socket_t, socket); + int map = io->map; + int sock = sp->number; + struct pccard_io_map *sio; + pgprot_t prot; + + DPRINTK("hs_set_io_map(sock=%d, map=%d, flags=0x%x, speed=%dns, start=%#lx, stop=%#lx)\n", + sock, map, io->flags, io->speed, io->start, io->stop); + if (map >= MAX_IO_WIN) + return -EINVAL; + sio = &sp->io_maps[map]; + + /* check for null changes */ + if (io->flags == sio->flags && + io->start == sio->start && + io->stop == sio->stop) + return 0; + + if (io->flags & MAP_AUTOSZ) + prot = PAGE_KERNEL_PCC(sock, _PAGE_PCC_IODYN); + else if (io->flags & MAP_16BIT) + prot = PAGE_KERNEL_PCC(sock, _PAGE_PCC_IO16); + else + prot = PAGE_KERNEL_PCC(sock, _PAGE_PCC_IO8); + + /* TODO: handle MAP_USE_WAIT */ + if (io->flags & MAP_USE_WAIT) + printk(KERN_INFO MODNAME ": MAP_USE_WAIT unimplemented\n"); + /* TODO: handle MAP_PREFETCH */ + if (io->flags & MAP_PREFETCH) + printk(KERN_INFO MODNAME ": MAP_PREFETCH unimplemented\n"); + /* TODO: handle MAP_WRPROT */ + if (io->flags & MAP_WRPROT) + printk(KERN_INFO MODNAME ": MAP_WRPROT unimplemented\n"); + /* TODO: handle MAP_0WS */ + if (io->flags & MAP_0WS) + printk(KERN_INFO MODNAME ": MAP_0WS unimplemented\n"); + + if (io->flags & MAP_ACTIVE) { + unsigned long pstart, psize, paddrbase; + + paddrbase = virt_to_phys((void*)(sp->mem_base + 2 * HD64465_PCC_WINDOW)); + pstart = io->start & PAGE_MASK; + psize = ((io->stop + PAGE_SIZE) & PAGE_MASK) - pstart; + + /* + * Change PTEs in only that portion of the mapping requested + * by the caller. This means that most of the time, most of + * the PTEs in the io_vma will be unmapped and only the bottom + * page will be mapped. But the code allows for weird cards + * that might want IO ports > 4K. + */ + sp->io_base = p3_ioremap(paddrbase + pstart, psize, pgprot_val(prot)); + + /* + * Change the mapping used by inb() outb() etc + */ + hd64465_port_map(io->start, + io->stop - io->start + 1, + (unsigned long)sp->io_base + io->start, 0); + } else { + hd64465_port_unmap(sio->start, sio->stop - sio->start + 1); + p3_iounmap(sp->io_base); + } + + *sio = *io; + return 0; +} + +/*============================================================*/ + +static int hs_set_mem_map(struct pcmcia_socket *s, struct pccard_mem_map *mem) +{ + hs_socket_t *sp = container_of(s, struct hs_socket_t, socket); + struct pccard_mem_map *smem; + int map = mem->map; + unsigned long paddr; + +#if 0 + DPRINTK("hs_set_mem_map(sock=%d, map=%d, flags=0x%x, card_start=0x%08x)\n", + sock, map, mem->flags, mem->card_start); +#endif + + if (map >= MAX_WIN) + return -EINVAL; + smem = &sp->mem_maps[map]; + + paddr = sp->mem_base; /* base of Attribute mapping */ + if (!(mem->flags & MAP_ATTRIB)) + paddr += HD64465_PCC_WINDOW; /* base of Common mapping */ + paddr += mem->card_start; + + /* Because we specified SS_CAP_STATIC_MAP, we are obliged + * at this time to report the system address corresponding + * to the card address requested. This is how Socket Services + * queries our fixed mapping. I wish this fact had been + * documented - Greg Banks. + */ + mem->static_start = paddr; + + *smem = *mem; + + return 0; +} + +/* TODO: do we need to use the MMU to access Common memory ??? */ + +/*============================================================*/ + +/* + * This function is registered with the HD64465 glue code to do a + * secondary demux step on the PCMCIA interrupts. It handles + * mapping the IREQ request from the card to a standard Linux + * IRQ, as requested by SocketServices. + */ +static int hs_irq_demux(int irq, void *dev) +{ + hs_socket_t *sp = (hs_socket_t *)dev; + u_int cscr; + + DPRINTK("hs_irq_demux(irq=%d)\n", irq); + + if (sp->state.io_irq && + (cscr = hs_in(sp, CSCR)) & HD64465_PCCCSCR_PIREQ) { + cscr &= ~HD64465_PCCCSCR_PIREQ; + hs_out(sp, cscr, CSCR); + return sp->state.io_irq; + } + + return irq; +} + +/*============================================================*/ + +/* + * Interrupt handling routine. + */ + +static irqreturn_t hs_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + hs_socket_t *sp = (hs_socket_t *)dev; + u_int events = 0; + u_int cscr; + + + cscr = hs_in(sp, CSCR); + + DPRINTK("hs_interrupt, cscr=%04x\n", cscr); + + /* check for bus-related changes to be reported to Socket Services */ + if (cscr & HD64465_PCCCSCR_PCDC) { + /* double-check for a 16-bit card, as we don't support CardBus */ + if ((hs_in(sp, ISR) & HD64465_PCCISR_PCD_MASK) != 0) { + printk(KERN_NOTICE MODNAME + ": socket %d, card not a supported card type or not inserted correctly\n", + sp->number); + /* Don't do the rest unless a card is present */ + cscr &= ~(HD64465_PCCCSCR_PCDC| + HD64465_PCCCSCR_PRC| + HD64465_PCCCSCR_PBW| + HD64465_PCCCSCR_PBD| + HD64465_PCCCSCR_PSC); + } else { + cscr &= ~HD64465_PCCCSCR_PCDC; + events |= SS_DETECT; /* card insertion or removal */ + } + } + if (cscr & HD64465_PCCCSCR_PRC) { + cscr &= ~HD64465_PCCCSCR_PRC; + events |= SS_READY; /* ready signal changed */ + } + if (cscr & HD64465_PCCCSCR_PBW) { + cscr &= ~HD64465_PCCCSCR_PSC; + events |= SS_BATWARN; /* battery warning */ + } + if (cscr & HD64465_PCCCSCR_PBD) { + cscr &= ~HD64465_PCCCSCR_PSC; + events |= SS_BATDEAD; /* battery dead */ + } + if (cscr & HD64465_PCCCSCR_PSC) { + cscr &= ~HD64465_PCCCSCR_PSC; + events |= SS_STSCHG; /* STSCHG (status changed) signal */ + } + + if (cscr & HD64465_PCCCSCR_PIREQ) { + cscr &= ~HD64465_PCCCSCR_PIREQ; + + /* This should have been dealt with during irq demux */ + printk(KERN_NOTICE MODNAME ": unexpected IREQ from card\n"); + } + + hs_out(sp, cscr, CSCR); + + if (events) + pcmcia_parse_events(&sp->socket, events); + + return IRQ_HANDLED; +} + +/*============================================================*/ + +static struct pccard_operations hs_operations = { + .init = hs_init, + .get_status = hs_get_status, + .get_socket = hs_get_socket, + .set_socket = hs_set_socket, + .set_io_map = hs_set_io_map, + .set_mem_map = hs_set_mem_map, +}; + +static int hs_init_socket(hs_socket_t *sp, int irq, unsigned long mem_base, + unsigned int ctrl_base) +{ + unsigned short v; + int i, err; + + memset(sp, 0, sizeof(*sp)); + sp->irq = irq; + sp->mem_base = mem_base; + sp->mem_length = 4*HD64465_PCC_WINDOW; /* 16MB */ + sp->ctrl_base = ctrl_base; + + for (i=0 ; i<MAX_IO_WIN ; i++) + sp->io_maps[i].map = i; + for (i=0 ; i<MAX_WIN ; i++) + sp->mem_maps[i].map = i; + + hd64465_register_irq_demux(sp->irq, hs_irq_demux, sp); + + if ((err = request_irq(sp->irq, hs_interrupt, SA_INTERRUPT, MODNAME, sp)) < 0) + return err; + if (request_mem_region(sp->mem_base, sp->mem_length, MODNAME) == 0) { + sp->mem_base = 0; + return -ENOMEM; + } + + + /* According to section 3.2 of the PCMCIA standard, low-voltage + * capable cards must implement cold insertion, i.e. Vpp and + * Vcc set to 0 before card is inserted. + */ + /*hs_set_voltages(sp, 0, 0);*/ + + /* hi-Z the outputs to the card and set 16MB map mode */ + v = hs_in(sp, GCR); + v &= ~HD64465_PCCGCR_PCCT; /* memory-only card */ + hs_out(sp, v, GCR); + + v = hs_in(sp, GCR); + v |= HD64465_PCCGCR_PDRV; /* enable outputs to card */ + hs_out(sp, v, GCR); + + v = hs_in(sp, GCR); + v |= HD64465_PCCGCR_PMMOD; /* 16MB mapping mode */ + hs_out(sp, v, GCR); + + v = hs_in(sp, GCR); + /* lowest 16MB of Common */ + v &= ~(HD64465_PCCGCR_PPA25|HD64465_PCCGCR_PPA24); + hs_out(sp, v, GCR); + + hs_reset_socket(sp, 1); + + printk(KERN_INFO "HD64465 PCMCIA bridge socket %d at 0x%08lx irq %d\n", + i, sp->mem_base, sp->irq); + + return 0; +} + +static void hs_exit_socket(hs_socket_t *sp) +{ + unsigned short cscier, gcr; + unsigned long flags; + + local_irq_save(flags); + + /* turn off interrupts in hardware */ + cscier = hs_in(sp, CSCIER); + cscier = (cscier & IER_MASK) | IER_OFF; + hs_out(sp, cscier, CSCIER); + + /* hi-Z the outputs to the card */ + gcr = hs_in(sp, GCR); + gcr &= HD64465_PCCGCR_PDRV; + hs_out(sp, gcr, GCR); + + /* power the card down */ + hs_set_voltages(sp, 0, 0); + + if (sp->mem_base != 0) + release_mem_region(sp->mem_base, sp->mem_length); + if (sp->irq != 0) { + free_irq(sp->irq, hs_interrupt); + hd64465_unregister_irq_demux(sp->irq); + } + + local_irq_restore(flags); +} + +static int hd64465_suspend(struct device *dev, u32 state, u32 level) +{ + int ret = 0; + if (level == SUSPEND_SAVE_STATE) + ret = pcmcia_socket_dev_suspend(dev, state); + return ret; +} + +static int hd64465_resume(struct device *dev, u32 level) +{ + int ret = 0; + if (level == RESUME_RESTORE_STATE) + ret = pcmcia_socket_dev_resume(dev); + return ret; +} + +static struct device_driver hd64465_driver = { + .name = "hd64465-pcmcia", + .bus = &platform_bus_type, + .suspend = hd64465_suspend, + .resume = hd64465_resume, +}; + +static struct platform_device hd64465_device = { + .name = "hd64465-pcmcia", + .id = 0, +}; + +static int __init init_hs(void) +{ + int i; + unsigned short v; + +/* hd64465_io_debug = 1; */ + if (driver_register(&hd64465_driver)) + return -EINVAL; + + /* Wake both sockets out of STANDBY mode */ + /* TODO: wait 15ms */ + v = inw(HD64465_REG_SMSCR); + v &= ~(HD64465_SMSCR_PC0ST|HD64465_SMSCR_PC1ST); + outw(v, HD64465_REG_SMSCR); + + /* keep power controller out of shutdown mode */ + v = inb(HD64465_REG_PCC0SCR); + v |= HD64465_PCCSCR_SHDN; + outb(v, HD64465_REG_PCC0SCR); + + /* use serial (TPS2206) power controller */ + v = inb(HD64465_REG_PCC0CSCR); + v |= HD64465_PCCCSCR_PSWSEL; + outb(v, HD64465_REG_PCC0CSCR); + + /* + * Setup hs_sockets[] structures and request system resources. + * TODO: on memory allocation failure, power down the socket + * before quitting. + */ + for (i=0; i<HS_MAX_SOCKETS; i++) { + hs_set_voltages(&hs_sockets[i], 0, 0); + + hs_sockets[i].socket.features |= SS_CAP_PCCARD | SS_CAP_STATIC_MAP; /* mappings are fixed in host memory */ + hs_sockets[i].socket.resource_ops = &pccard_static_ops; + hs_sockets[i].socket.irq_mask = 0xffde;/*0xffff*/ /* IRQs mapped in s/w so can do any, really */ + hs_sockets[i].socket.map_size = HD64465_PCC_WINDOW; /* 16MB fixed window size */ + + hs_sockets[i].socket.owner = THIS_MODULE; + hs_sockets[i].socket.ss_entry = &hs_operations; + } + + i = hs_init_socket(&hs_sockets[0], + HD64465_IRQ_PCMCIA0, + HD64465_PCC0_BASE, + HD64465_REG_PCC0ISR); + if (i < 0) { + unregister_driver(&hd64465_driver); + return i; + } + i = hs_init_socket(&hs_sockets[1], + HD64465_IRQ_PCMCIA1, + HD64465_PCC1_BASE, + HD64465_REG_PCC1ISR); + if (i < 0) { + unregister_driver(&hd64465_driver); + return i; + } + +/* hd64465_io_debug = 0; */ + + platform_device_register(&hd64465_device); + + for (i=0; i<HS_MAX_SOCKETS; i++) { + unsigned int ret; + hs_sockets[i].socket.dev.dev = &hd64465_device.dev; + hs_sockets[i].number = i; + ret = pcmcia_register_socket(&hs_sockets[i].socket); + if (ret && i) + pcmcia_unregister_socket(&hs_sockets[0].socket); + } + + return 0; +} + +static void __exit exit_hs(void) +{ + int i; + + for (i=0 ; i<HS_MAX_SOCKETS ; i++) { + pcmcia_unregister_socket(&hs_sockets[i].socket); + hs_exit_socket(&hs_sockets[i]); + } + + platform_device_unregister(&hd64465_device); + unregister_driver(&hd64465_driver); +} + +module_init(init_hs); +module_exit(exit_hs); + +/*============================================================*/ +/*END*/ diff --git a/drivers/pcmcia/i82092.c b/drivers/pcmcia/i82092.c new file mode 100644 index 000000000000..f3fdc748659d --- /dev/null +++ b/drivers/pcmcia/i82092.c @@ -0,0 +1,799 @@ +/* + * Driver for Intel I82092AA PCI-PCMCIA bridge. + * + * (C) 2001 Red Hat, Inc. + * + * Author: Arjan Van De Ven <arjanv@redhat.com> + * Loosly based on i82365.c from the pcmcia-cs package + * + * $Id: i82092aa.c,v 1.2 2001/10/23 14:43:34 arjanv Exp $ + */ + +#include <linux/kernel.h> +#include <linux/config.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/device.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> + +#include <asm/system.h> +#include <asm/io.h> + +#include "i82092aa.h" +#include "i82365.h" + +MODULE_LICENSE("GPL"); + +/* PCI core routines */ +static struct pci_device_id i82092aa_pci_ids[] = { + { + .vendor = PCI_VENDOR_ID_INTEL, + .device = PCI_DEVICE_ID_INTEL_82092AA_0, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + {} +}; +MODULE_DEVICE_TABLE(pci, i82092aa_pci_ids); + +static int i82092aa_socket_suspend (struct pci_dev *dev, pm_message_t state) +{ + return pcmcia_socket_dev_suspend(&dev->dev, state); +} + +static int i82092aa_socket_resume (struct pci_dev *dev) +{ + return pcmcia_socket_dev_resume(&dev->dev); +} + +static struct pci_driver i82092aa_pci_drv = { + .name = "i82092aa", + .id_table = i82092aa_pci_ids, + .probe = i82092aa_pci_probe, + .remove = __devexit_p(i82092aa_pci_remove), + .suspend = i82092aa_socket_suspend, + .resume = i82092aa_socket_resume, +}; + + +/* the pccard structure and its functions */ +static struct pccard_operations i82092aa_operations = { + .init = i82092aa_init, + .get_status = i82092aa_get_status, + .get_socket = i82092aa_get_socket, + .set_socket = i82092aa_set_socket, + .set_io_map = i82092aa_set_io_map, + .set_mem_map = i82092aa_set_mem_map, +}; + +/* The card can do upto 4 sockets, allocate a structure for each of them */ + +struct socket_info { + int number; + int card_state; /* 0 = no socket, + 1 = empty socket, + 2 = card but not initialized, + 3 = operational card */ + kio_addr_t io_base; /* base io address of the socket */ + + struct pcmcia_socket socket; + struct pci_dev *dev; /* The PCI device for the socket */ +}; + +#define MAX_SOCKETS 4 +static struct socket_info sockets[MAX_SOCKETS]; +static int socket_count; /* shortcut */ + + +static int __devinit i82092aa_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + unsigned char configbyte; + int i, ret; + + enter("i82092aa_pci_probe"); + + if ((ret = pci_enable_device(dev))) + return ret; + + pci_read_config_byte(dev, 0x40, &configbyte); /* PCI Configuration Control */ + switch(configbyte&6) { + case 0: + socket_count = 2; + break; + case 2: + socket_count = 1; + break; + case 4: + case 6: + socket_count = 4; + break; + + default: + printk(KERN_ERR "i82092aa: Oops, you did something we didn't think of.\n"); + ret = -EIO; + goto err_out_disable; + } + printk(KERN_INFO "i82092aa: configured as a %d socket device.\n", socket_count); + + if (!request_region(pci_resource_start(dev, 0), 2, "i82092aa")) { + ret = -EBUSY; + goto err_out_disable; + } + + for (i = 0;i<socket_count;i++) { + sockets[i].card_state = 1; /* 1 = present but empty */ + sockets[i].io_base = pci_resource_start(dev, 0); + sockets[i].socket.features |= SS_CAP_PCCARD; + sockets[i].socket.map_size = 0x1000; + sockets[i].socket.irq_mask = 0; + sockets[i].socket.pci_irq = dev->irq; + sockets[i].socket.owner = THIS_MODULE; + + sockets[i].number = i; + + if (card_present(i)) { + sockets[i].card_state = 3; + dprintk(KERN_DEBUG "i82092aa: slot %i is occupied\n",i); + } else { + dprintk(KERN_DEBUG "i82092aa: slot %i is vacant\n",i); + } + } + + /* Now, specifiy that all interrupts are to be done as PCI interrupts */ + configbyte = 0xFF; /* bitmask, one bit per event, 1 = PCI interrupt, 0 = ISA interrupt */ + pci_write_config_byte(dev, 0x50, configbyte); /* PCI Interrupt Routing Register */ + + /* Register the interrupt handler */ + dprintk(KERN_DEBUG "Requesting interrupt %i \n",dev->irq); + if ((ret = request_irq(dev->irq, i82092aa_interrupt, SA_SHIRQ, "i82092aa", i82092aa_interrupt))) { + printk(KERN_ERR "i82092aa: Failed to register IRQ %d, aborting\n", dev->irq); + goto err_out_free_res; + } + + pci_set_drvdata(dev, &sockets[i].socket); + + for (i = 0; i<socket_count; i++) { + sockets[i].socket.dev.dev = &dev->dev; + sockets[i].socket.ops = &i82092aa_operations; + sockets[i].socket.resource_ops = &pccard_nonstatic_ops; + ret = pcmcia_register_socket(&sockets[i].socket); + if (ret) { + goto err_out_free_sockets; + } + } + + leave("i82092aa_pci_probe"); + return 0; + +err_out_free_sockets: + if (i) { + for (i--;i>=0;i--) { + pcmcia_unregister_socket(&sockets[i].socket); + } + } + free_irq(dev->irq, i82092aa_interrupt); +err_out_free_res: + release_region(pci_resource_start(dev, 0), 2); +err_out_disable: + pci_disable_device(dev); + return ret; +} + +static void __devexit i82092aa_pci_remove(struct pci_dev *dev) +{ + struct pcmcia_socket *socket = pci_get_drvdata(dev); + + enter("i82092aa_pci_remove"); + + free_irq(dev->irq, i82092aa_interrupt); + + if (socket) + pcmcia_unregister_socket(socket); + + leave("i82092aa_pci_remove"); +} + +static DEFINE_SPINLOCK(port_lock); + +/* basic value read/write functions */ + +static unsigned char indirect_read(int socket, unsigned short reg) +{ + unsigned short int port; + unsigned char val; + unsigned long flags; + spin_lock_irqsave(&port_lock,flags); + reg += socket * 0x40; + port = sockets[socket].io_base; + outb(reg,port); + val = inb(port+1); + spin_unlock_irqrestore(&port_lock,flags); + return val; +} + +#if 0 +static unsigned short indirect_read16(int socket, unsigned short reg) +{ + unsigned short int port; + unsigned short tmp; + unsigned long flags; + spin_lock_irqsave(&port_lock,flags); + reg = reg + socket * 0x40; + port = sockets[socket].io_base; + outb(reg,port); + tmp = inb(port+1); + reg++; + outb(reg,port); + tmp = tmp | (inb(port+1)<<8); + spin_unlock_irqrestore(&port_lock,flags); + return tmp; +} +#endif + +static void indirect_write(int socket, unsigned short reg, unsigned char value) +{ + unsigned short int port; + unsigned long flags; + spin_lock_irqsave(&port_lock,flags); + reg = reg + socket * 0x40; + port = sockets[socket].io_base; + outb(reg,port); + outb(value,port+1); + spin_unlock_irqrestore(&port_lock,flags); +} + +static void indirect_setbit(int socket, unsigned short reg, unsigned char mask) +{ + unsigned short int port; + unsigned char val; + unsigned long flags; + spin_lock_irqsave(&port_lock,flags); + reg = reg + socket * 0x40; + port = sockets[socket].io_base; + outb(reg,port); + val = inb(port+1); + val |= mask; + outb(reg,port); + outb(val,port+1); + spin_unlock_irqrestore(&port_lock,flags); +} + + +static void indirect_resetbit(int socket, unsigned short reg, unsigned char mask) +{ + unsigned short int port; + unsigned char val; + unsigned long flags; + spin_lock_irqsave(&port_lock,flags); + reg = reg + socket * 0x40; + port = sockets[socket].io_base; + outb(reg,port); + val = inb(port+1); + val &= ~mask; + outb(reg,port); + outb(val,port+1); + spin_unlock_irqrestore(&port_lock,flags); +} + +static void indirect_write16(int socket, unsigned short reg, unsigned short value) +{ + unsigned short int port; + unsigned char val; + unsigned long flags; + spin_lock_irqsave(&port_lock,flags); + reg = reg + socket * 0x40; + port = sockets[socket].io_base; + + outb(reg,port); + val = value & 255; + outb(val,port+1); + + reg++; + + outb(reg,port); + val = value>>8; + outb(val,port+1); + spin_unlock_irqrestore(&port_lock,flags); +} + +/* simple helper functions */ +/* External clock time, in nanoseconds. 120 ns = 8.33 MHz */ +static int cycle_time = 120; + +static int to_cycles(int ns) +{ + if (cycle_time!=0) + return ns/cycle_time; + else + return 0; +} + + +/* Interrupt handler functionality */ + +static irqreturn_t i82092aa_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + int i; + int loopcount = 0; + int handled = 0; + + unsigned int events, active=0; + +/* enter("i82092aa_interrupt");*/ + + while (1) { + loopcount++; + if (loopcount>20) { + printk(KERN_ERR "i82092aa: infinite eventloop in interrupt \n"); + break; + } + + active = 0; + + for (i=0;i<socket_count;i++) { + int csc; + if (sockets[i].card_state==0) /* Inactive socket, should not happen */ + continue; + + csc = indirect_read(i,I365_CSC); /* card status change register */ + + if (csc==0) /* no events on this socket */ + continue; + handled = 1; + events = 0; + + if (csc & I365_CSC_DETECT) { + events |= SS_DETECT; + printk("Card detected in socket %i!\n",i); + } + + if (indirect_read(i,I365_INTCTL) & I365_PC_IOCARD) { + /* For IO/CARDS, bit 0 means "read the card" */ + events |= (csc & I365_CSC_STSCHG) ? SS_STSCHG : 0; + } else { + /* Check for battery/ready events */ + events |= (csc & I365_CSC_BVD1) ? SS_BATDEAD : 0; + events |= (csc & I365_CSC_BVD2) ? SS_BATWARN : 0; + events |= (csc & I365_CSC_READY) ? SS_READY : 0; + } + + if (events) { + pcmcia_parse_events(&sockets[i].socket, events); + } + active |= events; + } + + if (active==0) /* no more events to handle */ + break; + + } + return IRQ_RETVAL(handled); +/* leave("i82092aa_interrupt");*/ +} + + + +/* socket functions */ + +static int card_present(int socketno) +{ + unsigned int val; + enter("card_present"); + + if ((socketno<0) || (socketno >= MAX_SOCKETS)) + return 0; + if (sockets[socketno].io_base == 0) + return 0; + + + val = indirect_read(socketno, 1); /* Interface status register */ + if ((val&12)==12) { + leave("card_present 1"); + return 1; + } + + leave("card_present 0"); + return 0; +} + +static void set_bridge_state(int sock) +{ + enter("set_bridge_state"); + indirect_write(sock, I365_GBLCTL,0x00); + indirect_write(sock, I365_GENCTL,0x00); + + indirect_setbit(sock, I365_INTCTL,0x08); + leave("set_bridge_state"); +} + + + + + + +static int i82092aa_init(struct pcmcia_socket *sock) +{ + int i; + struct resource res = { .start = 0, .end = 0x0fff }; + pccard_io_map io = { 0, 0, 0, 0, 1 }; + pccard_mem_map mem = { .res = &res, }; + + enter("i82092aa_init"); + + for (i = 0; i < 2; i++) { + io.map = i; + i82092aa_set_io_map(sock, &io); + } + for (i = 0; i < 5; i++) { + mem.map = i; + i82092aa_set_mem_map(sock, &mem); + } + + leave("i82092aa_init"); + return 0; +} + +static int i82092aa_get_status(struct pcmcia_socket *socket, u_int *value) +{ + unsigned int sock = container_of(socket, struct socket_info, socket)->number; + unsigned int status; + + enter("i82092aa_get_status"); + + status = indirect_read(sock,I365_STATUS); /* Interface Status Register */ + *value = 0; + + if ((status & I365_CS_DETECT) == I365_CS_DETECT) { + *value |= SS_DETECT; + } + + /* IO cards have a different meaning of bits 0,1 */ + /* Also notice the inverse-logic on the bits */ + if (indirect_read(sock, I365_INTCTL) & I365_PC_IOCARD) { + /* IO card */ + if (!(status & I365_CS_STSCHG)) + *value |= SS_STSCHG; + } else { /* non I/O card */ + if (!(status & I365_CS_BVD1)) + *value |= SS_BATDEAD; + if (!(status & I365_CS_BVD2)) + *value |= SS_BATWARN; + + } + + if (status & I365_CS_WRPROT) + (*value) |= SS_WRPROT; /* card is write protected */ + + if (status & I365_CS_READY) + (*value) |= SS_READY; /* card is not busy */ + + if (status & I365_CS_POWERON) + (*value) |= SS_POWERON; /* power is applied to the card */ + + + leave("i82092aa_get_status"); + return 0; +} + + +static int i82092aa_get_socket(struct pcmcia_socket *socket, socket_state_t *state) +{ + unsigned int sock = container_of(socket, struct socket_info, socket)->number; + unsigned char reg,vcc,vpp; + + enter("i82092aa_get_socket"); + state->flags = 0; + state->Vcc = 0; + state->Vpp = 0; + state->io_irq = 0; + state->csc_mask = 0; + + /* First the power status of the socket */ + reg = indirect_read(sock,I365_POWER); /* PCTRL - Power Control Register */ + + if (reg & I365_PWR_AUTO) + state->flags |= SS_PWR_AUTO; /* Automatic Power Switch */ + + if (reg & I365_PWR_OUT) + state->flags |= SS_OUTPUT_ENA; /* Output signals are enabled */ + + vcc = reg & I365_VCC_MASK; vpp = reg & I365_VPP1_MASK; + + if (reg & I365_VCC_5V) { /* Can still be 3.3V, in this case the Vcc value will be overwritten later */ + state->Vcc = 50; + + if (vpp == I365_VPP1_5V) + state->Vpp = 50; + if (vpp == I365_VPP1_12V) + state->Vpp = 120; + + } + + if ((reg & I365_VCC_3V)==I365_VCC_3V) + state->Vcc = 33; + + + /* Now the IO card, RESET flags and IO interrupt */ + + reg = indirect_read(sock, I365_INTCTL); /* IGENC, Interrupt and General Control */ + + if ((reg & I365_PC_RESET)==0) + state->flags |= SS_RESET; + if (reg & I365_PC_IOCARD) + state->flags |= SS_IOCARD; /* This is an IO card */ + + /* Set the IRQ number */ + if (sockets[sock].dev!=NULL) + state->io_irq = sockets[sock].dev->irq; + + /* Card status change */ + reg = indirect_read(sock, I365_CSCINT); /* CSCICR, Card Status Change Interrupt Configuration */ + + if (reg & I365_CSC_DETECT) + state->csc_mask |= SS_DETECT; /* Card detect is enabled */ + + if (state->flags & SS_IOCARD) {/* IO Cards behave different */ + if (reg & I365_CSC_STSCHG) + state->csc_mask |= SS_STSCHG; + } else { + if (reg & I365_CSC_BVD1) + state->csc_mask |= SS_BATDEAD; + if (reg & I365_CSC_BVD2) + state->csc_mask |= SS_BATWARN; + if (reg & I365_CSC_READY) + state->csc_mask |= SS_READY; + } + + leave("i82092aa_get_socket"); + return 0; +} + +static int i82092aa_set_socket(struct pcmcia_socket *socket, socket_state_t *state) +{ + unsigned int sock = container_of(socket, struct socket_info, socket)->number; + unsigned char reg; + + enter("i82092aa_set_socket"); + + /* First, set the global controller options */ + + set_bridge_state(sock); + + /* Values for the IGENC register */ + + reg = 0; + if (!(state->flags & SS_RESET)) /* The reset bit has "inverse" logic */ + reg = reg | I365_PC_RESET; + if (state->flags & SS_IOCARD) + reg = reg | I365_PC_IOCARD; + + indirect_write(sock,I365_INTCTL,reg); /* IGENC, Interrupt and General Control Register */ + + /* Power registers */ + + reg = I365_PWR_NORESET; /* default: disable resetdrv on resume */ + + if (state->flags & SS_PWR_AUTO) { + printk("Auto power\n"); + reg |= I365_PWR_AUTO; /* automatic power mngmnt */ + } + if (state->flags & SS_OUTPUT_ENA) { + printk("Power Enabled \n"); + reg |= I365_PWR_OUT; /* enable power */ + } + + switch (state->Vcc) { + case 0: + break; + case 50: + printk("setting voltage to Vcc to 5V on socket %i\n",sock); + reg |= I365_VCC_5V; + break; + default: + printk("i82092aa: i82092aa_set_socket called with invalid VCC power value: %i ", state->Vcc); + leave("i82092aa_set_socket"); + return -EINVAL; + } + + + switch (state->Vpp) { + case 0: + printk("not setting Vpp on socket %i\n",sock); + break; + case 50: + printk("setting Vpp to 5.0 for socket %i\n",sock); + reg |= I365_VPP1_5V | I365_VPP2_5V; + break; + case 120: + printk("setting Vpp to 12.0\n"); + reg |= I365_VPP1_12V | I365_VPP2_12V; + break; + default: + printk("i82092aa: i82092aa_set_socket called with invalid VPP power value: %i ", state->Vcc); + leave("i82092aa_set_socket"); + return -EINVAL; + } + + if (reg != indirect_read(sock,I365_POWER)) /* only write if changed */ + indirect_write(sock,I365_POWER,reg); + + /* Enable specific interrupt events */ + + reg = 0x00; + if (state->csc_mask & SS_DETECT) { + reg |= I365_CSC_DETECT; + } + if (state->flags & SS_IOCARD) { + if (state->csc_mask & SS_STSCHG) + reg |= I365_CSC_STSCHG; + } else { + if (state->csc_mask & SS_BATDEAD) + reg |= I365_CSC_BVD1; + if (state->csc_mask & SS_BATWARN) + reg |= I365_CSC_BVD2; + if (state->csc_mask & SS_READY) + reg |= I365_CSC_READY; + + } + + /* now write the value and clear the (probably bogus) pending stuff by doing a dummy read*/ + + indirect_write(sock,I365_CSCINT,reg); + (void)indirect_read(sock,I365_CSC); + + leave("i82092aa_set_socket"); + return 0; +} + +static int i82092aa_set_io_map(struct pcmcia_socket *socket, struct pccard_io_map *io) +{ + unsigned int sock = container_of(socket, struct socket_info, socket)->number; + unsigned char map, ioctl; + + enter("i82092aa_set_io_map"); + + map = io->map; + + /* Check error conditions */ + if (map > 1) { + leave("i82092aa_set_io_map with invalid map"); + return -EINVAL; + } + if ((io->start > 0xffff) || (io->stop > 0xffff) || (io->stop < io->start)){ + leave("i82092aa_set_io_map with invalid io"); + return -EINVAL; + } + + /* Turn off the window before changing anything */ + if (indirect_read(sock, I365_ADDRWIN) & I365_ENA_IO(map)) + indirect_resetbit(sock, I365_ADDRWIN, I365_ENA_IO(map)); + +/* printk("set_io_map: Setting range to %x - %x \n",io->start,io->stop); */ + + /* write the new values */ + indirect_write16(sock,I365_IO(map)+I365_W_START,io->start); + indirect_write16(sock,I365_IO(map)+I365_W_STOP,io->stop); + + ioctl = indirect_read(sock,I365_IOCTL) & ~I365_IOCTL_MASK(map); + + if (io->flags & (MAP_16BIT|MAP_AUTOSZ)) + ioctl |= I365_IOCTL_16BIT(map); + + indirect_write(sock,I365_IOCTL,ioctl); + + /* Turn the window back on if needed */ + if (io->flags & MAP_ACTIVE) + indirect_setbit(sock,I365_ADDRWIN,I365_ENA_IO(map)); + + leave("i82092aa_set_io_map"); + return 0; +} + +static int i82092aa_set_mem_map(struct pcmcia_socket *socket, struct pccard_mem_map *mem) +{ + struct socket_info *sock_info = container_of(socket, struct socket_info, socket); + unsigned int sock = sock_info->number; + struct pci_bus_region region; + unsigned short base, i; + unsigned char map; + + enter("i82092aa_set_mem_map"); + + pcibios_resource_to_bus(sock_info->dev, ®ion, mem->res); + + map = mem->map; + if (map > 4) { + leave("i82092aa_set_mem_map: invalid map"); + return -EINVAL; + } + + + if ( (mem->card_start > 0x3ffffff) || (region.start > region.end) || + (mem->speed > 1000) ) { + leave("i82092aa_set_mem_map: invalid address / speed"); + printk("invalid mem map for socket %i : %lx to %lx with a start of %x \n",sock,region.start, region.end, mem->card_start); + return -EINVAL; + } + + /* Turn off the window before changing anything */ + if (indirect_read(sock, I365_ADDRWIN) & I365_ENA_MEM(map)) + indirect_resetbit(sock, I365_ADDRWIN, I365_ENA_MEM(map)); + + +/* printk("set_mem_map: Setting map %i range to %x - %x on socket %i, speed is %i, active = %i \n",map, region.start,region.end,sock,mem->speed,mem->flags & MAP_ACTIVE); */ + + /* write the start address */ + base = I365_MEM(map); + i = (region.start >> 12) & 0x0fff; + if (mem->flags & MAP_16BIT) + i |= I365_MEM_16BIT; + if (mem->flags & MAP_0WS) + i |= I365_MEM_0WS; + indirect_write16(sock,base+I365_W_START,i); + + /* write the stop address */ + + i= (region.end >> 12) & 0x0fff; + switch (to_cycles(mem->speed)) { + case 0: + break; + case 1: + i |= I365_MEM_WS0; + break; + case 2: + i |= I365_MEM_WS1; + break; + default: + i |= I365_MEM_WS1 | I365_MEM_WS0; + break; + } + + indirect_write16(sock,base+I365_W_STOP,i); + + /* card start */ + + i = ((mem->card_start - region.start) >> 12) & 0x3fff; + if (mem->flags & MAP_WRPROT) + i |= I365_MEM_WRPROT; + if (mem->flags & MAP_ATTRIB) { +/* printk("requesting attribute memory for socket %i\n",sock);*/ + i |= I365_MEM_REG; + } else { +/* printk("requesting normal memory for socket %i\n",sock);*/ + } + indirect_write16(sock,base+I365_W_OFF,i); + + /* Enable the window if necessary */ + if (mem->flags & MAP_ACTIVE) + indirect_setbit(sock, I365_ADDRWIN, I365_ENA_MEM(map)); + + leave("i82092aa_set_mem_map"); + return 0; +} + +static int i82092aa_module_init(void) +{ + enter("i82092aa_module_init"); + pci_register_driver(&i82092aa_pci_drv); + leave("i82092aa_module_init"); + return 0; +} + +static void i82092aa_module_exit(void) +{ + enter("i82092aa_module_exit"); + pci_unregister_driver(&i82092aa_pci_drv); + if (sockets[0].io_base>0) + release_region(sockets[0].io_base, 2); + leave("i82092aa_module_exit"); +} + +module_init(i82092aa_module_init); +module_exit(i82092aa_module_exit); + diff --git a/drivers/pcmcia/i82092aa.h b/drivers/pcmcia/i82092aa.h new file mode 100644 index 000000000000..b98cac7bda9f --- /dev/null +++ b/drivers/pcmcia/i82092aa.h @@ -0,0 +1,39 @@ +#ifndef _INCLUDE_GUARD_i82092aa_H_ +#define _INCLUDE_GUARD_i82092aa_H_ + +#include <linux/interrupt.h> + +/* $Id: i82092aa.h,v 1.1.1.1 2001/09/19 14:53:15 dwmw2 Exp $ */ + +/* Debuging defines */ +#ifdef NOTRACE +#define enter(x) printk("Enter: %s, %s line %i\n",x,__FILE__,__LINE__) +#define leave(x) printk("Leave: %s, %s line %i\n",x,__FILE__,__LINE__) +#define dprintk(fmt, args...) printk(fmt , ## args) +#else +#define enter(x) do {} while (0) +#define leave(x) do {} while (0) +#define dprintk(fmt, args...) do {} while (0) +#endif + + + +/* prototypes */ + +static int i82092aa_pci_probe(struct pci_dev *dev, const struct pci_device_id *id); +static void i82092aa_pci_remove(struct pci_dev *dev); +static int card_present(int socketno); +static irqreturn_t i82092aa_interrupt(int irq, void *dev, struct pt_regs *regs); + + + + +static int i82092aa_get_status(struct pcmcia_socket *socket, u_int *value); +static int i82092aa_get_socket(struct pcmcia_socket *socket, socket_state_t *state); +static int i82092aa_set_socket(struct pcmcia_socket *socket, socket_state_t *state); +static int i82092aa_set_io_map(struct pcmcia_socket *socket, struct pccard_io_map *io); +static int i82092aa_set_mem_map(struct pcmcia_socket *socket, struct pccard_mem_map *mem); +static int i82092aa_init(struct pcmcia_socket *socket); + +#endif + diff --git a/drivers/pcmcia/i82365.c b/drivers/pcmcia/i82365.c new file mode 100644 index 000000000000..90a335a5d9fa --- /dev/null +++ b/drivers/pcmcia/i82365.c @@ -0,0 +1,1454 @@ +/*====================================================================== + + Device driver for Intel 82365 and compatible PC Card controllers. + + i82365.c 1.265 1999/11/10 18:36:21 + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in which + case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/bitops.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/system.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> + +#include <linux/isapnp.h> + +/* ISA-bus controllers */ +#include "i82365.h" +#include "cirrus.h" +#include "vg468.h" +#include "ricoh.h" + +#ifdef DEBUG +static const char version[] = +"i82365.c 1.265 1999/11/10 18:36:21 (David Hinds)"; + +static int pc_debug; + +module_param(pc_debug, int, 0644); + +#define debug(lvl, fmt, arg...) do { \ + if (pc_debug > (lvl)) \ + printk(KERN_DEBUG "i82365: " fmt , ## arg); \ +} while (0) +#else +#define debug(lvl, fmt, arg...) do { } while (0) +#endif + +static irqreturn_t i365_count_irq(int, void *, struct pt_regs *); +static inline int _check_irq(int irq, int flags) +{ + if (request_irq(irq, i365_count_irq, flags, "x", i365_count_irq) != 0) + return -1; + free_irq(irq, i365_count_irq); + return 0; +} + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +/* Default base address for i82365sl and other ISA chips */ +static unsigned long i365_base = 0x3e0; +/* Should we probe at 0x3e2 for an extra ISA controller? */ +static int extra_sockets = 0; +/* Specify a socket number to ignore */ +static int ignore = -1; +/* Bit map or list of interrupts to choose from */ +static u_int irq_mask = 0xffff; +static int irq_list[16]; +static int irq_list_count; +/* The card status change interrupt -- 0 means autoselect */ +static int cs_irq = 0; + +/* Probe for safe interrupts? */ +static int do_scan = 1; +/* Poll status interval -- 0 means default to interrupt */ +static int poll_interval = 0; +/* External clock time, in nanoseconds. 120 ns = 8.33 MHz */ +static int cycle_time = 120; + +/* Cirrus options */ +static int has_dma = -1; +static int has_led = -1; +static int has_ring = -1; +static int dynamic_mode = 0; +static int freq_bypass = -1; +static int setup_time = -1; +static int cmd_time = -1; +static int recov_time = -1; + +/* Vadem options */ +static int async_clock = -1; +static int cable_mode = -1; +static int wakeup = 0; + +module_param(i365_base, ulong, 0444); +module_param(ignore, int, 0444); +module_param(extra_sockets, int, 0444); +module_param(irq_mask, int, 0444); +module_param_array(irq_list, int, &irq_list_count, 0444); +module_param(cs_irq, int, 0444); +module_param(async_clock, int, 0444); +module_param(cable_mode, int, 0444); +module_param(wakeup, int, 0444); + +module_param(do_scan, int, 0444); +module_param(poll_interval, int, 0444); +module_param(cycle_time, int, 0444); +module_param(has_dma, int, 0444); +module_param(has_led, int, 0444); +module_param(has_ring, int, 0444); +module_param(dynamic_mode, int, 0444); +module_param(freq_bypass, int, 0444); +module_param(setup_time, int, 0444); +module_param(cmd_time, int, 0444); +module_param(recov_time, int, 0444); + +/*====================================================================*/ + +typedef struct cirrus_state_t { + u_char misc1, misc2; + u_char timer[6]; +} cirrus_state_t; + +typedef struct vg46x_state_t { + u_char ctl, ema; +} vg46x_state_t; + +struct i82365_socket { + u_short type, flags; + struct pcmcia_socket socket; + unsigned int number; + kio_addr_t ioaddr; + u_short psock; + u_char cs_irq, intr; + union { + cirrus_state_t cirrus; + vg46x_state_t vg46x; + } state; +}; + +/* Where we keep track of our sockets... */ +static int sockets = 0; +static struct i82365_socket socket[8] = { + { 0, }, /* ... */ +}; + +/* Default ISA interrupt mask */ +#define I365_MASK 0xdeb8 /* irq 15,14,12,11,10,9,7,5,4,3 */ + +static int grab_irq; +static DEFINE_SPINLOCK(isa_lock); +#define ISA_LOCK(n, f) spin_lock_irqsave(&isa_lock, f) +#define ISA_UNLOCK(n, f) spin_unlock_irqrestore(&isa_lock, f) + +static struct timer_list poll_timer; + +/*====================================================================*/ + +/* These definitions must match the pcic table! */ +typedef enum pcic_id { + IS_I82365A, IS_I82365B, IS_I82365DF, + IS_IBM, IS_RF5Cx96, IS_VLSI, IS_VG468, IS_VG469, + IS_PD6710, IS_PD672X, IS_VT83C469, +} pcic_id; + +/* Flags for classifying groups of controllers */ +#define IS_VADEM 0x0001 +#define IS_CIRRUS 0x0002 +#define IS_VIA 0x0010 +#define IS_UNKNOWN 0x0400 +#define IS_VG_PWR 0x0800 +#define IS_DF_PWR 0x1000 +#define IS_REGISTERED 0x2000 +#define IS_ALIVE 0x8000 + +typedef struct pcic_t { + char *name; + u_short flags; +} pcic_t; + +static pcic_t pcic[] = { + { "Intel i82365sl A step", 0 }, + { "Intel i82365sl B step", 0 }, + { "Intel i82365sl DF", IS_DF_PWR }, + { "IBM Clone", 0 }, + { "Ricoh RF5C296/396", 0 }, + { "VLSI 82C146", 0 }, + { "Vadem VG-468", IS_VADEM }, + { "Vadem VG-469", IS_VADEM|IS_VG_PWR }, + { "Cirrus PD6710", IS_CIRRUS }, + { "Cirrus PD672x", IS_CIRRUS }, + { "VIA VT83C469", IS_CIRRUS|IS_VIA }, +}; + +#define PCIC_COUNT (sizeof(pcic)/sizeof(pcic_t)) + +/*====================================================================*/ + +static DEFINE_SPINLOCK(bus_lock); + +static u_char i365_get(u_short sock, u_short reg) +{ + unsigned long flags; + spin_lock_irqsave(&bus_lock,flags); + { + kio_addr_t port = socket[sock].ioaddr; + u_char val; + reg = I365_REG(socket[sock].psock, reg); + outb(reg, port); val = inb(port+1); + spin_unlock_irqrestore(&bus_lock,flags); + return val; + } +} + +static void i365_set(u_short sock, u_short reg, u_char data) +{ + unsigned long flags; + spin_lock_irqsave(&bus_lock,flags); + { + kio_addr_t port = socket[sock].ioaddr; + u_char val = I365_REG(socket[sock].psock, reg); + outb(val, port); outb(data, port+1); + spin_unlock_irqrestore(&bus_lock,flags); + } +} + +static void i365_bset(u_short sock, u_short reg, u_char mask) +{ + u_char d = i365_get(sock, reg); + d |= mask; + i365_set(sock, reg, d); +} + +static void i365_bclr(u_short sock, u_short reg, u_char mask) +{ + u_char d = i365_get(sock, reg); + d &= ~mask; + i365_set(sock, reg, d); +} + +static void i365_bflip(u_short sock, u_short reg, u_char mask, int b) +{ + u_char d = i365_get(sock, reg); + if (b) + d |= mask; + else + d &= ~mask; + i365_set(sock, reg, d); +} + +static u_short i365_get_pair(u_short sock, u_short reg) +{ + u_short a, b; + a = i365_get(sock, reg); + b = i365_get(sock, reg+1); + return (a + (b<<8)); +} + +static void i365_set_pair(u_short sock, u_short reg, u_short data) +{ + i365_set(sock, reg, data & 0xff); + i365_set(sock, reg+1, data >> 8); +} + +/*====================================================================== + + Code to save and restore global state information for Cirrus + PD67xx controllers, and to set and report global configuration + options. + + The VIA controllers also use these routines, as they are mostly + Cirrus lookalikes, without the timing registers. + +======================================================================*/ + +#define flip(v,b,f) (v = ((f)<0) ? v : ((f) ? ((v)|(b)) : ((v)&(~b)))) + +static void cirrus_get_state(u_short s) +{ + int i; + cirrus_state_t *p = &socket[s].state.cirrus; + p->misc1 = i365_get(s, PD67_MISC_CTL_1); + p->misc1 &= (PD67_MC1_MEDIA_ENA | PD67_MC1_INPACK_ENA); + p->misc2 = i365_get(s, PD67_MISC_CTL_2); + for (i = 0; i < 6; i++) + p->timer[i] = i365_get(s, PD67_TIME_SETUP(0)+i); +} + +static void cirrus_set_state(u_short s) +{ + int i; + u_char misc; + cirrus_state_t *p = &socket[s].state.cirrus; + + misc = i365_get(s, PD67_MISC_CTL_2); + i365_set(s, PD67_MISC_CTL_2, p->misc2); + if (misc & PD67_MC2_SUSPEND) mdelay(50); + misc = i365_get(s, PD67_MISC_CTL_1); + misc &= ~(PD67_MC1_MEDIA_ENA | PD67_MC1_INPACK_ENA); + i365_set(s, PD67_MISC_CTL_1, misc | p->misc1); + for (i = 0; i < 6; i++) + i365_set(s, PD67_TIME_SETUP(0)+i, p->timer[i]); +} + +static u_int __init cirrus_set_opts(u_short s, char *buf) +{ + struct i82365_socket *t = &socket[s]; + cirrus_state_t *p = &socket[s].state.cirrus; + u_int mask = 0xffff; + + if (has_ring == -1) has_ring = 1; + flip(p->misc2, PD67_MC2_IRQ15_RI, has_ring); + flip(p->misc2, PD67_MC2_DYNAMIC_MODE, dynamic_mode); + flip(p->misc2, PD67_MC2_FREQ_BYPASS, freq_bypass); + if (p->misc2 & PD67_MC2_IRQ15_RI) + strcat(buf, " [ring]"); + if (p->misc2 & PD67_MC2_DYNAMIC_MODE) + strcat(buf, " [dyn mode]"); + if (p->misc2 & PD67_MC2_FREQ_BYPASS) + strcat(buf, " [freq bypass]"); + if (p->misc1 & PD67_MC1_INPACK_ENA) + strcat(buf, " [inpack]"); + if (p->misc2 & PD67_MC2_IRQ15_RI) + mask &= ~0x8000; + if (has_led > 0) { + strcat(buf, " [led]"); + mask &= ~0x1000; + } + if (has_dma > 0) { + strcat(buf, " [dma]"); + mask &= ~0x0600; + } + if (!(t->flags & IS_VIA)) { + if (setup_time >= 0) + p->timer[0] = p->timer[3] = setup_time; + if (cmd_time > 0) { + p->timer[1] = cmd_time; + p->timer[4] = cmd_time*2+4; + } + if (p->timer[1] == 0) { + p->timer[1] = 6; p->timer[4] = 16; + if (p->timer[0] == 0) + p->timer[0] = p->timer[3] = 1; + } + if (recov_time >= 0) + p->timer[2] = p->timer[5] = recov_time; + buf += strlen(buf); + sprintf(buf, " [%d/%d/%d] [%d/%d/%d]", p->timer[0], p->timer[1], + p->timer[2], p->timer[3], p->timer[4], p->timer[5]); + } + return mask; +} + +/*====================================================================== + + Code to save and restore global state information for Vadem VG468 + and VG469 controllers, and to set and report global configuration + options. + +======================================================================*/ + +static void vg46x_get_state(u_short s) +{ + vg46x_state_t *p = &socket[s].state.vg46x; + p->ctl = i365_get(s, VG468_CTL); + if (socket[s].type == IS_VG469) + p->ema = i365_get(s, VG469_EXT_MODE); +} + +static void vg46x_set_state(u_short s) +{ + vg46x_state_t *p = &socket[s].state.vg46x; + i365_set(s, VG468_CTL, p->ctl); + if (socket[s].type == IS_VG469) + i365_set(s, VG469_EXT_MODE, p->ema); +} + +static u_int __init vg46x_set_opts(u_short s, char *buf) +{ + vg46x_state_t *p = &socket[s].state.vg46x; + + flip(p->ctl, VG468_CTL_ASYNC, async_clock); + flip(p->ema, VG469_MODE_CABLE, cable_mode); + if (p->ctl & VG468_CTL_ASYNC) + strcat(buf, " [async]"); + if (p->ctl & VG468_CTL_INPACK) + strcat(buf, " [inpack]"); + if (socket[s].type == IS_VG469) { + u_char vsel = i365_get(s, VG469_VSELECT); + if (vsel & VG469_VSEL_EXT_STAT) { + strcat(buf, " [ext mode]"); + if (vsel & VG469_VSEL_EXT_BUS) + strcat(buf, " [isa buf]"); + } + if (p->ema & VG469_MODE_CABLE) + strcat(buf, " [cable]"); + if (p->ema & VG469_MODE_COMPAT) + strcat(buf, " [c step]"); + } + return 0xffff; +} + +/*====================================================================== + + Generic routines to get and set controller options + +======================================================================*/ + +static void get_bridge_state(u_short s) +{ + struct i82365_socket *t = &socket[s]; + if (t->flags & IS_CIRRUS) + cirrus_get_state(s); + else if (t->flags & IS_VADEM) + vg46x_get_state(s); +} + +static void set_bridge_state(u_short s) +{ + struct i82365_socket *t = &socket[s]; + if (t->flags & IS_CIRRUS) + cirrus_set_state(s); + else { + i365_set(s, I365_GBLCTL, 0x00); + i365_set(s, I365_GENCTL, 0x00); + } + i365_bflip(s, I365_INTCTL, I365_INTR_ENA, t->intr); + if (t->flags & IS_VADEM) + vg46x_set_state(s); +} + +static u_int __init set_bridge_opts(u_short s, u_short ns) +{ + u_short i; + u_int m = 0xffff; + char buf[128]; + + for (i = s; i < s+ns; i++) { + if (socket[i].flags & IS_ALIVE) { + printk(KERN_INFO " host opts [%d]: already alive!\n", i); + continue; + } + buf[0] = '\0'; + get_bridge_state(i); + if (socket[i].flags & IS_CIRRUS) + m = cirrus_set_opts(i, buf); + else if (socket[i].flags & IS_VADEM) + m = vg46x_set_opts(i, buf); + set_bridge_state(i); + printk(KERN_INFO " host opts [%d]:%s\n", i, + (*buf) ? buf : " none"); + } + return m; +} + +/*====================================================================== + + Interrupt testing code, for ISA and PCI interrupts + +======================================================================*/ + +static volatile u_int irq_hits; +static u_short irq_sock; + +static irqreturn_t i365_count_irq(int irq, void *dev, struct pt_regs *regs) +{ + i365_get(irq_sock, I365_CSC); + irq_hits++; + debug(2, "-> hit on irq %d\n", irq); + return IRQ_HANDLED; +} + +static u_int __init test_irq(u_short sock, int irq) +{ + debug(2, " testing ISA irq %d\n", irq); + if (request_irq(irq, i365_count_irq, 0, "scan", i365_count_irq) != 0) + return 1; + irq_hits = 0; irq_sock = sock; + msleep(10); + if (irq_hits) { + free_irq(irq, i365_count_irq); + debug(2, " spurious hit!\n"); + return 1; + } + + /* Generate one interrupt */ + i365_set(sock, I365_CSCINT, I365_CSC_DETECT | (irq << 4)); + i365_bset(sock, I365_GENCTL, I365_CTL_SW_IRQ); + udelay(1000); + + free_irq(irq, i365_count_irq); + + /* mask all interrupts */ + i365_set(sock, I365_CSCINT, 0); + debug(2, " hits = %d\n", irq_hits); + + return (irq_hits != 1); +} + +static u_int __init isa_scan(u_short sock, u_int mask0) +{ + u_int mask1 = 0; + int i; + +#ifdef __alpha__ +#define PIC 0x4d0 + /* Don't probe level-triggered interrupts -- reserved for PCI */ + mask0 &= ~(inb(PIC) | (inb(PIC+1) << 8)); +#endif + + if (do_scan) { + set_bridge_state(sock); + i365_set(sock, I365_CSCINT, 0); + for (i = 0; i < 16; i++) + if ((mask0 & (1 << i)) && (test_irq(sock, i) == 0)) + mask1 |= (1 << i); + for (i = 0; i < 16; i++) + if ((mask1 & (1 << i)) && (test_irq(sock, i) != 0)) + mask1 ^= (1 << i); + } + + printk(KERN_INFO " ISA irqs ("); + if (mask1) { + printk("scanned"); + } else { + /* Fallback: just find interrupts that aren't in use */ + for (i = 0; i < 16; i++) + if ((mask0 & (1 << i)) && (_check_irq(i, 0) == 0)) + mask1 |= (1 << i); + printk("default"); + /* If scan failed, default to polled status */ + if (!cs_irq && (poll_interval == 0)) poll_interval = HZ; + } + printk(") = "); + + for (i = 0; i < 16; i++) + if (mask1 & (1<<i)) + printk("%s%d", ((mask1 & ((1<<i)-1)) ? "," : ""), i); + if (mask1 == 0) printk("none!"); + + return mask1; +} + +/*====================================================================*/ + +/* Time conversion functions */ + +static int to_cycles(int ns) +{ + return ns/cycle_time; +} + +/*====================================================================*/ + +static int __init identify(kio_addr_t port, u_short sock) +{ + u_char val; + int type = -1; + + /* Use the next free entry in the socket table */ + socket[sockets].ioaddr = port; + socket[sockets].psock = sock; + + /* Wake up a sleepy Cirrus controller */ + if (wakeup) { + i365_bclr(sockets, PD67_MISC_CTL_2, PD67_MC2_SUSPEND); + /* Pause at least 50 ms */ + mdelay(50); + } + + if ((val = i365_get(sockets, I365_IDENT)) & 0x70) + return -1; + switch (val) { + case 0x82: + type = IS_I82365A; break; + case 0x83: + type = IS_I82365B; break; + case 0x84: + type = IS_I82365DF; break; + case 0x88: case 0x89: case 0x8a: + type = IS_IBM; break; + } + + /* Check for Vadem VG-468 chips */ + outb(0x0e, port); + outb(0x37, port); + i365_bset(sockets, VG468_MISC, VG468_MISC_VADEMREV); + val = i365_get(sockets, I365_IDENT); + if (val & I365_IDENT_VADEM) { + i365_bclr(sockets, VG468_MISC, VG468_MISC_VADEMREV); + type = ((val & 7) >= 4) ? IS_VG469 : IS_VG468; + } + + /* Check for Ricoh chips */ + val = i365_get(sockets, RF5C_CHIP_ID); + if ((val == RF5C_CHIP_RF5C296) || (val == RF5C_CHIP_RF5C396)) + type = IS_RF5Cx96; + + /* Check for Cirrus CL-PD67xx chips */ + i365_set(sockets, PD67_CHIP_INFO, 0); + val = i365_get(sockets, PD67_CHIP_INFO); + if ((val & PD67_INFO_CHIP_ID) == PD67_INFO_CHIP_ID) { + val = i365_get(sockets, PD67_CHIP_INFO); + if ((val & PD67_INFO_CHIP_ID) == 0) { + type = (val & PD67_INFO_SLOTS) ? IS_PD672X : IS_PD6710; + i365_set(sockets, PD67_EXT_INDEX, 0xe5); + if (i365_get(sockets, PD67_EXT_INDEX) != 0xe5) + type = IS_VT83C469; + } + } + return type; +} /* identify */ + +/*====================================================================== + + See if a card is present, powered up, in IO mode, and already + bound to a (non PC Card) Linux driver. We leave these alone. + + We make an exception for cards that seem to be serial devices. + +======================================================================*/ + +static int __init is_alive(u_short sock) +{ + u_char stat; + kio_addr_t start, stop; + + stat = i365_get(sock, I365_STATUS); + start = i365_get_pair(sock, I365_IO(0)+I365_W_START); + stop = i365_get_pair(sock, I365_IO(0)+I365_W_STOP); + if ((stat & I365_CS_DETECT) && (stat & I365_CS_POWERON) && + (i365_get(sock, I365_INTCTL) & I365_PC_IOCARD) && + (i365_get(sock, I365_ADDRWIN) & I365_ENA_IO(0)) && + (check_region(start, stop-start+1) != 0) && + ((start & 0xfeef) != 0x02e8)) + return 1; + else + return 0; +} + +/*====================================================================*/ + +static void __init add_socket(kio_addr_t port, int psock, int type) +{ + socket[sockets].ioaddr = port; + socket[sockets].psock = psock; + socket[sockets].type = type; + socket[sockets].flags = pcic[type].flags; + if (is_alive(sockets)) + socket[sockets].flags |= IS_ALIVE; + sockets++; +} + +static void __init add_pcic(int ns, int type) +{ + u_int mask = 0, i, base; + int isa_irq = 0; + struct i82365_socket *t = &socket[sockets-ns]; + + base = sockets-ns; + if (t->ioaddr > 0) request_region(t->ioaddr, 2, "i82365"); + + if (base == 0) printk("\n"); + printk(KERN_INFO " %s", pcic[type].name); + printk(" ISA-to-PCMCIA at port %#lx ofs 0x%02x", + t->ioaddr, t->psock*0x40); + printk(", %d socket%s\n", ns, ((ns > 1) ? "s" : "")); + + /* Set host options, build basic interrupt mask */ + if (irq_list_count == 0) + mask = irq_mask; + else + for (i = mask = 0; i < irq_list_count; i++) + mask |= (1<<irq_list[i]); + mask &= I365_MASK & set_bridge_opts(base, ns); + /* Scan for ISA interrupts */ + mask = isa_scan(base, mask); + + /* Poll if only two interrupts available */ + if (!poll_interval) { + u_int tmp = (mask & 0xff20); + tmp = tmp & (tmp-1); + if ((tmp & (tmp-1)) == 0) + poll_interval = HZ; + } + /* Only try an ISA cs_irq if this is the first controller */ + if (!grab_irq && (cs_irq || !poll_interval)) { + /* Avoid irq 12 unless it is explicitly requested */ + u_int cs_mask = mask & ((cs_irq) ? (1<<cs_irq) : ~(1<<12)); + for (cs_irq = 15; cs_irq > 0; cs_irq--) + if ((cs_mask & (1 << cs_irq)) && + (_check_irq(cs_irq, 0) == 0)) + break; + if (cs_irq) { + grab_irq = 1; + isa_irq = cs_irq; + printk(" status change on irq %d\n", cs_irq); + } + } + + if (!isa_irq) { + if (poll_interval == 0) + poll_interval = HZ; + printk(" polling interval = %d ms\n", + poll_interval * 1000 / HZ); + + } + + /* Update socket interrupt information, capabilities */ + for (i = 0; i < ns; i++) { + t[i].socket.features |= SS_CAP_PCCARD; + t[i].socket.map_size = 0x1000; + t[i].socket.irq_mask = mask; + t[i].cs_irq = isa_irq; + } + +} /* add_pcic */ + +/*====================================================================*/ + +#ifdef CONFIG_PNP +static struct isapnp_device_id id_table[] __initdata = { + { ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('P', 'N', 'P'), + ISAPNP_FUNCTION(0x0e00), (unsigned long) "Intel 82365-Compatible" }, + { ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('P', 'N', 'P'), + ISAPNP_FUNCTION(0x0e01), (unsigned long) "Cirrus Logic CL-PD6720" }, + { ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('P', 'N', 'P'), + ISAPNP_FUNCTION(0x0e02), (unsigned long) "VLSI VL82C146" }, + { 0 } +}; +MODULE_DEVICE_TABLE(isapnp, id_table); + +static struct pnp_dev *i82365_pnpdev; +#endif + +static void __init isa_probe(void) +{ + int i, j, sock, k, ns, id; + kio_addr_t port; +#ifdef CONFIG_PNP + struct isapnp_device_id *devid; + struct pnp_dev *dev; + + for (devid = id_table; devid->vendor; devid++) { + if ((dev = pnp_find_dev(NULL, devid->vendor, devid->function, NULL))) { + + if (pnp_device_attach(dev) < 0) + continue; + + if (pnp_activate_dev(dev) < 0) { + printk("activate failed\n"); + pnp_device_detach(dev); + break; + } + + if (!pnp_port_valid(dev, 0)) { + printk("invalid resources ?\n"); + pnp_device_detach(dev); + break; + } + i365_base = pnp_port_start(dev, 0); + i82365_pnpdev = dev; + break; + } + } +#endif + + if (check_region(i365_base, 2) != 0) { + if (sockets == 0) + printk("port conflict at %#lx\n", i365_base); + return; + } + + id = identify(i365_base, 0); + if ((id == IS_I82365DF) && (identify(i365_base, 1) != id)) { + for (i = 0; i < 4; i++) { + if (i == ignore) continue; + port = i365_base + ((i & 1) << 2) + ((i & 2) << 1); + sock = (i & 1) << 1; + if (identify(port, sock) == IS_I82365DF) { + add_socket(port, sock, IS_VLSI); + add_pcic(1, IS_VLSI); + } + } + } else { + for (i = 0; i < 8; i += 2) { + if (sockets && !extra_sockets && (i == 4)) + break; + port = i365_base + 2*(i>>2); + sock = (i & 3); + id = identify(port, sock); + if (id < 0) continue; + + for (j = ns = 0; j < 2; j++) { + /* Does the socket exist? */ + if ((ignore == i+j) || (identify(port, sock+j) < 0)) + continue; + /* Check for bad socket decode */ + for (k = 0; k <= sockets; k++) + i365_set(k, I365_MEM(0)+I365_W_OFF, k); + for (k = 0; k <= sockets; k++) + if (i365_get(k, I365_MEM(0)+I365_W_OFF) != k) + break; + if (k <= sockets) break; + add_socket(port, sock+j, id); ns++; + } + if (ns != 0) add_pcic(ns, id); + } + } +} + +/*====================================================================*/ + +static irqreturn_t pcic_interrupt(int irq, void *dev, + struct pt_regs *regs) +{ + int i, j, csc; + u_int events, active; + u_long flags = 0; + int handled = 0; + + debug(4, "pcic_interrupt(%d)\n", irq); + + for (j = 0; j < 20; j++) { + active = 0; + for (i = 0; i < sockets; i++) { + if (socket[i].cs_irq != irq) + continue; + handled = 1; + ISA_LOCK(i, flags); + csc = i365_get(i, I365_CSC); + if ((csc == 0) || (i365_get(i, I365_IDENT) & 0x70)) { + ISA_UNLOCK(i, flags); + continue; + } + events = (csc & I365_CSC_DETECT) ? SS_DETECT : 0; + + if (i365_get(i, I365_INTCTL) & I365_PC_IOCARD) + events |= (csc & I365_CSC_STSCHG) ? SS_STSCHG : 0; + else { + events |= (csc & I365_CSC_BVD1) ? SS_BATDEAD : 0; + events |= (csc & I365_CSC_BVD2) ? SS_BATWARN : 0; + events |= (csc & I365_CSC_READY) ? SS_READY : 0; + } + ISA_UNLOCK(i, flags); + debug(2, "socket %d event 0x%02x\n", i, events); + + if (events) + pcmcia_parse_events(&socket[i].socket, events); + + active |= events; + } + if (!active) break; + } + if (j == 20) + printk(KERN_NOTICE "i82365: infinite loop in interrupt handler\n"); + + debug(4, "interrupt done\n"); + return IRQ_RETVAL(handled); +} /* pcic_interrupt */ + +static void pcic_interrupt_wrapper(u_long data) +{ + pcic_interrupt(0, NULL, NULL); + poll_timer.expires = jiffies + poll_interval; + add_timer(&poll_timer); +} + +/*====================================================================*/ + +static int i365_get_status(u_short sock, u_int *value) +{ + u_int status; + + status = i365_get(sock, I365_STATUS); + *value = ((status & I365_CS_DETECT) == I365_CS_DETECT) + ? SS_DETECT : 0; + + if (i365_get(sock, I365_INTCTL) & I365_PC_IOCARD) + *value |= (status & I365_CS_STSCHG) ? 0 : SS_STSCHG; + else { + *value |= (status & I365_CS_BVD1) ? 0 : SS_BATDEAD; + *value |= (status & I365_CS_BVD2) ? 0 : SS_BATWARN; + } + *value |= (status & I365_CS_WRPROT) ? SS_WRPROT : 0; + *value |= (status & I365_CS_READY) ? SS_READY : 0; + *value |= (status & I365_CS_POWERON) ? SS_POWERON : 0; + + if (socket[sock].type == IS_VG469) { + status = i365_get(sock, VG469_VSENSE); + if (socket[sock].psock & 1) { + *value |= (status & VG469_VSENSE_B_VS1) ? 0 : SS_3VCARD; + *value |= (status & VG469_VSENSE_B_VS2) ? 0 : SS_XVCARD; + } else { + *value |= (status & VG469_VSENSE_A_VS1) ? 0 : SS_3VCARD; + *value |= (status & VG469_VSENSE_A_VS2) ? 0 : SS_XVCARD; + } + } + + debug(1, "GetStatus(%d) = %#4.4x\n", sock, *value); + return 0; +} /* i365_get_status */ + +/*====================================================================*/ + +static int i365_get_socket(u_short sock, socket_state_t *state) +{ + struct i82365_socket *t = &socket[sock]; + u_char reg, vcc, vpp; + + reg = i365_get(sock, I365_POWER); + state->flags = (reg & I365_PWR_AUTO) ? SS_PWR_AUTO : 0; + state->flags |= (reg & I365_PWR_OUT) ? SS_OUTPUT_ENA : 0; + vcc = reg & I365_VCC_MASK; vpp = reg & I365_VPP1_MASK; + state->Vcc = state->Vpp = 0; + if (t->flags & IS_CIRRUS) { + if (i365_get(sock, PD67_MISC_CTL_1) & PD67_MC1_VCC_3V) { + if (reg & I365_VCC_5V) state->Vcc = 33; + if (vpp == I365_VPP1_5V) state->Vpp = 33; + } else { + if (reg & I365_VCC_5V) state->Vcc = 50; + if (vpp == I365_VPP1_5V) state->Vpp = 50; + } + if (vpp == I365_VPP1_12V) state->Vpp = 120; + } else if (t->flags & IS_VG_PWR) { + if (i365_get(sock, VG469_VSELECT) & VG469_VSEL_VCC) { + if (reg & I365_VCC_5V) state->Vcc = 33; + if (vpp == I365_VPP1_5V) state->Vpp = 33; + } else { + if (reg & I365_VCC_5V) state->Vcc = 50; + if (vpp == I365_VPP1_5V) state->Vpp = 50; + } + if (vpp == I365_VPP1_12V) state->Vpp = 120; + } else if (t->flags & IS_DF_PWR) { + if (vcc == I365_VCC_3V) state->Vcc = 33; + if (vcc == I365_VCC_5V) state->Vcc = 50; + if (vpp == I365_VPP1_5V) state->Vpp = 50; + if (vpp == I365_VPP1_12V) state->Vpp = 120; + } else { + if (reg & I365_VCC_5V) { + state->Vcc = 50; + if (vpp == I365_VPP1_5V) state->Vpp = 50; + if (vpp == I365_VPP1_12V) state->Vpp = 120; + } + } + + /* IO card, RESET flags, IO interrupt */ + reg = i365_get(sock, I365_INTCTL); + state->flags |= (reg & I365_PC_RESET) ? 0 : SS_RESET; + if (reg & I365_PC_IOCARD) state->flags |= SS_IOCARD; + state->io_irq = reg & I365_IRQ_MASK; + + /* speaker control */ + if (t->flags & IS_CIRRUS) { + if (i365_get(sock, PD67_MISC_CTL_1) & PD67_MC1_SPKR_ENA) + state->flags |= SS_SPKR_ENA; + } + + /* Card status change mask */ + reg = i365_get(sock, I365_CSCINT); + state->csc_mask = (reg & I365_CSC_DETECT) ? SS_DETECT : 0; + if (state->flags & SS_IOCARD) + state->csc_mask |= (reg & I365_CSC_STSCHG) ? SS_STSCHG : 0; + else { + state->csc_mask |= (reg & I365_CSC_BVD1) ? SS_BATDEAD : 0; + state->csc_mask |= (reg & I365_CSC_BVD2) ? SS_BATWARN : 0; + state->csc_mask |= (reg & I365_CSC_READY) ? SS_READY : 0; + } + + debug(1, "GetSocket(%d) = flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x\n", sock, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + return 0; +} /* i365_get_socket */ + +/*====================================================================*/ + +static int i365_set_socket(u_short sock, socket_state_t *state) +{ + struct i82365_socket *t = &socket[sock]; + u_char reg; + + debug(1, "SetSocket(%d, flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x)\n", sock, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + + /* First set global controller options */ + set_bridge_state(sock); + + /* IO card, RESET flag, IO interrupt */ + reg = t->intr; + reg |= state->io_irq; + reg |= (state->flags & SS_RESET) ? 0 : I365_PC_RESET; + reg |= (state->flags & SS_IOCARD) ? I365_PC_IOCARD : 0; + i365_set(sock, I365_INTCTL, reg); + + reg = I365_PWR_NORESET; + if (state->flags & SS_PWR_AUTO) reg |= I365_PWR_AUTO; + if (state->flags & SS_OUTPUT_ENA) reg |= I365_PWR_OUT; + + if (t->flags & IS_CIRRUS) { + if (state->Vpp != 0) { + if (state->Vpp == 120) + reg |= I365_VPP1_12V; + else if (state->Vpp == state->Vcc) + reg |= I365_VPP1_5V; + else return -EINVAL; + } + if (state->Vcc != 0) { + reg |= I365_VCC_5V; + if (state->Vcc == 33) + i365_bset(sock, PD67_MISC_CTL_1, PD67_MC1_VCC_3V); + else if (state->Vcc == 50) + i365_bclr(sock, PD67_MISC_CTL_1, PD67_MC1_VCC_3V); + else return -EINVAL; + } + } else if (t->flags & IS_VG_PWR) { + if (state->Vpp != 0) { + if (state->Vpp == 120) + reg |= I365_VPP1_12V; + else if (state->Vpp == state->Vcc) + reg |= I365_VPP1_5V; + else return -EINVAL; + } + if (state->Vcc != 0) { + reg |= I365_VCC_5V; + if (state->Vcc == 33) + i365_bset(sock, VG469_VSELECT, VG469_VSEL_VCC); + else if (state->Vcc == 50) + i365_bclr(sock, VG469_VSELECT, VG469_VSEL_VCC); + else return -EINVAL; + } + } else if (t->flags & IS_DF_PWR) { + switch (state->Vcc) { + case 0: break; + case 33: reg |= I365_VCC_3V; break; + case 50: reg |= I365_VCC_5V; break; + default: return -EINVAL; + } + switch (state->Vpp) { + case 0: break; + case 50: reg |= I365_VPP1_5V; break; + case 120: reg |= I365_VPP1_12V; break; + default: return -EINVAL; + } + } else { + switch (state->Vcc) { + case 0: break; + case 50: reg |= I365_VCC_5V; break; + default: return -EINVAL; + } + switch (state->Vpp) { + case 0: break; + case 50: reg |= I365_VPP1_5V | I365_VPP2_5V; break; + case 120: reg |= I365_VPP1_12V | I365_VPP2_12V; break; + default: return -EINVAL; + } + } + + if (reg != i365_get(sock, I365_POWER)) + i365_set(sock, I365_POWER, reg); + + /* Chipset-specific functions */ + if (t->flags & IS_CIRRUS) { + /* Speaker control */ + i365_bflip(sock, PD67_MISC_CTL_1, PD67_MC1_SPKR_ENA, + state->flags & SS_SPKR_ENA); + } + + /* Card status change interrupt mask */ + reg = t->cs_irq << 4; + if (state->csc_mask & SS_DETECT) reg |= I365_CSC_DETECT; + if (state->flags & SS_IOCARD) { + if (state->csc_mask & SS_STSCHG) reg |= I365_CSC_STSCHG; + } else { + if (state->csc_mask & SS_BATDEAD) reg |= I365_CSC_BVD1; + if (state->csc_mask & SS_BATWARN) reg |= I365_CSC_BVD2; + if (state->csc_mask & SS_READY) reg |= I365_CSC_READY; + } + i365_set(sock, I365_CSCINT, reg); + i365_get(sock, I365_CSC); + + return 0; +} /* i365_set_socket */ + +/*====================================================================*/ + +static int i365_set_io_map(u_short sock, struct pccard_io_map *io) +{ + u_char map, ioctl; + + debug(1, "SetIOMap(%d, %d, %#2.2x, %d ns, " + "%#lx-%#lx)\n", sock, io->map, io->flags, + io->speed, io->start, io->stop); + map = io->map; + if ((map > 1) || (io->start > 0xffff) || (io->stop > 0xffff) || + (io->stop < io->start)) return -EINVAL; + /* Turn off the window before changing anything */ + if (i365_get(sock, I365_ADDRWIN) & I365_ENA_IO(map)) + i365_bclr(sock, I365_ADDRWIN, I365_ENA_IO(map)); + i365_set_pair(sock, I365_IO(map)+I365_W_START, io->start); + i365_set_pair(sock, I365_IO(map)+I365_W_STOP, io->stop); + ioctl = i365_get(sock, I365_IOCTL) & ~I365_IOCTL_MASK(map); + if (io->speed) ioctl |= I365_IOCTL_WAIT(map); + if (io->flags & MAP_0WS) ioctl |= I365_IOCTL_0WS(map); + if (io->flags & MAP_16BIT) ioctl |= I365_IOCTL_16BIT(map); + if (io->flags & MAP_AUTOSZ) ioctl |= I365_IOCTL_IOCS16(map); + i365_set(sock, I365_IOCTL, ioctl); + /* Turn on the window if necessary */ + if (io->flags & MAP_ACTIVE) + i365_bset(sock, I365_ADDRWIN, I365_ENA_IO(map)); + return 0; +} /* i365_set_io_map */ + +/*====================================================================*/ + +static int i365_set_mem_map(u_short sock, struct pccard_mem_map *mem) +{ + u_short base, i; + u_char map; + + debug(1, "SetMemMap(%d, %d, %#2.2x, %d ns, %#lx-%#lx, " + "%#x)\n", sock, mem->map, mem->flags, mem->speed, + mem->res->start, mem->res->end, mem->card_start); + + map = mem->map; + if ((map > 4) || (mem->card_start > 0x3ffffff) || + (mem->res->start > mem->res->end) || (mem->speed > 1000)) + return -EINVAL; + if ((mem->res->start > 0xffffff) || (mem->res->end > 0xffffff)) + return -EINVAL; + + /* Turn off the window before changing anything */ + if (i365_get(sock, I365_ADDRWIN) & I365_ENA_MEM(map)) + i365_bclr(sock, I365_ADDRWIN, I365_ENA_MEM(map)); + + base = I365_MEM(map); + i = (mem->res->start >> 12) & 0x0fff; + if (mem->flags & MAP_16BIT) i |= I365_MEM_16BIT; + if (mem->flags & MAP_0WS) i |= I365_MEM_0WS; + i365_set_pair(sock, base+I365_W_START, i); + + i = (mem->res->end >> 12) & 0x0fff; + switch (to_cycles(mem->speed)) { + case 0: break; + case 1: i |= I365_MEM_WS0; break; + case 2: i |= I365_MEM_WS1; break; + default: i |= I365_MEM_WS1 | I365_MEM_WS0; break; + } + i365_set_pair(sock, base+I365_W_STOP, i); + + i = ((mem->card_start - mem->res->start) >> 12) & 0x3fff; + if (mem->flags & MAP_WRPROT) i |= I365_MEM_WRPROT; + if (mem->flags & MAP_ATTRIB) i |= I365_MEM_REG; + i365_set_pair(sock, base+I365_W_OFF, i); + + /* Turn on the window if necessary */ + if (mem->flags & MAP_ACTIVE) + i365_bset(sock, I365_ADDRWIN, I365_ENA_MEM(map)); + return 0; +} /* i365_set_mem_map */ + +#if 0 /* driver model ordering issue */ +/*====================================================================== + + Routines for accessing socket information and register dumps via + /sys/class/pcmcia_socket/... + +======================================================================*/ + +static ssize_t show_info(struct class_device *class_dev, char *buf) +{ + struct i82365_socket *s = container_of(class_dev, struct i82365_socket, socket.dev); + return sprintf(buf, "type: %s\npsock: %d\n", + pcic[s->type].name, s->psock); +} + +static ssize_t show_exca(struct class_device *class_dev, char *buf) +{ + struct i82365_socket *s = container_of(class_dev, struct i82365_socket, socket.dev); + unsigned short sock; + int i; + ssize_t ret = 0; + unsigned long flags = 0; + + sock = s->number; + + ISA_LOCK(sock, flags); + for (i = 0; i < 0x40; i += 4) { + ret += sprintf(buf, "%02x %02x %02x %02x%s", + i365_get(sock,i), i365_get(sock,i+1), + i365_get(sock,i+2), i365_get(sock,i+3), + ((i % 16) == 12) ? "\n" : " "); + buf += ret; + } + ISA_UNLOCK(sock, flags); + + return ret; +} + +static CLASS_DEVICE_ATTR(exca, S_IRUGO, show_exca, NULL); +static CLASS_DEVICE_ATTR(info, S_IRUGO, show_info, NULL); +#endif + +/*====================================================================*/ + +/* this is horribly ugly... proper locking needs to be done here at + * some time... */ +#define LOCKED(x) do { \ + int retval; \ + unsigned long flags; \ + spin_lock_irqsave(&isa_lock, flags); \ + retval = x; \ + spin_unlock_irqrestore(&isa_lock, flags); \ + return retval; \ +} while (0) + + +static int pcic_get_status(struct pcmcia_socket *s, u_int *value) +{ + unsigned int sock = container_of(s, struct i82365_socket, socket)->number; + + if (socket[sock].flags & IS_ALIVE) { + *value = 0; + return -EINVAL; + } + + LOCKED(i365_get_status(sock, value)); +} + +static int pcic_get_socket(struct pcmcia_socket *s, socket_state_t *state) +{ + unsigned int sock = container_of(s, struct i82365_socket, socket)->number; + + if (socket[sock].flags & IS_ALIVE) + return -EINVAL; + + LOCKED(i365_get_socket(sock, state)); +} + +static int pcic_set_socket(struct pcmcia_socket *s, socket_state_t *state) +{ + unsigned int sock = container_of(s, struct i82365_socket, socket)->number; + + if (socket[sock].flags & IS_ALIVE) + return -EINVAL; + + LOCKED(i365_set_socket(sock, state)); +} + +static int pcic_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io) +{ + unsigned int sock = container_of(s, struct i82365_socket, socket)->number; + if (socket[sock].flags & IS_ALIVE) + return -EINVAL; + + LOCKED(i365_set_io_map(sock, io)); +} + +static int pcic_set_mem_map(struct pcmcia_socket *s, struct pccard_mem_map *mem) +{ + unsigned int sock = container_of(s, struct i82365_socket, socket)->number; + if (socket[sock].flags & IS_ALIVE) + return -EINVAL; + + LOCKED(i365_set_mem_map(sock, mem)); +} + +static int pcic_init(struct pcmcia_socket *s) +{ + int i; + struct resource res = { .start = 0, .end = 0x1000 }; + pccard_io_map io = { 0, 0, 0, 0, 1 }; + pccard_mem_map mem = { .res = &res, }; + + for (i = 0; i < 2; i++) { + io.map = i; + pcic_set_io_map(s, &io); + } + for (i = 0; i < 5; i++) { + mem.map = i; + pcic_set_mem_map(s, &mem); + } + return 0; +} + +static struct pccard_operations pcic_operations = { + .init = pcic_init, + .get_status = pcic_get_status, + .get_socket = pcic_get_socket, + .set_socket = pcic_set_socket, + .set_io_map = pcic_set_io_map, + .set_mem_map = pcic_set_mem_map, +}; + +/*====================================================================*/ + +static int i82365_suspend(struct device *dev, pm_message_t state, u32 level) +{ + int ret = 0; + if (level == SUSPEND_SAVE_STATE) + ret = pcmcia_socket_dev_suspend(dev, state); + return ret; +} + +static int i82365_resume(struct device *dev, u32 level) +{ + int ret = 0; + if (level == RESUME_RESTORE_STATE) + ret = pcmcia_socket_dev_resume(dev); + return ret; +} + +static struct device_driver i82365_driver = { + .name = "i82365", + .bus = &platform_bus_type, + .suspend = i82365_suspend, + .resume = i82365_resume, +}; + +static struct platform_device i82365_device = { + .name = "i82365", + .id = 0, +}; + +static int __init init_i82365(void) +{ + int i, ret; + + ret = driver_register(&i82365_driver); + if (ret) + return ret; + + ret = platform_device_register(&i82365_device); + if (ret) { + driver_unregister(&i82365_driver); + return ret; + } + + printk(KERN_INFO "Intel ISA PCIC probe: "); + sockets = 0; + + isa_probe(); + + if (sockets == 0) { + printk("not found.\n"); + platform_device_unregister(&i82365_device); + driver_unregister(&i82365_driver); + return -ENODEV; + } + + /* Set up interrupt handler(s) */ + if (grab_irq != 0) + request_irq(cs_irq, pcic_interrupt, 0, "i82365", pcic_interrupt); + + /* register sockets with the pcmcia core */ + for (i = 0; i < sockets; i++) { + socket[i].socket.dev.dev = &i82365_device.dev; + socket[i].socket.ops = &pcic_operations; + socket[i].socket.resource_ops = &pccard_nonstatic_ops; + socket[i].socket.owner = THIS_MODULE; + socket[i].number = i; + ret = pcmcia_register_socket(&socket[i].socket); + if (!ret) + socket[i].flags |= IS_REGISTERED; + +#if 0 /* driver model ordering issue */ + class_device_create_file(&socket[i].socket.dev, + &class_device_attr_info); + class_device_create_file(&socket[i].socket.dev, + &class_device_attr_exca); +#endif + } + + /* Finally, schedule a polling interrupt */ + if (poll_interval != 0) { + poll_timer.function = pcic_interrupt_wrapper; + poll_timer.data = 0; + init_timer(&poll_timer); + poll_timer.expires = jiffies + poll_interval; + add_timer(&poll_timer); + } + + return 0; + +} /* init_i82365 */ + +static void __exit exit_i82365(void) +{ + int i; + + for (i = 0; i < sockets; i++) { + if (socket[i].flags & IS_REGISTERED) + pcmcia_unregister_socket(&socket[i].socket); + } + platform_device_unregister(&i82365_device); + if (poll_interval != 0) + del_timer_sync(&poll_timer); + if (grab_irq != 0) + free_irq(cs_irq, pcic_interrupt); + for (i = 0; i < sockets; i++) { + /* Turn off all interrupt sources! */ + i365_set(i, I365_CSCINT, 0); + release_region(socket[i].ioaddr, 2); + } +#ifdef CONFIG_PNP + if (i82365_pnpdev) + pnp_disable_dev(i82365_pnpdev); +#endif + driver_unregister(&i82365_driver); +} /* exit_i82365 */ + +module_init(init_i82365); +module_exit(exit_i82365); +MODULE_LICENSE("Dual MPL/GPL"); +/*====================================================================*/ diff --git a/drivers/pcmcia/i82365.h b/drivers/pcmcia/i82365.h new file mode 100644 index 000000000000..622860c689d9 --- /dev/null +++ b/drivers/pcmcia/i82365.h @@ -0,0 +1,135 @@ +/* + * i82365.h 1.15 1999/10/25 20:03:34 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in which + * case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_I82365_H +#define _LINUX_I82365_H + +/* register definitions for the Intel 82365SL PCMCIA controller */ + +/* Offsets for PCIC registers */ +#define I365_IDENT 0x00 /* Identification and revision */ +#define I365_STATUS 0x01 /* Interface status */ +#define I365_POWER 0x02 /* Power and RESETDRV control */ +#define I365_INTCTL 0x03 /* Interrupt and general control */ +#define I365_CSC 0x04 /* Card status change */ +#define I365_CSCINT 0x05 /* Card status change interrupt control */ +#define I365_ADDRWIN 0x06 /* Address window enable */ +#define I365_IOCTL 0x07 /* I/O control */ +#define I365_GENCTL 0x16 /* Card detect and general control */ +#define I365_GBLCTL 0x1E /* Global control register */ + +/* Offsets for I/O and memory window registers */ +#define I365_IO(map) (0x08+((map)<<2)) +#define I365_MEM(map) (0x10+((map)<<3)) +#define I365_W_START 0 +#define I365_W_STOP 2 +#define I365_W_OFF 4 + +/* Flags for I365_STATUS */ +#define I365_CS_BVD1 0x01 +#define I365_CS_STSCHG 0x01 +#define I365_CS_BVD2 0x02 +#define I365_CS_SPKR 0x02 +#define I365_CS_DETECT 0x0C +#define I365_CS_WRPROT 0x10 +#define I365_CS_READY 0x20 /* Inverted */ +#define I365_CS_POWERON 0x40 +#define I365_CS_GPI 0x80 + +/* Flags for I365_POWER */ +#define I365_PWR_OFF 0x00 /* Turn off the socket */ +#define I365_PWR_OUT 0x80 /* Output enable */ +#define I365_PWR_NORESET 0x40 /* Disable RESETDRV on resume */ +#define I365_PWR_AUTO 0x20 /* Auto pwr switch enable */ +#define I365_VCC_MASK 0x18 /* Mask for turning off Vcc */ +/* There are different layouts for B-step and DF-step chips: the B + step has independent Vpp1/Vpp2 control, and the DF step has only + Vpp1 control, plus 3V control */ +#define I365_VCC_5V 0x10 /* Vcc = 5.0v */ +#define I365_VCC_3V 0x18 /* Vcc = 3.3v */ +#define I365_VPP2_MASK 0x0c /* Mask for turning off Vpp2 */ +#define I365_VPP2_5V 0x04 /* Vpp2 = 5.0v */ +#define I365_VPP2_12V 0x08 /* Vpp2 = 12.0v */ +#define I365_VPP1_MASK 0x03 /* Mask for turning off Vpp1 */ +#define I365_VPP1_5V 0x01 /* Vpp2 = 5.0v */ +#define I365_VPP1_12V 0x02 /* Vpp2 = 12.0v */ + +/* Flags for I365_INTCTL */ +#define I365_RING_ENA 0x80 +#define I365_PC_RESET 0x40 +#define I365_PC_IOCARD 0x20 +#define I365_INTR_ENA 0x10 +#define I365_IRQ_MASK 0x0F + +/* Flags for I365_CSC and I365_CSCINT*/ +#define I365_CSC_BVD1 0x01 +#define I365_CSC_STSCHG 0x01 +#define I365_CSC_BVD2 0x02 +#define I365_CSC_READY 0x04 +#define I365_CSC_DETECT 0x08 +#define I365_CSC_ANY 0x0F +#define I365_CSC_GPI 0x10 + +/* Flags for I365_ADDRWIN */ +#define I365_ENA_IO(map) (0x40 << (map)) +#define I365_ENA_MEM(map) (0x01 << (map)) + +/* Flags for I365_IOCTL */ +#define I365_IOCTL_MASK(map) (0x0F << (map<<2)) +#define I365_IOCTL_WAIT(map) (0x08 << (map<<2)) +#define I365_IOCTL_0WS(map) (0x04 << (map<<2)) +#define I365_IOCTL_IOCS16(map) (0x02 << (map<<2)) +#define I365_IOCTL_16BIT(map) (0x01 << (map<<2)) + +/* Flags for I365_GENCTL */ +#define I365_CTL_16DELAY 0x01 +#define I365_CTL_RESET 0x02 +#define I365_CTL_GPI_ENA 0x04 +#define I365_CTL_GPI_CTL 0x08 +#define I365_CTL_RESUME 0x10 +#define I365_CTL_SW_IRQ 0x20 + +/* Flags for I365_GBLCTL */ +#define I365_GBL_PWRDOWN 0x01 +#define I365_GBL_CSC_LEV 0x02 +#define I365_GBL_WRBACK 0x04 +#define I365_GBL_IRQ_0_LEV 0x08 +#define I365_GBL_IRQ_1_LEV 0x10 + +/* Flags for memory window registers */ +#define I365_MEM_16BIT 0x8000 /* In memory start high byte */ +#define I365_MEM_0WS 0x4000 +#define I365_MEM_WS1 0x8000 /* In memory stop high byte */ +#define I365_MEM_WS0 0x4000 +#define I365_MEM_WRPROT 0x8000 /* In offset high byte */ +#define I365_MEM_REG 0x4000 + +#define I365_REG(slot, reg) (((slot) << 6) + reg) + +#endif /* _LINUX_I82365_H */ diff --git a/drivers/pcmcia/m32r_cfc.c b/drivers/pcmcia/m32r_cfc.c new file mode 100644 index 000000000000..b0b7a7a41120 --- /dev/null +++ b/drivers/pcmcia/m32r_cfc.c @@ -0,0 +1,873 @@ +/* + * drivers/pcmcia/m32r_cfc.c + * + * Device driver for the CFC functionality of M32R. + * + * Copyright (c) 2001, 2002, 2003, 2004 + * Hiroyuki Kondo, Naoto Sugai, Hayato Fujiwara + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/bitops.h> +#include <asm/system.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> + +#undef MAX_IO_WIN /* FIXME */ +#define MAX_IO_WIN 1 +#undef MAX_WIN /* FIXME */ +#define MAX_WIN 1 + +#include "m32r_cfc.h" + +#ifdef DEBUG +static int m32r_cfc_debug; +module_param(m32r_cfc_debug, int, 0644); +#define debug(lvl, fmt, arg...) do { \ + if (m32r_cfc_debug > (lvl)) \ + printk(KERN_DEBUG "m32r_cfc: " fmt , ## arg); \ +} while (0) +#else +#define debug(n, args...) do { } while (0) +#endif + +/* Poll status interval -- 0 means default to interrupt */ +static int poll_interval = 0; + +typedef enum pcc_space { as_none = 0, as_comm, as_attr, as_io } pcc_as_t; + +typedef struct pcc_socket { + u_short type, flags; + struct pcmcia_socket socket; + unsigned int number; + kio_addr_t ioaddr; + u_long mapaddr; + u_long base; /* PCC register base */ + u_char cs_irq1, cs_irq2, intr; + pccard_io_map io_map[MAX_IO_WIN]; + pccard_mem_map mem_map[MAX_WIN]; + u_char io_win; + u_char mem_win; + pcc_as_t current_space; + u_char last_iodbex; +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *proc; +#endif +} pcc_socket_t; + +static int pcc_sockets = 0; +static pcc_socket_t socket[M32R_MAX_PCC] = { + { 0, }, /* ... */ +}; + +/*====================================================================*/ + +static unsigned int pcc_get(u_short, unsigned int); +static void pcc_set(u_short, unsigned int , unsigned int ); + +static DEFINE_SPINLOCK(pcc_lock); + +#if !defined(CONFIG_PLAT_USRV) +static inline u_long pcc_port2addr(unsigned long port, int size) { + u_long addr = 0; + u_long odd; + + if (size == 1) { /* byte access */ + odd = (port&1) << 11; + port -= port & 1; + addr = CFC_IO_MAPBASE_BYTE - CFC_IOPORT_BASE + odd + port; + } else if (size == 2) + addr = CFC_IO_MAPBASE_WORD - CFC_IOPORT_BASE + port; + + return addr; +} +#else /* CONFIG_PLAT_USRV */ +static inline u_long pcc_port2addr(unsigned long port, int size) { + u_long odd; + u_long addr = ((port - CFC_IOPORT_BASE) & 0xf000) << 8; + + if (size == 1) { /* byte access */ + odd = port & 1; + port -= odd; + odd <<= 11; + addr = (addr | CFC_IO_MAPBASE_BYTE) + odd + (port & 0xfff); + } else if (size == 2) /* word access */ + addr = (addr | CFC_IO_MAPBASE_WORD) + (port & 0xfff); + + return addr; +} +#endif /* CONFIG_PLAT_USRV */ + +void pcc_ioread_byte(int sock, unsigned long port, void *buf, size_t size, + size_t nmemb, int flag) +{ + u_long addr; + unsigned char *bp = (unsigned char *)buf; + unsigned long flags; + + debug(3, "m32r_cfc: pcc_ioread_byte: sock=%d, port=%#lx, buf=%p, " + "size=%u, nmemb=%d, flag=%d\n", + sock, port, buf, size, nmemb, flag); + + addr = pcc_port2addr(port, 1); + if (!addr) { + printk("m32r_cfc:ioread_byte null port :%#lx\n",port); + return; + } + debug(3, "m32r_cfc: pcc_ioread_byte: addr=%#lx\n", addr); + + spin_lock_irqsave(&pcc_lock, flags); + /* read Byte */ + while (nmemb--) + *bp++ = readb(addr); + spin_unlock_irqrestore(&pcc_lock, flags); +} + +void pcc_ioread_word(int sock, unsigned long port, void *buf, size_t size, + size_t nmemb, int flag) +{ + u_long addr; + unsigned short *bp = (unsigned short *)buf; + unsigned long flags; + + debug(3, "m32r_cfc: pcc_ioread_word: sock=%d, port=%#lx, " + "buf=%p, size=%u, nmemb=%d, flag=%d\n", + sock, port, buf, size, nmemb, flag); + + if (size != 2) + printk("m32r_cfc: ioread_word :illigal size %u : %#lx\n", size, + port); + if (size == 9) + printk("m32r_cfc: ioread_word :insw \n"); + + addr = pcc_port2addr(port, 2); + if (!addr) { + printk("m32r_cfc:ioread_word null port :%#lx\n",port); + return; + } + debug(3, "m32r_cfc: pcc_ioread_word: addr=%#lx\n", addr); + + spin_lock_irqsave(&pcc_lock, flags); + /* read Word */ + while (nmemb--) + *bp++ = readw(addr); + spin_unlock_irqrestore(&pcc_lock, flags); +} + +void pcc_iowrite_byte(int sock, unsigned long port, void *buf, size_t size, + size_t nmemb, int flag) +{ + u_long addr; + unsigned char *bp = (unsigned char *)buf; + unsigned long flags; + + debug(3, "m32r_cfc: pcc_iowrite_byte: sock=%d, port=%#lx, " + "buf=%p, size=%u, nmemb=%d, flag=%d\n", + sock, port, buf, size, nmemb, flag); + + /* write Byte */ + addr = pcc_port2addr(port, 1); + if (!addr) { + printk("m32r_cfc:iowrite_byte null port:%#lx\n",port); + return; + } + debug(3, "m32r_cfc: pcc_iowrite_byte: addr=%#lx\n", addr); + + spin_lock_irqsave(&pcc_lock, flags); + while (nmemb--) + writeb(*bp++, addr); + spin_unlock_irqrestore(&pcc_lock, flags); +} + +void pcc_iowrite_word(int sock, unsigned long port, void *buf, size_t size, + size_t nmemb, int flag) +{ + u_long addr; + unsigned short *bp = (unsigned short *)buf; + unsigned long flags; + + debug(3, "m32r_cfc: pcc_iowrite_word: sock=%d, port=%#lx, " + "buf=%p, size=%u, nmemb=%d, flag=%d\n", + sock, port, buf, size, nmemb, flag); + + if(size != 2) + printk("m32r_cfc: iowrite_word :illigal size %u : %#lx\n", + size, port); + if(size == 9) + printk("m32r_cfc: iowrite_word :outsw \n"); + + addr = pcc_port2addr(port, 2); + if (!addr) { + printk("m32r_cfc:iowrite_word null addr :%#lx\n",port); + return; + } +#if 1 + if (addr & 1) { + printk("m32r_cfc:iowrite_word port addr (%#lx):%#lx\n", port, + addr); + return; + } +#endif + debug(3, "m32r_cfc: pcc_iowrite_word: addr=%#lx\n", addr); + + spin_lock_irqsave(&pcc_lock, flags); + while (nmemb--) + writew(*bp++, addr); + spin_unlock_irqrestore(&pcc_lock, flags); +} + +/*====================================================================*/ + +#define IS_REGISTERED 0x2000 +#define IS_ALIVE 0x8000 + +typedef struct pcc_t { + char *name; + u_short flags; +} pcc_t; + +static pcc_t pcc[] = { +#if !defined(CONFIG_PLAT_USRV) + { "m32r_cfc", 0 }, { "", 0 }, +#else /* CONFIG_PLAT_USRV */ + { "m32r_cfc", 0 }, { "m32r_cfc", 0 }, { "m32r_cfc", 0 }, + { "m32r_cfc", 0 }, { "m32r_cfc", 0 }, { "", 0 }, +#endif /* CONFIG_PLAT_USRV */ +}; + +static irqreturn_t pcc_interrupt(int, void *, struct pt_regs *); + +/*====================================================================*/ + +static struct timer_list poll_timer; + +static unsigned int pcc_get(u_short sock, unsigned int reg) +{ + unsigned int val = inw(reg); + debug(3, "m32r_cfc: pcc_get: reg(0x%08x)=0x%04x\n", reg, val); + return val; +} + + +static void pcc_set(u_short sock, unsigned int reg, unsigned int data) +{ + outw(data, reg); + debug(3, "m32r_cfc: pcc_set: reg(0x%08x)=0x%04x\n", reg, data); +} + +/*====================================================================== + + See if a card is present, powered up, in IO mode, and already + bound to a (non PC Card) Linux driver. We leave these alone. + + We make an exception for cards that seem to be serial devices. + +======================================================================*/ + +static int __init is_alive(u_short sock) +{ + unsigned int stat; + + debug(3, "m32r_cfc: is_alive:\n"); + + printk("CF: "); + stat = pcc_get(sock, (unsigned int)PLD_CFSTS); + if (!stat) + printk("No "); + printk("Card is detected at socket %d : stat = 0x%08x\n", sock, stat); + debug(3, "m32r_cfc: is_alive: sock stat is 0x%04x\n", stat); + + return 0; +} + +static void add_pcc_socket(ulong base, int irq, ulong mapaddr, kio_addr_t ioaddr) +{ + pcc_socket_t *t = &socket[pcc_sockets]; + + debug(3, "m32r_cfc: add_pcc_socket: base=%#lx, irq=%d, " + "mapaddr=%#lx, ioaddr=%08x\n", + base, irq, mapaddr, ioaddr); + + /* add sockets */ + t->ioaddr = ioaddr; + t->mapaddr = mapaddr; +#if !defined(CONFIG_PLAT_USRV) + t->base = 0; + t->flags = 0; + t->cs_irq1 = irq; // insert irq + t->cs_irq2 = irq + 1; // eject irq +#else /* CONFIG_PLAT_USRV */ + t->base = base; + t->flags = 0; + t->cs_irq1 = 0; // insert irq + t->cs_irq2 = 0; // eject irq +#endif /* CONFIG_PLAT_USRV */ + + if (is_alive(pcc_sockets)) + t->flags |= IS_ALIVE; + + /* add pcc */ +#if !defined(CONFIG_PLAT_USRV) + request_region((unsigned int)PLD_CFRSTCR, 0x20, "m32r_cfc"); +#else /* CONFIG_PLAT_USRV */ + { + unsigned int reg_base; + + reg_base = (unsigned int)PLD_CFRSTCR; + reg_base |= pcc_sockets << 8; + request_region(reg_base, 0x20, "m32r_cfc"); + } +#endif /* CONFIG_PLAT_USRV */ + printk(KERN_INFO " %s ", pcc[pcc_sockets].name); + printk("pcc at 0x%08lx\n", t->base); + + /* Update socket interrupt information, capabilities */ + t->socket.features |= (SS_CAP_PCCARD | SS_CAP_STATIC_MAP); + t->socket.map_size = M32R_PCC_MAPSIZE; + t->socket.io_offset = ioaddr; /* use for io access offset */ + t->socket.irq_mask = 0; +#if !defined(CONFIG_PLAT_USRV) + t->socket.pci_irq = PLD_IRQ_CFIREQ ; /* card interrupt */ +#else /* CONFIG_PLAT_USRV */ + t->socket.pci_irq = PLD_IRQ_CF0 + pcc_sockets; +#endif /* CONFIG_PLAT_USRV */ + +#ifndef CONFIG_PLAT_USRV + /* insert interrupt */ + request_irq(irq, pcc_interrupt, 0, "m32r_cfc", pcc_interrupt); + /* eject interrupt */ + request_irq(irq+1, pcc_interrupt, 0, "m32r_cfc", pcc_interrupt); + + debug(3, "m32r_cfc: enable CFMSK, RDYSEL\n"); + pcc_set(pcc_sockets, (unsigned int)PLD_CFIMASK, 0x01); +#endif /* CONFIG_PLAT_USRV */ +#if defined(CONFIG_PLAT_M32700UT) || defined(CONFIG_PLAT_USRV) || defined(CONFIG_PLAT_OPSPUT) + pcc_set(pcc_sockets, (unsigned int)PLD_CFCR1, 0x0200); +#endif + pcc_sockets++; + + return; +} + + +/*====================================================================*/ + +static irqreturn_t pcc_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + int i; + u_int events = 0; + int handled = 0; + + debug(3, "m32r_cfc: pcc_interrupt: irq=%d, dev=%p, regs=%p\n", + irq, dev, regs); + for (i = 0; i < pcc_sockets; i++) { + if (socket[i].cs_irq1 != irq && socket[i].cs_irq2 != irq) + continue; + + handled = 1; + debug(3, "m32r_cfc: pcc_interrupt: socket %d irq 0x%02x ", + i, irq); + events |= SS_DETECT; /* insert or eject */ + if (events) + pcmcia_parse_events(&socket[i].socket, events); + } + debug(3, "m32r_cfc: pcc_interrupt: done\n"); + + return IRQ_RETVAL(handled); +} /* pcc_interrupt */ + +static void pcc_interrupt_wrapper(u_long data) +{ + debug(3, "m32r_cfc: pcc_interrupt_wrapper:\n"); + pcc_interrupt(0, NULL, NULL); + init_timer(&poll_timer); + poll_timer.expires = jiffies + poll_interval; + add_timer(&poll_timer); +} + +/*====================================================================*/ + +static int _pcc_get_status(u_short sock, u_int *value) +{ + u_int status; + + debug(3, "m32r_cfc: _pcc_get_status:\n"); + status = pcc_get(sock, (unsigned int)PLD_CFSTS); + *value = (status) ? SS_DETECT : 0; + debug(3, "m32r_cfc: _pcc_get_status: status=0x%08x\n", status); + +#if defined(CONFIG_PLAT_M32700UT) || defined(CONFIG_PLAT_USRV) || defined(CONFIG_PLAT_OPSPUT) + if ( status ) { + /* enable CF power */ + status = inw((unsigned int)PLD_CPCR); + if (!(status & PLD_CPCR_CF)) { + debug(3, "m32r_cfc: _pcc_get_status: " + "power on (CPCR=0x%08x)\n", status); + status |= PLD_CPCR_CF; + outw(status, (unsigned int)PLD_CPCR); + udelay(100); + } + *value |= SS_POWERON; + + pcc_set(sock, (unsigned int)PLD_CFBUFCR,0);/* enable buffer */ + udelay(100); + + *value |= SS_READY; /* always ready */ + *value |= SS_3VCARD; + } else { + /* disable CF power */ + status = inw((unsigned int)PLD_CPCR); + status &= ~PLD_CPCR_CF; + outw(status, (unsigned int)PLD_CPCR); + udelay(100); + debug(3, "m32r_cfc: _pcc_get_status: " + "power off (CPCR=0x%08x)\n", status); + } +#elif defined(CONFIG_PLAT_MAPPI2) + if ( status ) { + status = pcc_get(sock, (unsigned int)PLD_CPCR); + if (status == 0) { /* power off */ + pcc_set(sock, (unsigned int)PLD_CPCR, 1); + pcc_set(sock, (unsigned int)PLD_CFBUFCR,0); /* force buffer off for ZA-36 */ + udelay(50); + } + status = pcc_get(sock, (unsigned int)PLD_CFBUFCR); + if (status != 0) { /* buffer off */ + pcc_set(sock, (unsigned int)PLD_CFBUFCR,0); + udelay(50); + pcc_set(sock, (unsigned int)PLD_CFRSTCR, 0x0101); + udelay(25); /* for IDE reset */ + pcc_set(sock, (unsigned int)PLD_CFRSTCR, 0x0100); + mdelay(2); /* for IDE reset */ + } else { + *value |= SS_POWERON; + *value |= SS_READY; + } + } +#else +#error no platform configuration +#endif + debug(3, "m32r_cfc: _pcc_get_status: GetStatus(%d) = %#4.4x\n", + sock, *value); + return 0; +} /* _get_status */ + +/*====================================================================*/ + +static int _pcc_get_socket(u_short sock, socket_state_t *state) +{ +// pcc_socket_t *t = &socket[sock]; + +#if defined(CONFIG_PLAT_M32700UT) || defined(CONFIG_PLAT_USRV) || defined(CONFIG_PLAT_OPSPUT) + state->flags = 0; + state->csc_mask = SS_DETECT; + state->csc_mask |= SS_READY; + state->io_irq = 0; + state->Vcc = 33; /* 3.3V fixed */ + state->Vpp = 33; +#endif + debug(3, "m32r_cfc: GetSocket(%d) = flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x\n", sock, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + return 0; +} /* _get_socket */ + +/*====================================================================*/ + +static int _pcc_set_socket(u_short sock, socket_state_t *state) +{ +#if defined(CONFIG_PLAT_MAPPI2) + u_long reg = 0; +#endif + debug(3, "m32r_cfc: SetSocket(%d, flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x)\n", sock, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + +#if defined(CONFIG_PLAT_M32700UT) || defined(CONFIG_PLAT_USRV) || defined(CONFIG_PLAT_OPSPUT) + if (state->Vcc) { + if ((state->Vcc != 50) && (state->Vcc != 33)) + return -EINVAL; + /* accept 5V and 3.3V */ + } +#elif defined(CONFIG_PLAT_MAPPI2) + if (state->Vcc) { + /* + * 5V only + */ + if (state->Vcc == 50) { + reg |= PCCSIGCR_VEN; + } else { + return -EINVAL; + } + } +#endif + + if (state->flags & SS_RESET) { + debug(3, ":RESET\n"); + pcc_set(sock,(unsigned int)PLD_CFRSTCR,0x101); + }else{ + pcc_set(sock,(unsigned int)PLD_CFRSTCR,0x100); + } + if (state->flags & SS_OUTPUT_ENA){ + debug(3, ":OUTPUT_ENA\n"); + /* bit clear */ + pcc_set(sock,(unsigned int)PLD_CFBUFCR,0); + } else { + pcc_set(sock,(unsigned int)PLD_CFBUFCR,1); + } + +#ifdef DEBUG + if(state->flags & SS_IOCARD){ + debug(3, ":IOCARD"); + } + if (state->flags & SS_PWR_AUTO) { + debug(3, ":PWR_AUTO"); + } + if (state->csc_mask & SS_DETECT) + debug(3, ":csc-SS_DETECT"); + if (state->flags & SS_IOCARD) { + if (state->csc_mask & SS_STSCHG) + debug(3, ":STSCHG"); + } else { + if (state->csc_mask & SS_BATDEAD) + debug(3, ":BATDEAD"); + if (state->csc_mask & SS_BATWARN) + debug(3, ":BATWARN"); + if (state->csc_mask & SS_READY) + debug(3, ":READY"); + } + debug(3, "\n"); +#endif + return 0; +} /* _set_socket */ + +/*====================================================================*/ + +static int _pcc_set_io_map(u_short sock, struct pccard_io_map *io) +{ + u_char map; + + debug(3, "m32r_cfc: SetIOMap(%d, %d, %#2.2x, %d ns, " + "%#lx-%#lx)\n", sock, io->map, io->flags, + io->speed, io->start, io->stop); + map = io->map; + + return 0; +} /* _set_io_map */ + +/*====================================================================*/ + +static int _pcc_set_mem_map(u_short sock, struct pccard_mem_map *mem) +{ + + u_char map = mem->map; + u_long addr; + pcc_socket_t *t = &socket[sock]; + + debug(3, "m32r_cfc: SetMemMap(%d, %d, %#2.2x, %d ns, " + "%#lx, %#x)\n", sock, map, mem->flags, + mem->speed, mem->static_start, mem->card_start); + + /* + * sanity check + */ + if ((map > MAX_WIN) || (mem->card_start > 0x3ffffff)){ + return -EINVAL; + } + + /* + * de-activate + */ + if ((mem->flags & MAP_ACTIVE) == 0) { + t->current_space = as_none; + return 0; + } + + /* + * Set mode + */ + if (mem->flags & MAP_ATTRIB) { + t->current_space = as_attr; + } else { + t->current_space = as_comm; + } + + /* + * Set address + */ + addr = t->mapaddr + (mem->card_start & M32R_PCC_MAPMASK); + mem->static_start = addr + mem->card_start; + + return 0; + +} /* _set_mem_map */ + +#if 0 /* driver model ordering issue */ +/*====================================================================== + + Routines for accessing socket information and register dumps via + /proc/bus/pccard/... + +======================================================================*/ + +static ssize_t show_info(struct class_device *class_dev, char *buf) +{ + pcc_socket_t *s = container_of(class_dev, struct pcc_socket, + socket.dev); + + return sprintf(buf, "type: %s\nbase addr: 0x%08lx\n", + pcc[s->type].name, s->base); +} + +static ssize_t show_exca(struct class_device *class_dev, char *buf) +{ + /* FIXME */ + + return 0; +} + +static CLASS_DEVICE_ATTR(info, S_IRUGO, show_info, NULL); +static CLASS_DEVICE_ATTR(exca, S_IRUGO, show_exca, NULL); +#endif + +/*====================================================================*/ + +/* this is horribly ugly... proper locking needs to be done here at + * some time... */ +#define LOCKED(x) do { \ + int retval; \ + unsigned long flags; \ + spin_lock_irqsave(&pcc_lock, flags); \ + retval = x; \ + spin_unlock_irqrestore(&pcc_lock, flags); \ + return retval; \ +} while (0) + + +static int pcc_get_status(struct pcmcia_socket *s, u_int *value) +{ + unsigned int sock = container_of(s, struct pcc_socket, socket)->number; + + if (socket[sock].flags & IS_ALIVE) { + debug(3, "m32r_cfc: pcc_get_status: sock(%d) -EINVAL\n", sock); + *value = 0; + return -EINVAL; + } + debug(3, "m32r_cfc: pcc_get_status: sock(%d)\n", sock); + LOCKED(_pcc_get_status(sock, value)); +} + +static int pcc_get_socket(struct pcmcia_socket *s, socket_state_t *state) +{ + unsigned int sock = container_of(s, struct pcc_socket, socket)->number; + + if (socket[sock].flags & IS_ALIVE) { + debug(3, "m32r_cfc: pcc_get_socket: sock(%d) -EINVAL\n", sock); + return -EINVAL; + } + debug(3, "m32r_cfc: pcc_get_socket: sock(%d)\n", sock); + LOCKED(_pcc_get_socket(sock, state)); +} + +static int pcc_set_socket(struct pcmcia_socket *s, socket_state_t *state) +{ + unsigned int sock = container_of(s, struct pcc_socket, socket)->number; + + if (socket[sock].flags & IS_ALIVE) { + debug(3, "m32r_cfc: pcc_set_socket: sock(%d) -EINVAL\n", sock); + return -EINVAL; + } + debug(3, "m32r_cfc: pcc_set_socket: sock(%d)\n", sock); + LOCKED(_pcc_set_socket(sock, state)); +} + +static int pcc_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io) +{ + unsigned int sock = container_of(s, struct pcc_socket, socket)->number; + + if (socket[sock].flags & IS_ALIVE) { + debug(3, "m32r_cfc: pcc_set_io_map: sock(%d) -EINVAL\n", sock); + return -EINVAL; + } + debug(3, "m32r_cfc: pcc_set_io_map: sock(%d)\n", sock); + LOCKED(_pcc_set_io_map(sock, io)); +} + +static int pcc_set_mem_map(struct pcmcia_socket *s, struct pccard_mem_map *mem) +{ + unsigned int sock = container_of(s, struct pcc_socket, socket)->number; + + if (socket[sock].flags & IS_ALIVE) { + debug(3, "m32r_cfc: pcc_set_mem_map: sock(%d) -EINVAL\n", sock); + return -EINVAL; + } + debug(3, "m32r_cfc: pcc_set_mem_map: sock(%d)\n", sock); + LOCKED(_pcc_set_mem_map(sock, mem)); +} + +static int pcc_init(struct pcmcia_socket *s) +{ + debug(3, "m32r_cfc: pcc_init()\n"); + return 0; +} + +static struct pccard_operations pcc_operations = { + .init = pcc_init, + .get_status = pcc_get_status, + .get_socket = pcc_get_socket, + .set_socket = pcc_set_socket, + .set_io_map = pcc_set_io_map, + .set_mem_map = pcc_set_mem_map, +}; + +/*====================================================================*/ + +static int m32r_pcc_suspend(struct device *dev, u32 state, u32 level) +{ + int ret = 0; + if (level == SUSPEND_SAVE_STATE) + ret = pcmcia_socket_dev_suspend(dev, state); + return ret; +} + +static int m32r_pcc_resume(struct device *dev, u32 level) +{ + int ret = 0; + if (level == RESUME_RESTORE_STATE) + ret = pcmcia_socket_dev_resume(dev); + return ret; +} + + +static struct device_driver pcc_driver = { + .name = "cfc", + .bus = &platform_bus_type, + .suspend = m32r_pcc_suspend, + .resume = m32r_pcc_resume, +}; + +static struct platform_device pcc_device = { + .name = "cfc", + .id = 0, +}; + +/*====================================================================*/ + +static int __init init_m32r_pcc(void) +{ + int i, ret; + + ret = driver_register(&pcc_driver); + if (ret) + return ret; + + ret = platform_device_register(&pcc_device); + if (ret){ + driver_unregister(&pcc_driver); + return ret; + } + +#if defined(CONFIG_PLAT_MAPPI2) + pcc_set(0, (unsigned int)PLD_CFCR0, 0x0f0f); + pcc_set(0, (unsigned int)PLD_CFCR1, 0x0200); +#endif + + pcc_sockets = 0; + +#if !defined(CONFIG_PLAT_USRV) + add_pcc_socket(M32R_PCC0_BASE, PLD_IRQ_CFC_INSERT, CFC_ATTR_MAPBASE, + CFC_IOPORT_BASE); +#else /* CONFIG_PLAT_USRV */ + { + ulong base, mapaddr; + kio_addr_t ioaddr; + + for (i = 0 ; i < M32R_MAX_PCC ; i++) { + base = (ulong)PLD_CFRSTCR; + base = base | (i << 8); + ioaddr = (i + 1) << 12; + mapaddr = CFC_ATTR_MAPBASE | (i << 20); + add_pcc_socket(base, 0, mapaddr, ioaddr); + } + } +#endif /* CONFIG_PLAT_USRV */ + + if (pcc_sockets == 0) { + printk("socket is not found.\n"); + platform_device_unregister(&pcc_device); + driver_unregister(&pcc_driver); + return -ENODEV; + } + + /* Set up interrupt handler(s) */ + + for (i = 0 ; i < pcc_sockets ; i++) { + socket[i].socket.dev.dev = &pcc_device.dev; + socket[i].socket.ops = &pcc_operations; + socket[i].socket.resource_ops = &pccard_static_ops; + socket[i].socket.owner = THIS_MODULE; + socket[i].number = i; + ret = pcmcia_register_socket(&socket[i].socket); + if (!ret) + socket[i].flags |= IS_REGISTERED; + +#if 0 /* driver model ordering issue */ + class_device_create_file(&socket[i].socket.dev, + &class_device_attr_info); + class_device_create_file(&socket[i].socket.dev, + &class_device_attr_exca); +#endif + } + + /* Finally, schedule a polling interrupt */ + if (poll_interval != 0) { + poll_timer.function = pcc_interrupt_wrapper; + poll_timer.data = 0; + init_timer(&poll_timer); + poll_timer.expires = jiffies + poll_interval; + add_timer(&poll_timer); + } + + return 0; +} /* init_m32r_pcc */ + +static void __exit exit_m32r_pcc(void) +{ + int i; + + for (i = 0; i < pcc_sockets; i++) + if (socket[i].flags & IS_REGISTERED) + pcmcia_unregister_socket(&socket[i].socket); + + platform_device_unregister(&pcc_device); + if (poll_interval != 0) + del_timer_sync(&poll_timer); + + driver_unregister(&pcc_driver); +} /* exit_m32r_pcc */ + +module_init(init_m32r_pcc); +module_exit(exit_m32r_pcc); +MODULE_LICENSE("Dual MPL/GPL"); +/*====================================================================*/ diff --git a/drivers/pcmcia/m32r_cfc.h b/drivers/pcmcia/m32r_cfc.h new file mode 100644 index 000000000000..17c1db7ae155 --- /dev/null +++ b/drivers/pcmcia/m32r_cfc.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2001 by Hiroyuki Kondo + */ + +#if !defined(CONFIG_M32R_CFC_NUM) +#define M32R_MAX_PCC 2 +#else +#define M32R_MAX_PCC CONFIG_M32R_CFC_NUM +#endif + +/* + * M32R PC Card Controler + */ +#define M32R_PCC0_BASE 0x00ef7000 +#define M32R_PCC1_BASE 0x00ef7020 + +/* + * Register offsets + */ +#define PCCR 0x00 +#define PCADR 0x04 +#define PCMOD 0x08 +#define PCIRC 0x0c +#define PCCSIGCR 0x10 +#define PCATCR 0x14 + +/* + * PCCR + */ +#define PCCR_PCEN (1UL<<(31-31)) + +/* + * PCIRC + */ +#define PCIRC_BWERR (1UL<<(31-7)) +#define PCIRC_CDIN1 (1UL<<(31-14)) +#define PCIRC_CDIN2 (1UL<<(31-15)) +#define PCIRC_BEIEN (1UL<<(31-23)) +#define PCIRC_CIIEN (1UL<<(31-30)) +#define PCIRC_COIEN (1UL<<(31-31)) + +/* + * PCCSIGCR + */ +#define PCCSIGCR_SEN (1UL<<(31-3)) +#define PCCSIGCR_VEN (1UL<<(31-7)) +#define PCCSIGCR_CRST (1UL<<(31-15)) +#define PCCSIGCR_COCR (1UL<<(31-31)) + +/* + * + */ +#define PCMOD_AS_ATTRIB (1UL<<(31-19)) +#define PCMOD_AS_IO (1UL<<(31-18)) + +#define PCMOD_CBSZ (1UL<<(31-23)) /* set for 8bit */ + +#define PCMOD_DBEX (1UL<<(31-31)) /* set for excahnge */ + +/* + * M32R PCC Map addr + */ + +#define M32R_PCC0_MAPBASE 0x14000000 +#define M32R_PCC1_MAPBASE 0x16000000 + +#define M32R_PCC_MAPMAX 0x02000000 + +#define M32R_PCC_MAPSIZE 0x00001000 /* XXX */ +#define M32R_PCC_MAPMASK (~(M32R_PCC_MAPMAX-1)) + +#define CFC_IOPORT_BASE 0x1000 + +#if !defined(CONFIG_PLAT_USRV) +#define CFC_ATTR_MAPBASE 0x0c014000 +#define CFC_IO_MAPBASE_BYTE 0xac012000 +#define CFC_IO_MAPBASE_WORD 0xac002000 +#else /* CONFIG_PLAT_USRV */ +#define CFC_ATTR_MAPBASE 0x04014000 +#define CFC_IO_MAPBASE_BYTE 0xa4012000 +#define CFC_IO_MAPBASE_WORD 0xa4002000 +#endif /* CONFIG_PLAT_USRV */ + diff --git a/drivers/pcmcia/m32r_pcc.c b/drivers/pcmcia/m32r_pcc.c new file mode 100644 index 000000000000..cafba6f45dfa --- /dev/null +++ b/drivers/pcmcia/m32r_pcc.c @@ -0,0 +1,811 @@ +/* + * drivers/pcmcia/m32r_pcc.c + * + * Device driver for the PCMCIA functionality of M32R. + * + * Copyright (c) 2001, 2002, 2003, 2004 + * Hiroyuki Kondo, Naoto Sugai, Hayato Fujiwara + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/bitops.h> +#include <asm/system.h> +#include <asm/addrspace.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> + +/* XXX: should be moved into asm/irq.h */ +#define PCC0_IRQ 24 +#define PCC1_IRQ 25 + +#include "m32r_pcc.h" + +#define CHAOS_PCC_DEBUG +#ifdef CHAOS_PCC_DEBUG + static volatile u_short dummy_readbuf; +#endif + +#define PCC_DEBUG_DBEX + +#ifdef DEBUG +static int m32r_pcc_debug; +module_param(m32r_pcc_debug, int, 0644); +#define debug(lvl, fmt, arg...) do { \ + if (m32r_pcc_debug > (lvl)) \ + printk(KERN_DEBUG "m32r_pcc: " fmt , ## arg); \ +} while (0) +#else +#define debug(n, args...) do { } while (0) +#endif + +/* Poll status interval -- 0 means default to interrupt */ +static int poll_interval = 0; + +typedef enum pcc_space { as_none = 0, as_comm, as_attr, as_io } pcc_as_t; + +typedef struct pcc_socket { + u_short type, flags; + struct pcmcia_socket socket; + unsigned int number; + kio_addr_t ioaddr; + u_long mapaddr; + u_long base; /* PCC register base */ + u_char cs_irq, intr; + pccard_io_map io_map[MAX_IO_WIN]; + pccard_mem_map mem_map[MAX_WIN]; + u_char io_win; + u_char mem_win; + pcc_as_t current_space; + u_char last_iodbex; +#ifdef CHAOS_PCC_DEBUG + u_char last_iosize; +#endif +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *proc; +#endif +} pcc_socket_t; + +static int pcc_sockets = 0; +static pcc_socket_t socket[M32R_MAX_PCC] = { + { 0, }, /* ... */ +}; + +/*====================================================================*/ + +static unsigned int pcc_get(u_short, unsigned int); +static void pcc_set(u_short, unsigned int , unsigned int ); + +static DEFINE_SPINLOCK(pcc_lock); + +void pcc_iorw(int sock, unsigned long port, void *buf, size_t size, size_t nmemb, int wr, int flag) +{ + u_long addr; + u_long flags; + int need_ex; +#ifdef PCC_DEBUG_DBEX + int _dbex; +#endif + pcc_socket_t *t = &socket[sock]; +#ifdef CHAOS_PCC_DEBUG + int map_changed = 0; +#endif + + /* Need lock ? */ + spin_lock_irqsave(&pcc_lock, flags); + + /* + * Check if need dbex + */ + need_ex = (size > 1 && flag == 0) ? PCMOD_DBEX : 0; +#ifdef PCC_DEBUG_DBEX + _dbex = need_ex; + need_ex = 0; +#endif + + /* + * calculate access address + */ + addr = t->mapaddr + port - t->ioaddr + KSEG1; /* XXX */ + + /* + * Check current mapping + */ + if (t->current_space != as_io || t->last_iodbex != need_ex) { + + u_long cbsz; + + /* + * Disable first + */ + pcc_set(sock, PCCR, 0); + + /* + * Set mode and io address + */ + cbsz = (t->flags & MAP_16BIT) ? 0 : PCMOD_CBSZ; + pcc_set(sock, PCMOD, PCMOD_AS_IO | cbsz | need_ex); + pcc_set(sock, PCADR, addr & 0x1ff00000); + + /* + * Enable and read it + */ + pcc_set(sock, PCCR, 1); + +#ifdef CHAOS_PCC_DEBUG +#if 0 + map_changed = (t->current_space == as_attr && size == 2); /* XXX */ +#else + map_changed = 1; +#endif +#endif + t->current_space = as_io; + } + + /* + * access to IO space + */ + if (size == 1) { + /* Byte */ + unsigned char *bp = (unsigned char *)buf; + +#ifdef CHAOS_DEBUG + if (map_changed) { + dummy_readbuf = readb(addr); + } +#endif + if (wr) { + /* write Byte */ + while (nmemb--) { + writeb(*bp++, addr); + } + } else { + /* read Byte */ + while (nmemb--) { + *bp++ = readb(addr); + } + } + } else { + /* Word */ + unsigned short *bp = (unsigned short *)buf; + +#ifdef CHAOS_PCC_DEBUG + if (map_changed) { + dummy_readbuf = readw(addr); + } +#endif + if (wr) { + /* write Word */ + while (nmemb--) { +#ifdef PCC_DEBUG_DBEX + if (_dbex) { + unsigned char *cp = (unsigned char *)bp; + unsigned short tmp; + tmp = cp[1] << 8 | cp[0]; + writew(tmp, addr); + bp++; + } else +#endif + writew(*bp++, addr); + } + } else { + /* read Word */ + while (nmemb--) { +#ifdef PCC_DEBUG_DBEX + if (_dbex) { + unsigned char *cp = (unsigned char *)bp; + unsigned short tmp; + tmp = readw(addr); + cp[0] = tmp & 0xff; + cp[1] = (tmp >> 8) & 0xff; + bp++; + } else +#endif + *bp++ = readw(addr); + } + } + } + +#if 1 + /* addr is no longer used */ + if ((addr = pcc_get(sock, PCIRC)) & PCIRC_BWERR) { + printk("m32r_pcc: BWERR detected : port 0x%04lx : iosize %dbit\n", + port, size * 8); + pcc_set(sock, PCIRC, addr); + } +#endif + /* + * save state + */ + t->last_iosize = size; + t->last_iodbex = need_ex; + + /* Need lock ? */ + + spin_unlock_irqrestore(&pcc_lock,flags); + + return; +} + +void pcc_ioread(int sock, unsigned long port, void *buf, size_t size, size_t nmemb, int flag) { + pcc_iorw(sock, port, buf, size, nmemb, 0, flag); +} + +void pcc_iowrite(int sock, unsigned long port, void *buf, size_t size, size_t nmemb, int flag) { + pcc_iorw(sock, port, buf, size, nmemb, 1, flag); +} + +/*====================================================================*/ + +#define IS_REGISTERED 0x2000 +#define IS_ALIVE 0x8000 + +typedef struct pcc_t { + char *name; + u_short flags; +} pcc_t; + +static pcc_t pcc[] = { + { "xnux2", 0 }, { "xnux2", 0 }, +}; + +static irqreturn_t pcc_interrupt(int, void *, struct pt_regs *); + +/*====================================================================*/ + +static struct timer_list poll_timer; + +static unsigned int pcc_get(u_short sock, unsigned int reg) +{ + return inl(socket[sock].base + reg); +} + + +static void pcc_set(u_short sock, unsigned int reg, unsigned int data) +{ + outl(data, socket[sock].base + reg); +} + +/*====================================================================== + + See if a card is present, powered up, in IO mode, and already + bound to a (non PC Card) Linux driver. We leave these alone. + + We make an exception for cards that seem to be serial devices. + +======================================================================*/ + +static int __init is_alive(u_short sock) +{ + unsigned int stat; + unsigned int f; + + stat = pcc_get(sock, PCIRC); + f = (stat & (PCIRC_CDIN1 | PCIRC_CDIN2)) >> 16; + if(!f){ + printk("m32r_pcc: No Card is detected at socket %d : stat = 0x%08x\n",stat,sock); + return 0; + } + if(f!=3) + printk("m32r_pcc: Insertion fail (%.8x) at socket %d\n",stat,sock); + else + printk("m32r_pcc: Card is Inserted at socket %d(%.8x)\n",sock,stat); + return 0; +} + +static void add_pcc_socket(ulong base, int irq, ulong mapaddr, kio_addr_t ioaddr) +{ + pcc_socket_t *t = &socket[pcc_sockets]; + + /* add sockets */ + t->ioaddr = ioaddr; + t->mapaddr = mapaddr; + t->base = base; +#ifdef CHAOS_PCC_DEBUG + t->flags = MAP_16BIT; +#else + t->flags = 0; +#endif + if (is_alive(pcc_sockets)) + t->flags |= IS_ALIVE; + + /* add pcc */ + if (t->base > 0) { + request_region(t->base, 0x20, "m32r-pcc"); + } + + printk(KERN_INFO " %s ", pcc[pcc_sockets].name); + printk("pcc at 0x%08lx\n", t->base); + + /* Update socket interrupt information, capabilities */ + t->socket.features |= (SS_CAP_PCCARD | SS_CAP_STATIC_MAP); + t->socket.map_size = M32R_PCC_MAPSIZE; + t->socket.io_offset = ioaddr; /* use for io access offset */ + t->socket.irq_mask = 0; + t->socket.pci_irq = 2 + pcc_sockets; /* XXX */ + + request_irq(irq, pcc_interrupt, 0, "m32r-pcc", pcc_interrupt); + + pcc_sockets++; + + return; +} + + +/*====================================================================*/ + +static irqreturn_t pcc_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + int i, j, irc; + u_int events, active; + int handled = 0; + + debug(4, "m32r: pcc_interrupt(%d)\n", irq); + + for (j = 0; j < 20; j++) { + active = 0; + for (i = 0; i < pcc_sockets; i++) { + if ((socket[i].cs_irq != irq) && + (socket[i].socket.pci_irq != irq)) + continue; + handled = 1; + irc = pcc_get(i, PCIRC); + irc >>=16; + debug(2, "m32r-pcc:interrput: socket %d pcirc 0x%02x ", i, irc); + if (!irc) + continue; + + events = (irc) ? SS_DETECT : 0; + events |= (pcc_get(i,PCCR) & PCCR_PCEN) ? SS_READY : 0; + debug(2, " event 0x%02x\n", events); + + if (events) + pcmcia_parse_events(&socket[i].socket, events); + + active |= events; + active = 0; + } + if (!active) break; + } + if (j == 20) + printk(KERN_NOTICE "m32r-pcc: infinite loop in interrupt handler\n"); + + debug(4, "m32r-pcc: interrupt done\n"); + + return IRQ_RETVAL(handled); +} /* pcc_interrupt */ + +static void pcc_interrupt_wrapper(u_long data) +{ + pcc_interrupt(0, NULL, NULL); + init_timer(&poll_timer); + poll_timer.expires = jiffies + poll_interval; + add_timer(&poll_timer); +} + +/*====================================================================*/ + +static int _pcc_get_status(u_short sock, u_int *value) +{ + u_int status; + + status = pcc_get(sock,PCIRC); + *value = ((status & PCIRC_CDIN1) && (status & PCIRC_CDIN2)) + ? SS_DETECT : 0; + + status = pcc_get(sock,PCCR); + +#if 0 + *value |= (status & PCCR_PCEN) ? SS_READY : 0; +#else + *value |= SS_READY; /* XXX: always */ +#endif + + status = pcc_get(sock,PCCSIGCR); + *value |= (status & PCCSIGCR_VEN) ? SS_POWERON : 0; + + debug(3, "m32r-pcc: GetStatus(%d) = %#4.4x\n", sock, *value); + return 0; +} /* _get_status */ + +/*====================================================================*/ + +static int _pcc_get_socket(u_short sock, socket_state_t *state) +{ + debug(3, "m32r-pcc: GetSocket(%d) = flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x\n", sock, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + return 0; +} /* _get_socket */ + +/*====================================================================*/ + +static int _pcc_set_socket(u_short sock, socket_state_t *state) +{ + u_long reg = 0; + + debug(3, "m32r-pcc: SetSocket(%d, flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x)", sock, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + + if (state->Vcc) { + /* + * 5V only + */ + if (state->Vcc == 50) { + reg |= PCCSIGCR_VEN; + } else { + return -EINVAL; + } + } + + if (state->flags & SS_RESET) { + debug(3, ":RESET\n"); + reg |= PCCSIGCR_CRST; + } + if (state->flags & SS_OUTPUT_ENA){ + debug(3, ":OUTPUT_ENA\n"); + /* bit clear */ + } else { + reg |= PCCSIGCR_SEN; + } + + pcc_set(sock,PCCSIGCR,reg); + +#ifdef DEBUG + if(state->flags & SS_IOCARD){ + debug(3, ":IOCARD"); + } + if (state->flags & SS_PWR_AUTO) { + debug(3, ":PWR_AUTO"); + } + if (state->csc_mask & SS_DETECT) + debug(3, ":csc-SS_DETECT"); + if (state->flags & SS_IOCARD) { + if (state->csc_mask & SS_STSCHG) + debug(3, ":STSCHG"); + } else { + if (state->csc_mask & SS_BATDEAD) + debug(3, ":BATDEAD"); + if (state->csc_mask & SS_BATWARN) + debug(3, ":BATWARN"); + if (state->csc_mask & SS_READY) + debug(3, ":READY"); + } + debug(3, "\n"); +#endif + return 0; +} /* _set_socket */ + +/*====================================================================*/ + +static int _pcc_set_io_map(u_short sock, struct pccard_io_map *io) +{ + u_char map; + + debug(3, "m32r-pcc: SetIOMap(%d, %d, %#2.2x, %d ns, " + "%#lx-%#lx)\n", sock, io->map, io->flags, + io->speed, io->start, io->stop); + map = io->map; + + return 0; +} /* _set_io_map */ + +/*====================================================================*/ + +static int _pcc_set_mem_map(u_short sock, struct pccard_mem_map *mem) +{ + + u_char map = mem->map; + u_long mode; + u_long addr; + pcc_socket_t *t = &socket[sock]; +#ifdef CHAOS_PCC_DEBUG +#if 0 + pcc_as_t last = t->current_space; +#endif +#endif + + debug(3, "m32r-pcc: SetMemMap(%d, %d, %#2.2x, %d ns, " + "%#lx, %#x)\n", sock, map, mem->flags, + mem->speed, mem->static_start, mem->card_start); + + /* + * sanity check + */ + if ((map > MAX_WIN) || (mem->card_start > 0x3ffffff)){ + return -EINVAL; + } + + /* + * de-activate + */ + if ((mem->flags & MAP_ACTIVE) == 0) { + t->current_space = as_none; + return 0; + } + + /* + * Disable first + */ + pcc_set(sock, PCCR, 0); + + /* + * Set mode + */ + if (mem->flags & MAP_ATTRIB) { + mode = PCMOD_AS_ATTRIB | PCMOD_CBSZ; + t->current_space = as_attr; + } else { + mode = 0; /* common memory */ + t->current_space = as_comm; + } + pcc_set(sock, PCMOD, mode); + + /* + * Set address + */ + addr = t->mapaddr + (mem->card_start & M32R_PCC_MAPMASK); + pcc_set(sock, PCADR, addr); + + mem->static_start = addr + mem->card_start; + + /* + * Enable again + */ + pcc_set(sock, PCCR, 1); + +#ifdef CHAOS_PCC_DEBUG +#if 0 + if (last != as_attr) { +#else + if (1) { +#endif + dummy_readbuf = *(u_char *)(addr + KSEG1); + } +#endif + + return 0; + +} /* _set_mem_map */ + +#if 0 /* driver model ordering issue */ +/*====================================================================== + + Routines for accessing socket information and register dumps via + /proc/bus/pccard/... + +======================================================================*/ + +static ssize_t show_info(struct class_device *class_dev, char *buf) +{ + pcc_socket_t *s = container_of(class_dev, struct pcc_socket, + socket.dev); + + return sprintf(buf, "type: %s\nbase addr: 0x%08lx\n", + pcc[s->type].name, s->base); +} + +static ssize_t show_exca(struct class_device *class_dev, char *buf) +{ + /* FIXME */ + + return 0; +} + +static CLASS_DEVICE_ATTR(info, S_IRUGO, show_info, NULL); +static CLASS_DEVICE_ATTR(exca, S_IRUGO, show_exca, NULL); +#endif + +/*====================================================================*/ + +/* this is horribly ugly... proper locking needs to be done here at + * some time... */ +#define LOCKED(x) do { \ + int retval; \ + unsigned long flags; \ + spin_lock_irqsave(&pcc_lock, flags); \ + retval = x; \ + spin_unlock_irqrestore(&pcc_lock, flags); \ + return retval; \ +} while (0) + + +static int pcc_get_status(struct pcmcia_socket *s, u_int *value) +{ + unsigned int sock = container_of(s, struct pcc_socket, socket)->number; + + if (socket[sock].flags & IS_ALIVE) { + *value = 0; + return -EINVAL; + } + LOCKED(_pcc_get_status(sock, value)); +} + +static int pcc_get_socket(struct pcmcia_socket *s, socket_state_t *state) +{ + unsigned int sock = container_of(s, struct pcc_socket, socket)->number; + + if (socket[sock].flags & IS_ALIVE) + return -EINVAL; + LOCKED(_pcc_get_socket(sock, state)); +} + +static int pcc_set_socket(struct pcmcia_socket *s, socket_state_t *state) +{ + unsigned int sock = container_of(s, struct pcc_socket, socket)->number; + + if (socket[sock].flags & IS_ALIVE) + return -EINVAL; + + LOCKED(_pcc_set_socket(sock, state)); +} + +static int pcc_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io) +{ + unsigned int sock = container_of(s, struct pcc_socket, socket)->number; + + if (socket[sock].flags & IS_ALIVE) + return -EINVAL; + LOCKED(_pcc_set_io_map(sock, io)); +} + +static int pcc_set_mem_map(struct pcmcia_socket *s, struct pccard_mem_map *mem) +{ + unsigned int sock = container_of(s, struct pcc_socket, socket)->number; + + if (socket[sock].flags & IS_ALIVE) + return -EINVAL; + LOCKED(_pcc_set_mem_map(sock, mem)); +} + +static int pcc_init(struct pcmcia_socket *s) +{ + debug(4, "m32r-pcc: init call\n"); + return 0; +} + +static struct pccard_operations pcc_operations = { + .init = pcc_init, + .get_status = pcc_get_status, + .get_socket = pcc_get_socket, + .set_socket = pcc_set_socket, + .set_io_map = pcc_set_io_map, + .set_mem_map = pcc_set_mem_map, +}; + +/*====================================================================*/ + +static int m32r_pcc_suspend(struct device *dev, u32 state, u32 level) +{ + int ret = 0; + if (level == SUSPEND_SAVE_STATE) + ret = pcmcia_socket_dev_suspend(dev, state); + return ret; +} + +static int m32r_pcc_resume(struct device *dev, u32 level) +{ + int ret = 0; + if (level == RESUME_RESTORE_STATE) + ret = pcmcia_socket_dev_resume(dev); + return ret; +} + + +static struct device_driver pcc_driver = { + .name = "pcc", + .bus = &platform_bus_type, + .suspend = m32r_pcc_suspend, + .resume = m32r_pcc_resume, +}; + +static struct platform_device pcc_device = { + .name = "pcc", + .id = 0, +}; + +/*====================================================================*/ + +static int __init init_m32r_pcc(void) +{ + int i, ret; + + ret = driver_register(&pcc_driver); + if (ret) + return ret; + + ret = platform_device_register(&pcc_device); + if (ret){ + driver_unregister(&pcc_driver); + return ret; + } + + printk(KERN_INFO "m32r PCC probe:\n"); + + pcc_sockets = 0; + + add_pcc_socket(M32R_PCC0_BASE, PCC0_IRQ, M32R_PCC0_MAPBASE, 0x1000); + +#ifdef CONFIG_M32RPCC_SLOT2 + add_pcc_socket(M32R_PCC1_BASE, PCC1_IRQ, M32R_PCC1_MAPBASE, 0x2000); +#endif + + if (pcc_sockets == 0) { + printk("socket is not found.\n"); + platform_device_unregister(&pcc_device); + driver_unregister(&pcc_driver); + return -ENODEV; + } + + /* Set up interrupt handler(s) */ + + for (i = 0 ; i < pcc_sockets ; i++) { + socket[i].socket.dev.dev = &pcc_device.dev; + socket[i].socket.ops = &pcc_operations; + socket[i].socket.resource_ops = &pccard_static_ops; + socket[i].socket.owner = THIS_MODULE; + socket[i].number = i; + ret = pcmcia_register_socket(&socket[i].socket); + if (!ret) + socket[i].flags |= IS_REGISTERED; + +#if 0 /* driver model ordering issue */ + class_device_create_file(&socket[i].socket.dev, + &class_device_attr_info); + class_device_create_file(&socket[i].socket.dev, + &class_device_attr_exca); +#endif + } + + /* Finally, schedule a polling interrupt */ + if (poll_interval != 0) { + poll_timer.function = pcc_interrupt_wrapper; + poll_timer.data = 0; + init_timer(&poll_timer); + poll_timer.expires = jiffies + poll_interval; + add_timer(&poll_timer); + } + + return 0; +} /* init_m32r_pcc */ + +static void __exit exit_m32r_pcc(void) +{ + int i; + + for (i = 0; i < pcc_sockets; i++) + if (socket[i].flags & IS_REGISTERED) + pcmcia_unregister_socket(&socket[i].socket); + + platform_device_unregister(&pcc_device); + if (poll_interval != 0) + del_timer_sync(&poll_timer); + + driver_unregister(&pcc_driver); +} /* exit_m32r_pcc */ + +module_init(init_m32r_pcc); +module_exit(exit_m32r_pcc); +MODULE_LICENSE("Dual MPL/GPL"); +/*====================================================================*/ diff --git a/drivers/pcmcia/m32r_pcc.h b/drivers/pcmcia/m32r_pcc.h new file mode 100644 index 000000000000..e4fffe417ba9 --- /dev/null +++ b/drivers/pcmcia/m32r_pcc.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2001 by Hiroyuki Kondo + */ + +#define M32R_MAX_PCC 2 + +/* + * M32R PC Card Controler + */ +#define M32R_PCC0_BASE 0x00ef7000 +#define M32R_PCC1_BASE 0x00ef7020 + +/* + * Register offsets + */ +#define PCCR 0x00 +#define PCADR 0x04 +#define PCMOD 0x08 +#define PCIRC 0x0c +#define PCCSIGCR 0x10 +#define PCATCR 0x14 + +/* + * PCCR + */ +#define PCCR_PCEN (1UL<<(31-31)) + +/* + * PCIRC + */ +#define PCIRC_BWERR (1UL<<(31-7)) +#define PCIRC_CDIN1 (1UL<<(31-14)) +#define PCIRC_CDIN2 (1UL<<(31-15)) +#define PCIRC_BEIEN (1UL<<(31-23)) +#define PCIRC_CIIEN (1UL<<(31-30)) +#define PCIRC_COIEN (1UL<<(31-31)) + +/* + * PCCSIGCR + */ +#define PCCSIGCR_SEN (1UL<<(31-3)) +#define PCCSIGCR_VEN (1UL<<(31-7)) +#define PCCSIGCR_CRST (1UL<<(31-15)) +#define PCCSIGCR_COCR (1UL<<(31-31)) + +/* + * + */ +#define PCMOD_AS_ATTRIB (1UL<<(31-19)) +#define PCMOD_AS_IO (1UL<<(31-18)) + +#define PCMOD_CBSZ (1UL<<(31-23)) /* set for 8bit */ + +#define PCMOD_DBEX (1UL<<(31-31)) /* set for excahnge */ + +/* + * M32R PCC Map addr + */ +#define M32R_PCC0_MAPBASE 0x14000000 +#define M32R_PCC1_MAPBASE 0x16000000 + +#define M32R_PCC_MAPMAX 0x02000000 + +#define M32R_PCC_MAPSIZE 0x00001000 /* XXX */ +#define M32R_PCC_MAPMASK (~(M32R_PCC_MAPMAX-1)) diff --git a/drivers/pcmcia/o2micro.h b/drivers/pcmcia/o2micro.h new file mode 100644 index 000000000000..b1f6e3d9ee06 --- /dev/null +++ b/drivers/pcmcia/o2micro.h @@ -0,0 +1,164 @@ +/* + * o2micro.h 1.13 1999/10/25 20:03:34 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in which + * case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_O2MICRO_H +#define _LINUX_O2MICRO_H + +#ifndef PCI_VENDOR_ID_O2 +#define PCI_VENDOR_ID_O2 0x1217 +#endif +#ifndef PCI_DEVICE_ID_O2_6729 +#define PCI_DEVICE_ID_O2_6729 0x6729 +#endif +#ifndef PCI_DEVICE_ID_O2_6730 +#define PCI_DEVICE_ID_O2_6730 0x673a +#endif +#ifndef PCI_DEVICE_ID_O2_6832 +#define PCI_DEVICE_ID_O2_6832 0x6832 +#endif +#ifndef PCI_DEVICE_ID_O2_6836 +#define PCI_DEVICE_ID_O2_6836 0x6836 +#endif +#ifndef PCI_DEVICE_ID_O2_6812 +#define PCI_DEVICE_ID_O2_6812 0x6872 +#endif + +/* Additional PCI configuration registers */ + +#define O2_MUX_CONTROL 0x90 /* 32 bit */ +#define O2_MUX_RING_OUT 0x0000000f +#define O2_MUX_SKTB_ACTV 0x000000f0 +#define O2_MUX_SCTA_ACTV_ENA 0x00000100 +#define O2_MUX_SCTB_ACTV_ENA 0x00000200 +#define O2_MUX_SER_IRQ_ROUTE 0x0000e000 +#define O2_MUX_SER_PCI 0x00010000 + +#define O2_MUX_SKTA_TURBO 0x000c0000 /* for 6833, 6860 */ +#define O2_MUX_SKTB_TURBO 0x00300000 +#define O2_MUX_AUX_VCC_3V 0x00400000 +#define O2_MUX_PCI_VCC_5V 0x00800000 +#define O2_MUX_PME_MUX 0x0f000000 + +/* Additional ExCA registers */ + +#define O2_MODE_A 0x38 +#define O2_MODE_A_2 0x26 /* for 6833B, 6860C */ +#define O2_MODE_A_CD_PULSE 0x04 +#define O2_MODE_A_SUSP_EDGE 0x08 +#define O2_MODE_A_HOST_SUSP 0x10 +#define O2_MODE_A_PWR_MASK 0x60 +#define O2_MODE_A_QUIET 0x80 + +#define O2_MODE_B 0x39 +#define O2_MODE_B_2 0x2e /* for 6833B, 6860C */ +#define O2_MODE_B_IDENT 0x03 +#define O2_MODE_B_ID_BSTEP 0x00 +#define O2_MODE_B_ID_CSTEP 0x01 +#define O2_MODE_B_ID_O2 0x02 +#define O2_MODE_B_VS1 0x04 +#define O2_MODE_B_VS2 0x08 +#define O2_MODE_B_IRQ15_RI 0x80 + +#define O2_MODE_C 0x3a +#define O2_MODE_C_DREQ_MASK 0x03 +#define O2_MODE_C_DREQ_INPACK 0x01 +#define O2_MODE_C_DREQ_WP 0x02 +#define O2_MODE_C_DREQ_BVD2 0x03 +#define O2_MODE_C_ZVIDEO 0x08 +#define O2_MODE_C_IREQ_SEL 0x30 +#define O2_MODE_C_MGMT_SEL 0xc0 + +#define O2_MODE_D 0x3b +#define O2_MODE_D_IRQ_MODE 0x03 +#define O2_MODE_D_PCI_CLKRUN 0x04 +#define O2_MODE_D_CB_CLKRUN 0x08 +#define O2_MODE_D_SKT_ACTV 0x20 +#define O2_MODE_D_PCI_FIFO 0x40 /* for OZ6729, OZ6730 */ +#define O2_MODE_D_W97_IRQ 0x40 +#define O2_MODE_D_ISA_IRQ 0x80 + +#define O2_MHPG_DMA 0x3c +#define O2_MHPG_CHANNEL 0x07 +#define O2_MHPG_CINT_ENA 0x08 +#define O2_MHPG_CSC_ENA 0x10 + +#define O2_FIFO_ENA 0x3d +#define O2_FIFO_ZVIDEO_3 0x08 +#define O2_FIFO_PCI_FIFO 0x10 +#define O2_FIFO_POSTWR 0x40 +#define O2_FIFO_BUFFER 0x80 + +#define O2_MODE_E 0x3e +#define O2_MODE_E_MHPG_DMA 0x01 +#define O2_MODE_E_SPKR_OUT 0x02 +#define O2_MODE_E_LED_OUT 0x08 +#define O2_MODE_E_SKTA_ACTV 0x10 + +static int o2micro_override(struct yenta_socket *socket) +{ + /* + * 'reserved' register at 0x94/D4. chaning it to 0xCA (8 bit) enables + * read prefetching which for example makes the RME Hammerfall DSP + * working. for some bridges it is at 0x94, for others at 0xD4. it's + * ok to write to both registers on all O2 bridges. + * from Eric Still, 02Micro. + */ + u8 a, b; + + if (PCI_FUNC(socket->dev->devfn) == 0) { + a = config_readb(socket, 0x94); + b = config_readb(socket, 0xD4); + + printk(KERN_INFO "Yenta O2: res at 0x94/0xD4: %02x/%02x\n", a, b); + + switch (socket->dev->device) { + case PCI_DEVICE_ID_O2_6832: + printk(KERN_INFO "Yenta O2: old bridge, not enabling read prefetch / write burst\n"); + break; + + default: + printk(KERN_INFO "Yenta O2: enabling read prefetch/write burst\n"); + config_writeb(socket, 0x94, a | 0x0a); + config_writeb(socket, 0xD4, b | 0x0a); + } + } + + return 0; +} + +static void o2micro_restore_state(struct yenta_socket *socket) +{ + /* + * as long as read prefetch is the only thing in + * o2micro_override, it's safe to call it from here + */ + o2micro_override(socket); +} + +#endif /* _LINUX_O2MICRO_H */ diff --git a/drivers/pcmcia/pcmcia_compat.c b/drivers/pcmcia/pcmcia_compat.c new file mode 100644 index 000000000000..68b80084f83f --- /dev/null +++ b/drivers/pcmcia/pcmcia_compat.c @@ -0,0 +1,125 @@ +/* + * PCMCIA 16-bit compatibility functions + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Copyright (C) 2004 Dominik Brodowski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/init.h> + +#define IN_CARD_SERVICES +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> +#include <pcmcia/ss.h> + +#include "cs_internal.h" + +int pcmcia_get_first_tuple(client_handle_t handle, tuple_t *tuple) +{ + struct pcmcia_socket *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + return pccard_get_first_tuple(s, handle->Function, tuple); +} +EXPORT_SYMBOL(pcmcia_get_first_tuple); + +int pcmcia_get_next_tuple(client_handle_t handle, tuple_t *tuple) +{ + struct pcmcia_socket *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + return pccard_get_next_tuple(s, handle->Function, tuple); +} +EXPORT_SYMBOL(pcmcia_get_next_tuple); + +int pcmcia_get_tuple_data(client_handle_t handle, tuple_t *tuple) +{ + struct pcmcia_socket *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + return pccard_get_tuple_data(s, tuple); +} +EXPORT_SYMBOL(pcmcia_get_tuple_data); + +int pcmcia_parse_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + return pccard_parse_tuple(tuple, parse); +} +EXPORT_SYMBOL(pcmcia_parse_tuple); + +int pcmcia_validate_cis(client_handle_t handle, cisinfo_t *info) +{ + struct pcmcia_socket *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + return pccard_validate_cis(s, handle->Function, info); +} +EXPORT_SYMBOL(pcmcia_validate_cis); + +int pcmcia_get_configuration_info(client_handle_t handle, + config_info_t *config) +{ + struct pcmcia_socket *s; + + if ((CHECK_HANDLE(handle)) || !config) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!s) + return CS_BAD_HANDLE; + return pccard_get_configuration_info(s, handle->Function, config); +} +EXPORT_SYMBOL(pcmcia_get_configuration_info); + +int pcmcia_reset_card(client_handle_t handle, client_req_t *req) +{ + struct pcmcia_socket *skt; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + skt = SOCKET(handle); + if (!skt) + return CS_BAD_HANDLE; + + return pccard_reset_card(skt); +} +EXPORT_SYMBOL(pcmcia_reset_card); + +int pcmcia_get_status(client_handle_t handle, cs_status_t *status) +{ + struct pcmcia_socket *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + return pccard_get_status(s, handle->Function, status); +} +EXPORT_SYMBOL(pcmcia_get_status); + +int pcmcia_access_configuration_register(client_handle_t handle, + conf_reg_t *reg) +{ + struct pcmcia_socket *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + return pccard_access_configuration_register(s, handle->Function, reg); +} +EXPORT_SYMBOL(pcmcia_access_configuration_register); + diff --git a/drivers/pcmcia/pd6729.c b/drivers/pcmcia/pd6729.c new file mode 100644 index 000000000000..3f4364341d8d --- /dev/null +++ b/drivers/pcmcia/pd6729.c @@ -0,0 +1,871 @@ +/* + * Driver for the Cirrus PD6729 PCI-PCMCIA bridge. + * + * Based on the i82092.c driver. + * + * This software may be used and distributed according to the terms of + * the GNU General Public License, incorporated herein by reference. + */ + +#include <linux/kernel.h> +#include <linux/config.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/device.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> + +#include <asm/system.h> +#include <asm/io.h> + +#include "pd6729.h" +#include "i82365.h" +#include "cirrus.h" + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Driver for the Cirrus PD6729 PCI-PCMCIA bridge"); +MODULE_AUTHOR("Jun Komuro <komurojun-mbn@nifty.com>"); + +#define MAX_SOCKETS 2 + +/* + * simple helper functions + * External clock time, in nanoseconds. 120 ns = 8.33 MHz + */ +#define to_cycles(ns) ((ns)/120) + +#ifndef NO_IRQ +#define NO_IRQ ((unsigned int)(0)) +#endif + +/* + * PARAMETERS + * irq_mode=n + * Specifies the interrupt delivery mode. The default (1) is to use PCI + * interrupts; a value of 0 selects ISA interrupts. This must be set for + * correct operation of PCI card readers. + * + * irq_list=i,j,... + * This list limits the set of interrupts that can be used by PCMCIA + * cards. + * The default list is 3,4,5,7,9,10,11. + * (irq_list parameter is not used, if irq_mode = 1) + */ + +static int irq_mode = 1; /* 0 = ISA interrupt, 1 = PCI interrupt */ +static int irq_list[16]; +static int irq_list_count = 0; + +module_param(irq_mode, int, 0444); +module_param_array(irq_list, int, &irq_list_count, 0444); +MODULE_PARM_DESC(irq_mode, + "interrupt delivery mode. 0 = ISA, 1 = PCI. default is 1"); +MODULE_PARM_DESC(irq_list, "interrupts that can be used by PCMCIA cards"); + +static DEFINE_SPINLOCK(port_lock); + +/* basic value read/write functions */ + +static unsigned char indirect_read(struct pd6729_socket *socket, + unsigned short reg) +{ + unsigned long port; + unsigned char val; + unsigned long flags; + + spin_lock_irqsave(&port_lock, flags); + reg += socket->number * 0x40; + port = socket->io_base; + outb(reg, port); + val = inb(port + 1); + spin_unlock_irqrestore(&port_lock, flags); + + return val; +} + +static unsigned short indirect_read16(struct pd6729_socket *socket, + unsigned short reg) +{ + unsigned long port; + unsigned short tmp; + unsigned long flags; + + spin_lock_irqsave(&port_lock, flags); + reg = reg + socket->number * 0x40; + port = socket->io_base; + outb(reg, port); + tmp = inb(port + 1); + reg++; + outb(reg, port); + tmp = tmp | (inb(port + 1) << 8); + spin_unlock_irqrestore(&port_lock, flags); + + return tmp; +} + +static void indirect_write(struct pd6729_socket *socket, unsigned short reg, + unsigned char value) +{ + unsigned long port; + unsigned long flags; + + spin_lock_irqsave(&port_lock, flags); + reg = reg + socket->number * 0x40; + port = socket->io_base; + outb(reg, port); + outb(value, port + 1); + spin_unlock_irqrestore(&port_lock, flags); +} + +static void indirect_setbit(struct pd6729_socket *socket, unsigned short reg, + unsigned char mask) +{ + unsigned long port; + unsigned char val; + unsigned long flags; + + spin_lock_irqsave(&port_lock, flags); + reg = reg + socket->number * 0x40; + port = socket->io_base; + outb(reg, port); + val = inb(port + 1); + val |= mask; + outb(reg, port); + outb(val, port + 1); + spin_unlock_irqrestore(&port_lock, flags); +} + +static void indirect_resetbit(struct pd6729_socket *socket, unsigned short reg, + unsigned char mask) +{ + unsigned long port; + unsigned char val; + unsigned long flags; + + spin_lock_irqsave(&port_lock, flags); + reg = reg + socket->number * 0x40; + port = socket->io_base; + outb(reg, port); + val = inb(port + 1); + val &= ~mask; + outb(reg, port); + outb(val, port + 1); + spin_unlock_irqrestore(&port_lock, flags); +} + +static void indirect_write16(struct pd6729_socket *socket, unsigned short reg, + unsigned short value) +{ + unsigned long port; + unsigned char val; + unsigned long flags; + + spin_lock_irqsave(&port_lock, flags); + reg = reg + socket->number * 0x40; + port = socket->io_base; + + outb(reg, port); + val = value & 255; + outb(val, port + 1); + + reg++; + + outb(reg, port); + val = value >> 8; + outb(val, port + 1); + spin_unlock_irqrestore(&port_lock, flags); +} + +/* Interrupt handler functionality */ + +static irqreturn_t pd6729_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + struct pd6729_socket *socket = (struct pd6729_socket *)dev; + int i; + int loopcount = 0; + int handled = 0; + unsigned int events, active = 0; + + while (1) { + loopcount++; + if (loopcount > 20) { + printk(KERN_ERR "pd6729: infinite eventloop " + "in interrupt\n"); + break; + } + + active = 0; + + for (i = 0; i < MAX_SOCKETS; i++) { + unsigned int csc; + + /* card status change register */ + csc = indirect_read(&socket[i], I365_CSC); + if (csc == 0) /* no events on this socket */ + continue; + + handled = 1; + events = 0; + + if (csc & I365_CSC_DETECT) { + events |= SS_DETECT; + dprintk("Card detected in socket %i!\n", i); + } + + if (indirect_read(&socket[i], I365_INTCTL) + & I365_PC_IOCARD) { + /* For IO/CARDS, bit 0 means "read the card" */ + events |= (csc & I365_CSC_STSCHG) + ? SS_STSCHG : 0; + } else { + /* Check for battery/ready events */ + events |= (csc & I365_CSC_BVD1) + ? SS_BATDEAD : 0; + events |= (csc & I365_CSC_BVD2) + ? SS_BATWARN : 0; + events |= (csc & I365_CSC_READY) + ? SS_READY : 0; + } + + if (events) { + pcmcia_parse_events(&socket[i].socket, events); + } + active |= events; + } + + if (active == 0) /* no more events to handle */ + break; + } + return IRQ_RETVAL(handled); +} + +/* socket functions */ + +static void pd6729_interrupt_wrapper(unsigned long data) +{ + struct pd6729_socket *socket = (struct pd6729_socket *) data; + + pd6729_interrupt(0, (void *)socket, NULL); + mod_timer(&socket->poll_timer, jiffies + HZ); +} + +static int pd6729_get_status(struct pcmcia_socket *sock, u_int *value) +{ + struct pd6729_socket *socket + = container_of(sock, struct pd6729_socket, socket); + unsigned int status; + unsigned int data; + struct pd6729_socket *t; + + /* Interface Status Register */ + status = indirect_read(socket, I365_STATUS); + *value = 0; + + if ((status & I365_CS_DETECT) == I365_CS_DETECT) { + *value |= SS_DETECT; + } + + /* + * IO cards have a different meaning of bits 0,1 + * Also notice the inverse-logic on the bits + */ + if (indirect_read(socket, I365_INTCTL) & I365_PC_IOCARD) { + /* IO card */ + if (!(status & I365_CS_STSCHG)) + *value |= SS_STSCHG; + } else { + /* non I/O card */ + if (!(status & I365_CS_BVD1)) + *value |= SS_BATDEAD; + if (!(status & I365_CS_BVD2)) + *value |= SS_BATWARN; + } + + if (status & I365_CS_WRPROT) + *value |= SS_WRPROT; /* card is write protected */ + + if (status & I365_CS_READY) + *value |= SS_READY; /* card is not busy */ + + if (status & I365_CS_POWERON) + *value |= SS_POWERON; /* power is applied to the card */ + + t = (socket->number) ? socket : socket + 1; + indirect_write(t, PD67_EXT_INDEX, PD67_EXTERN_DATA); + data = indirect_read16(t, PD67_EXT_DATA); + *value |= (data & PD67_EXD_VS1(socket->number)) ? 0 : SS_3VCARD; + + return 0; +} + + +static int pd6729_get_socket(struct pcmcia_socket *sock, socket_state_t *state) +{ + struct pd6729_socket *socket + = container_of(sock, struct pd6729_socket, socket); + unsigned char reg, vcc, vpp; + + state->flags = 0; + state->Vcc = 0; + state->Vpp = 0; + state->io_irq = 0; + state->csc_mask = 0; + + /* First the power status of the socket */ + reg = indirect_read(socket, I365_POWER); + + if (reg & I365_PWR_AUTO) + state->flags |= SS_PWR_AUTO; /* Automatic Power Switch */ + + if (reg & I365_PWR_OUT) + state->flags |= SS_OUTPUT_ENA; /* Output signals are enabled */ + + vcc = reg & I365_VCC_MASK; vpp = reg & I365_VPP1_MASK; + + if (reg & I365_VCC_5V) { + state->Vcc = (indirect_read(socket, PD67_MISC_CTL_1) & + PD67_MC1_VCC_3V) ? 33 : 50; + + if (vpp == I365_VPP1_5V) { + if (state->Vcc == 50) + state->Vpp = 50; + else + state->Vpp = 33; + } + if (vpp == I365_VPP1_12V) + state->Vpp = 120; + } + + /* Now the IO card, RESET flags and IO interrupt */ + reg = indirect_read(socket, I365_INTCTL); + + if ((reg & I365_PC_RESET) == 0) + state->flags |= SS_RESET; + if (reg & I365_PC_IOCARD) + state->flags |= SS_IOCARD; /* This is an IO card */ + + /* Set the IRQ number */ + state->io_irq = socket->card_irq; + + /* Card status change */ + reg = indirect_read(socket, I365_CSCINT); + + if (reg & I365_CSC_DETECT) + state->csc_mask |= SS_DETECT; /* Card detect is enabled */ + + if (state->flags & SS_IOCARD) {/* IO Cards behave different */ + if (reg & I365_CSC_STSCHG) + state->csc_mask |= SS_STSCHG; + } else { + if (reg & I365_CSC_BVD1) + state->csc_mask |= SS_BATDEAD; + if (reg & I365_CSC_BVD2) + state->csc_mask |= SS_BATWARN; + if (reg & I365_CSC_READY) + state->csc_mask |= SS_READY; + } + + return 0; +} + +static int pd6729_set_socket(struct pcmcia_socket *sock, socket_state_t *state) +{ + struct pd6729_socket *socket + = container_of(sock, struct pd6729_socket, socket); + unsigned char reg, data; + + /* First, set the global controller options */ + indirect_write(socket, I365_GBLCTL, 0x00); + indirect_write(socket, I365_GENCTL, 0x00); + + /* Values for the IGENC register */ + socket->card_irq = state->io_irq; + + reg = 0; + /* The reset bit has "inverse" logic */ + if (!(state->flags & SS_RESET)) + reg |= I365_PC_RESET; + if (state->flags & SS_IOCARD) + reg |= I365_PC_IOCARD; + + /* IGENC, Interrupt and General Control Register */ + indirect_write(socket, I365_INTCTL, reg); + + /* Power registers */ + + reg = I365_PWR_NORESET; /* default: disable resetdrv on resume */ + + if (state->flags & SS_PWR_AUTO) { + dprintk("Auto power\n"); + reg |= I365_PWR_AUTO; /* automatic power mngmnt */ + } + if (state->flags & SS_OUTPUT_ENA) { + dprintk("Power Enabled\n"); + reg |= I365_PWR_OUT; /* enable power */ + } + + switch (state->Vcc) { + case 0: + break; + case 33: + dprintk("setting voltage to Vcc to 3.3V on socket %i\n", + socket->number); + reg |= I365_VCC_5V; + indirect_setbit(socket, PD67_MISC_CTL_1, PD67_MC1_VCC_3V); + break; + case 50: + dprintk("setting voltage to Vcc to 5V on socket %i\n", + socket->number); + reg |= I365_VCC_5V; + indirect_resetbit(socket, PD67_MISC_CTL_1, PD67_MC1_VCC_3V); + break; + default: + dprintk("pd6729: pd6729_set_socket called with " + "invalid VCC power value: %i\n", + state->Vcc); + return -EINVAL; + } + + switch (state->Vpp) { + case 0: + dprintk("not setting Vpp on socket %i\n", socket->number); + break; + case 33: + case 50: + dprintk("setting Vpp to Vcc for socket %i\n", socket->number); + reg |= I365_VPP1_5V; + break; + case 120: + dprintk("setting Vpp to 12.0\n"); + reg |= I365_VPP1_12V; + break; + default: + dprintk("pd6729: pd6729_set_socket called with invalid VPP power value: %i\n", + state->Vpp); + return -EINVAL; + } + + /* only write if changed */ + if (reg != indirect_read(socket, I365_POWER)) + indirect_write(socket, I365_POWER, reg); + + if (irq_mode == 1) { + /* all interrupts are to be done as PCI interrupts */ + data = PD67_EC1_INV_MGMT_IRQ | PD67_EC1_INV_CARD_IRQ; + } else + data = 0; + + indirect_write(socket, PD67_EXT_INDEX, PD67_EXT_CTL_1); + indirect_write(socket, PD67_EXT_DATA, data); + + /* Enable specific interrupt events */ + + reg = 0x00; + if (state->csc_mask & SS_DETECT) { + reg |= I365_CSC_DETECT; + } + if (state->flags & SS_IOCARD) { + if (state->csc_mask & SS_STSCHG) + reg |= I365_CSC_STSCHG; + } else { + if (state->csc_mask & SS_BATDEAD) + reg |= I365_CSC_BVD1; + if (state->csc_mask & SS_BATWARN) + reg |= I365_CSC_BVD2; + if (state->csc_mask & SS_READY) + reg |= I365_CSC_READY; + } + if (irq_mode == 1) + reg |= 0x30; /* management IRQ: PCI INTA# = "irq 3" */ + indirect_write(socket, I365_CSCINT, reg); + + reg = indirect_read(socket, I365_INTCTL); + if (irq_mode == 1) + reg |= 0x03; /* card IRQ: PCI INTA# = "irq 3" */ + else + reg |= socket->card_irq; + indirect_write(socket, I365_INTCTL, reg); + + /* now clear the (probably bogus) pending stuff by doing a dummy read */ + (void)indirect_read(socket, I365_CSC); + + return 0; +} + +static int pd6729_set_io_map(struct pcmcia_socket *sock, + struct pccard_io_map *io) +{ + struct pd6729_socket *socket + = container_of(sock, struct pd6729_socket, socket); + unsigned char map, ioctl; + + map = io->map; + + /* Check error conditions */ + if (map > 1) { + dprintk("pd6729_set_io_map with invalid map"); + return -EINVAL; + } + + /* Turn off the window before changing anything */ + if (indirect_read(socket, I365_ADDRWIN) & I365_ENA_IO(map)) + indirect_resetbit(socket, I365_ADDRWIN, I365_ENA_IO(map)); + + /* dprintk("set_io_map: Setting range to %x - %x\n", + io->start, io->stop);*/ + + /* write the new values */ + indirect_write16(socket, I365_IO(map)+I365_W_START, io->start); + indirect_write16(socket, I365_IO(map)+I365_W_STOP, io->stop); + + ioctl = indirect_read(socket, I365_IOCTL) & ~I365_IOCTL_MASK(map); + + if (io->flags & MAP_0WS) ioctl |= I365_IOCTL_0WS(map); + if (io->flags & MAP_16BIT) ioctl |= I365_IOCTL_16BIT(map); + if (io->flags & MAP_AUTOSZ) ioctl |= I365_IOCTL_IOCS16(map); + + indirect_write(socket, I365_IOCTL, ioctl); + + /* Turn the window back on if needed */ + if (io->flags & MAP_ACTIVE) + indirect_setbit(socket, I365_ADDRWIN, I365_ENA_IO(map)); + + return 0; +} + +static int pd6729_set_mem_map(struct pcmcia_socket *sock, + struct pccard_mem_map *mem) +{ + struct pd6729_socket *socket + = container_of(sock, struct pd6729_socket, socket); + unsigned short base, i; + unsigned char map; + + map = mem->map; + if (map > 4) { + printk("pd6729_set_mem_map: invalid map"); + return -EINVAL; + } + + if ((mem->res->start > mem->res->end) || (mem->speed > 1000)) { + printk("pd6729_set_mem_map: invalid address / speed"); + return -EINVAL; + } + + /* Turn off the window before changing anything */ + if (indirect_read(socket, I365_ADDRWIN) & I365_ENA_MEM(map)) + indirect_resetbit(socket, I365_ADDRWIN, I365_ENA_MEM(map)); + + /* write the start address */ + base = I365_MEM(map); + i = (mem->res->start >> 12) & 0x0fff; + if (mem->flags & MAP_16BIT) + i |= I365_MEM_16BIT; + if (mem->flags & MAP_0WS) + i |= I365_MEM_0WS; + indirect_write16(socket, base + I365_W_START, i); + + /* write the stop address */ + + i= (mem->res->end >> 12) & 0x0fff; + switch (to_cycles(mem->speed)) { + case 0: + break; + case 1: + i |= I365_MEM_WS0; + break; + case 2: + i |= I365_MEM_WS1; + break; + default: + i |= I365_MEM_WS1 | I365_MEM_WS0; + break; + } + + indirect_write16(socket, base + I365_W_STOP, i); + + /* Take care of high byte */ + indirect_write(socket, PD67_EXT_INDEX, PD67_MEM_PAGE(map)); + indirect_write(socket, PD67_EXT_DATA, mem->res->start >> 24); + + /* card start */ + + i = ((mem->card_start - mem->res->start) >> 12) & 0x3fff; + if (mem->flags & MAP_WRPROT) + i |= I365_MEM_WRPROT; + if (mem->flags & MAP_ATTRIB) { + /* dprintk("requesting attribute memory for socket %i\n", + socket->number);*/ + i |= I365_MEM_REG; + } else { + /* dprintk("requesting normal memory for socket %i\n", + socket->number);*/ + } + indirect_write16(socket, base + I365_W_OFF, i); + + /* Enable the window if necessary */ + if (mem->flags & MAP_ACTIVE) + indirect_setbit(socket, I365_ADDRWIN, I365_ENA_MEM(map)); + + return 0; +} + +static int pd6729_init(struct pcmcia_socket *sock) +{ + int i; + struct resource res = { .end = 0x0fff }; + pccard_io_map io = { 0, 0, 0, 0, 1 }; + pccard_mem_map mem = { .res = &res, }; + + pd6729_set_socket(sock, &dead_socket); + for (i = 0; i < 2; i++) { + io.map = i; + pd6729_set_io_map(sock, &io); + } + for (i = 0; i < 5; i++) { + mem.map = i; + pd6729_set_mem_map(sock, &mem); + } + + return 0; +} + + +/* the pccard structure and its functions */ +static struct pccard_operations pd6729_operations = { + .init = pd6729_init, + .get_status = pd6729_get_status, + .get_socket = pd6729_get_socket, + .set_socket = pd6729_set_socket, + .set_io_map = pd6729_set_io_map, + .set_mem_map = pd6729_set_mem_map, +}; + +static irqreturn_t pd6729_test(int irq, void *dev, struct pt_regs *regs) +{ + dprintk("-> hit on irq %d\n", irq); + return IRQ_HANDLED; +} + +static int pd6729_check_irq(int irq, int flags) +{ + if (request_irq(irq, pd6729_test, flags, "x", pd6729_test) != 0) + return -1; + free_irq(irq, pd6729_test); + return 0; +} + +static u_int __init pd6729_isa_scan(void) +{ + u_int mask0, mask = 0; + int i; + + if (irq_mode == 1) { + printk(KERN_INFO "pd6729: PCI card interrupts, " + "PCI status changes\n"); + return 0; + } + + if (irq_list_count == 0) + mask0 = 0xffff; + else + for (i = mask0 = 0; i < irq_list_count; i++) + mask0 |= (1<<irq_list[i]); + + mask0 &= PD67_MASK; + + /* just find interrupts that aren't in use */ + for (i = 0; i < 16; i++) + if ((mask0 & (1 << i)) && (pd6729_check_irq(i, 0) == 0)) + mask |= (1 << i); + + printk(KERN_INFO "pd6729: ISA irqs = "); + for (i = 0; i < 16; i++) + if (mask & (1<<i)) + printk("%s%d", ((mask & ((1<<i)-1)) ? "," : ""), i); + + if (mask == 0) printk("none!"); + + printk(" polling status changes.\n"); + + return mask; +} + +static int __devinit pd6729_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + int i, j, ret; + u_int mask; + char configbyte; + struct pd6729_socket *socket; + + socket = kmalloc(sizeof(struct pd6729_socket) * MAX_SOCKETS, + GFP_KERNEL); + if (!socket) + return -ENOMEM; + + memset(socket, 0, sizeof(struct pd6729_socket) * MAX_SOCKETS); + + if ((ret = pci_enable_device(dev))) + goto err_out_free_mem; + + printk(KERN_INFO "pd6729: Cirrus PD6729 PCI to PCMCIA Bridge " + "at 0x%lx on irq %d\n", pci_resource_start(dev, 0), dev->irq); + /* + * Since we have no memory BARs some firmware may not + * have had PCI_COMMAND_MEMORY enabled, yet the device needs it. + */ + pci_read_config_byte(dev, PCI_COMMAND, &configbyte); + if (!(configbyte & PCI_COMMAND_MEMORY)) { + printk(KERN_DEBUG "pd6729: Enabling PCI_COMMAND_MEMORY.\n"); + configbyte |= PCI_COMMAND_MEMORY; + pci_write_config_byte(dev, PCI_COMMAND, configbyte); + } + + ret = pci_request_regions(dev, "pd6729"); + if (ret) { + printk(KERN_INFO "pd6729: pci request region failed.\n"); + goto err_out_disable; + } + + if (dev->irq == NO_IRQ) + irq_mode = 0; /* fall back to ISA interrupt mode */ + + mask = pd6729_isa_scan(); + if (irq_mode == 0 && mask == 0) { + printk(KERN_INFO "pd6729: no ISA interrupt is available.\n"); + goto err_out_free_res; + } + + for (i = 0; i < MAX_SOCKETS; i++) { + socket[i].io_base = pci_resource_start(dev, 0); + socket[i].socket.features |= SS_CAP_PCCARD; + socket[i].socket.map_size = 0x1000; + socket[i].socket.irq_mask = mask; + socket[i].socket.pci_irq = dev->irq; + socket[i].socket.owner = THIS_MODULE; + + socket[i].number = i; + + socket[i].socket.ops = &pd6729_operations; + socket[i].socket.resource_ops = &pccard_nonstatic_ops; + socket[i].socket.dev.dev = &dev->dev; + socket[i].socket.driver_data = &socket[i]; + } + + pci_set_drvdata(dev, socket); + if (irq_mode == 1) { + /* Register the interrupt handler */ + if ((ret = request_irq(dev->irq, pd6729_interrupt, SA_SHIRQ, + "pd6729", socket))) { + printk(KERN_ERR "pd6729: Failed to register irq %d, " + "aborting\n", dev->irq); + goto err_out_free_res; + } + } else { + /* poll Card status change */ + init_timer(&socket->poll_timer); + socket->poll_timer.function = pd6729_interrupt_wrapper; + socket->poll_timer.data = (unsigned long)socket; + socket->poll_timer.expires = jiffies + HZ; + add_timer(&socket->poll_timer); + } + + for (i = 0; i < MAX_SOCKETS; i++) { + ret = pcmcia_register_socket(&socket[i].socket); + if (ret) { + printk(KERN_INFO "pd6729: pcmcia_register_socket " + "failed.\n"); + for (j = 0; j < i ; j++) + pcmcia_unregister_socket(&socket[j].socket); + goto err_out_free_res2; + } + } + + return 0; + + err_out_free_res2: + if (irq_mode == 1) + free_irq(dev->irq, socket); + else + del_timer_sync(&socket->poll_timer); + err_out_free_res: + pci_release_regions(dev); + err_out_disable: + pci_disable_device(dev); + + err_out_free_mem: + kfree(socket); + return ret; +} + +static void __devexit pd6729_pci_remove(struct pci_dev *dev) +{ + int i; + struct pd6729_socket *socket = pci_get_drvdata(dev); + + for (i = 0; i < MAX_SOCKETS; i++) { + /* Turn off all interrupt sources */ + indirect_write(&socket[i], I365_CSCINT, 0); + indirect_write(&socket[i], I365_INTCTL, 0); + + pcmcia_unregister_socket(&socket[i].socket); + } + + if (irq_mode == 1) + free_irq(dev->irq, socket); + else + del_timer_sync(&socket->poll_timer); + pci_release_regions(dev); + pci_disable_device(dev); + + kfree(socket); +} + +static int pd6729_socket_suspend(struct pci_dev *dev, pm_message_t state) +{ + return pcmcia_socket_dev_suspend(&dev->dev, state); +} + +static int pd6729_socket_resume(struct pci_dev *dev) +{ + return pcmcia_socket_dev_resume(&dev->dev); +} + +static struct pci_device_id pd6729_pci_ids[] = { + { + .vendor = PCI_VENDOR_ID_CIRRUS, + .device = PCI_DEVICE_ID_CIRRUS_6729, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { } +}; +MODULE_DEVICE_TABLE(pci, pd6729_pci_ids); + +static struct pci_driver pd6729_pci_drv = { + .name = "pd6729", + .id_table = pd6729_pci_ids, + .probe = pd6729_pci_probe, + .remove = __devexit_p(pd6729_pci_remove), + .suspend = pd6729_socket_suspend, + .resume = pd6729_socket_resume, +}; + +static int pd6729_module_init(void) +{ + return pci_register_driver(&pd6729_pci_drv); +} + +static void pd6729_module_exit(void) +{ + pci_unregister_driver(&pd6729_pci_drv); +} + +module_init(pd6729_module_init); +module_exit(pd6729_module_exit); diff --git a/drivers/pcmcia/pd6729.h b/drivers/pcmcia/pd6729.h new file mode 100644 index 000000000000..f392e458cdfd --- /dev/null +++ b/drivers/pcmcia/pd6729.h @@ -0,0 +1,30 @@ +#ifndef _INCLUDE_GUARD_PD6729_H_ +#define _INCLUDE_GUARD_PD6729_H_ + +/* Debuging defines */ +#ifdef NOTRACE +#define dprintk(fmt, args...) printk(fmt , ## args) +#else +#define dprintk(fmt, args...) do {} while (0) +#endif + +/* Flags for I365_GENCTL */ +#define I365_DF_VS1 0x40 /* DF-step Voltage Sense */ +#define I365_DF_VS2 0x80 + +/* Fields in PD67_EXTERN_DATA */ +#define PD67_EXD_VS1(s) (0x01 << ((s) << 1)) +#define PD67_EXD_VS2(s) (0x02 << ((s) << 1)) + +/* Default ISA interrupt mask */ +#define PD67_MASK 0x0eb8 /* irq 11,10,9,7,5,4,3 */ + +struct pd6729_socket { + int number; + int card_irq; + unsigned long io_base; /* base io address of the socket */ + struct pcmcia_socket socket; + struct timer_list poll_timer; +}; + +#endif diff --git a/drivers/pcmcia/pxa2xx_base.c b/drivers/pcmcia/pxa2xx_base.c new file mode 100644 index 000000000000..a6936a75a87e --- /dev/null +++ b/drivers/pcmcia/pxa2xx_base.c @@ -0,0 +1,254 @@ +/*====================================================================== + + Device driver for the PCMCIA control functionality of PXA2xx + microprocessors. + + The contents of this file may be used under the + terms of the GNU Public License version 2 (the "GPL") + + (c) Ian Molton (spyro@f2s.com) 2003 + (c) Stefan Eletzhofer (stefan.eletzhofer@inquant.de) 2003,4 + + derived from sa11xx_base.c + + Portions created by John G. Dorsey are + Copyright (C) 1999 John G. Dorsey. + + ======================================================================*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/config.h> +#include <linux/cpufreq.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> + +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/arch/pxa-regs.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> + +#include "cs_internal.h" +#include "soc_common.h" +#include "pxa2xx_base.h" + + +#define MCXX_SETUP_MASK (0x7f) +#define MCXX_ASST_MASK (0x1f) +#define MCXX_HOLD_MASK (0x3f) +#define MCXX_SETUP_SHIFT (0) +#define MCXX_ASST_SHIFT (7) +#define MCXX_HOLD_SHIFT (14) + +static inline u_int pxa2xx_mcxx_hold(u_int pcmcia_cycle_ns, + u_int mem_clk_10khz) +{ + u_int code = pcmcia_cycle_ns * mem_clk_10khz; + return (code / 300000) + ((code % 300000) ? 1 : 0) - 1; +} + +static inline u_int pxa2xx_mcxx_asst(u_int pcmcia_cycle_ns, + u_int mem_clk_10khz) +{ + u_int code = pcmcia_cycle_ns * mem_clk_10khz; + return (code / 300000) + ((code % 300000) ? 1 : 0) - 1; +} + +static inline u_int pxa2xx_mcxx_setup(u_int pcmcia_cycle_ns, + u_int mem_clk_10khz) +{ + u_int code = pcmcia_cycle_ns * mem_clk_10khz; + return (code / 100000) + ((code % 100000) ? 1 : 0) - 1; +} + +/* This function returns the (approximate) command assertion period, in + * nanoseconds, for a given CPU clock frequency and MCXX_ASST value: + */ +static inline u_int pxa2xx_pcmcia_cmd_time(u_int mem_clk_10khz, + u_int pcmcia_mcxx_asst) +{ + return (300000 * (pcmcia_mcxx_asst + 1) / mem_clk_10khz); +} + +static int pxa2xx_pcmcia_set_mcmem( int sock, int speed, int clock ) +{ + MCMEM(sock) = ((pxa2xx_mcxx_setup(speed, clock) + & MCXX_SETUP_MASK) << MCXX_SETUP_SHIFT) + | ((pxa2xx_mcxx_asst(speed, clock) + & MCXX_ASST_MASK) << MCXX_ASST_SHIFT) + | ((pxa2xx_mcxx_hold(speed, clock) + & MCXX_HOLD_MASK) << MCXX_HOLD_SHIFT); + + return 0; +} + +static int pxa2xx_pcmcia_set_mcio( int sock, int speed, int clock ) +{ + MCIO(sock) = ((pxa2xx_mcxx_setup(speed, clock) + & MCXX_SETUP_MASK) << MCXX_SETUP_SHIFT) + | ((pxa2xx_mcxx_asst(speed, clock) + & MCXX_ASST_MASK) << MCXX_ASST_SHIFT) + | ((pxa2xx_mcxx_hold(speed, clock) + & MCXX_HOLD_MASK) << MCXX_HOLD_SHIFT); + + return 0; +} + +static int pxa2xx_pcmcia_set_mcatt( int sock, int speed, int clock ) +{ + MCATT(sock) = ((pxa2xx_mcxx_setup(speed, clock) + & MCXX_SETUP_MASK) << MCXX_SETUP_SHIFT) + | ((pxa2xx_mcxx_asst(speed, clock) + & MCXX_ASST_MASK) << MCXX_ASST_SHIFT) + | ((pxa2xx_mcxx_hold(speed, clock) + & MCXX_HOLD_MASK) << MCXX_HOLD_SHIFT); + + return 0; +} + +static int pxa2xx_pcmcia_set_mcxx(struct soc_pcmcia_socket *skt, unsigned int clk) +{ + struct soc_pcmcia_timing timing; + int sock = skt->nr; + + soc_common_pcmcia_get_timing(skt, &timing); + + pxa2xx_pcmcia_set_mcmem(sock, timing.mem, clk); + pxa2xx_pcmcia_set_mcatt(sock, timing.attr, clk); + pxa2xx_pcmcia_set_mcio(sock, timing.io, clk); + + return 0; +} + +static int pxa2xx_pcmcia_set_timing(struct soc_pcmcia_socket *skt) +{ + unsigned int clk = get_memclk_frequency_10khz(); + return pxa2xx_pcmcia_set_mcxx(skt, clk); +} + +#ifdef CONFIG_CPU_FREQ + +static int +pxa2xx_pcmcia_frequency_change(struct soc_pcmcia_socket *skt, + unsigned long val, + struct cpufreq_freqs *freqs) +{ +#warning "it's not clear if this is right since the core CPU (N) clock has no effect on the memory (L) clock" + switch (val) { + case CPUFREQ_PRECHANGE: + if (freqs->new > freqs->old) { + debug(skt, 2, "new frequency %u.%uMHz > %u.%uMHz, " + "pre-updating\n", + freqs->new / 1000, (freqs->new / 100) % 10, + freqs->old / 1000, (freqs->old / 100) % 10); + pxa2xx_pcmcia_set_mcxx(skt, freqs->new); + } + break; + + case CPUFREQ_POSTCHANGE: + if (freqs->new < freqs->old) { + debug(skt, 2, "new frequency %u.%uMHz < %u.%uMHz, " + "post-updating\n", + freqs->new / 1000, (freqs->new / 100) % 10, + freqs->old / 1000, (freqs->old / 100) % 10); + pxa2xx_pcmcia_set_mcxx(skt, freqs->new); + } + break; + } + return 0; +} +#endif + +int pxa2xx_drv_pcmcia_probe(struct device *dev) +{ + int ret; + struct pcmcia_low_level *ops; + int first, nr; + + if (!dev || !dev->platform_data) + return -ENODEV; + + ops = (struct pcmcia_low_level *)dev->platform_data; + first = ops->first; + nr = ops->nr; + + /* Provide our PXA2xx specific timing routines. */ + ops->set_timing = pxa2xx_pcmcia_set_timing; +#ifdef CONFIG_CPU_FREQ + ops->frequency_change = pxa2xx_pcmcia_frequency_change; +#endif + + ret = soc_common_drv_pcmcia_probe(dev, ops, first, nr); + + if (ret == 0) { + /* + * We have at least one socket, so set MECR:CIT + * (Card Is There) + */ + MECR |= MECR_CIT; + + /* Set MECR:NOS (Number Of Sockets) */ + if (nr > 1) + MECR |= MECR_NOS; + else + MECR &= ~MECR_NOS; + } + + return ret; +} +EXPORT_SYMBOL(pxa2xx_drv_pcmcia_probe); + +static int pxa2xx_drv_pcmcia_suspend(struct device *dev, u32 state, u32 level) +{ + int ret = 0; + if (level == SUSPEND_SAVE_STATE) + ret = pcmcia_socket_dev_suspend(dev, state); + return ret; +} + +static int pxa2xx_drv_pcmcia_resume(struct device *dev, u32 level) +{ + int ret = 0; + if (level == RESUME_RESTORE_STATE) + { + struct pcmcia_low_level *ops = dev->platform_data; + int nr = ops ? ops->nr : 0; + + MECR = nr > 1 ? MECR_CIT | MECR_NOS : (nr > 0 ? MECR_CIT : 0); + ret = pcmcia_socket_dev_resume(dev); + } + return ret; +} + +static struct device_driver pxa2xx_pcmcia_driver = { + .probe = pxa2xx_drv_pcmcia_probe, + .remove = soc_common_drv_pcmcia_remove, + .suspend = pxa2xx_drv_pcmcia_suspend, + .resume = pxa2xx_drv_pcmcia_resume, + .name = "pxa2xx-pcmcia", + .bus = &platform_bus_type, +}; + +static int __init pxa2xx_pcmcia_init(void) +{ + return driver_register(&pxa2xx_pcmcia_driver); +} + +static void __exit pxa2xx_pcmcia_exit(void) +{ + driver_unregister(&pxa2xx_pcmcia_driver); +} + +module_init(pxa2xx_pcmcia_init); +module_exit(pxa2xx_pcmcia_exit); + +MODULE_AUTHOR("Stefan Eletzhofer <stefan.eletzhofer@inquant.de> and Ian Molton <spyro@f2s.com>"); +MODULE_DESCRIPTION("Linux PCMCIA Card Services: PXA2xx core socket driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pcmcia/pxa2xx_base.h b/drivers/pcmcia/pxa2xx_base.h new file mode 100644 index 000000000000..e46cff345d47 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_base.h @@ -0,0 +1,3 @@ +/* temporary measure */ +extern int pxa2xx_drv_pcmcia_probe(struct device *); + diff --git a/drivers/pcmcia/pxa2xx_lubbock.c b/drivers/pcmcia/pxa2xx_lubbock.c new file mode 100644 index 000000000000..fd1f691c7c2c --- /dev/null +++ b/drivers/pcmcia/pxa2xx_lubbock.c @@ -0,0 +1,269 @@ +/* + * linux/drivers/pcmcia/pxa2xx_lubbock.c + * + * Author: George Davis + * Created: Jan 10, 2002 + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Originally based upon linux/drivers/pcmcia/sa1100_neponset.c + * + * Lubbock PCMCIA specific routines. + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include <asm/hardware.h> +#include <asm/hardware/sa1111.h> +#include <asm/mach-types.h> +#include <asm/arch/pxa-regs.h> +#include <asm/arch/lubbock.h> + +#include "sa1111_generic.h" + +static int +lubbock_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + /* + * Setup default state of GPIO outputs + * before we enable them as outputs. + */ + GPSR(GPIO48_nPOE) = + GPIO_bit(GPIO48_nPOE) | + GPIO_bit(GPIO49_nPWE) | + GPIO_bit(GPIO50_nPIOR) | + GPIO_bit(GPIO51_nPIOW) | + GPIO_bit(GPIO52_nPCE_1) | + GPIO_bit(GPIO53_nPCE_2); + + pxa_gpio_mode(GPIO48_nPOE_MD); + pxa_gpio_mode(GPIO49_nPWE_MD); + pxa_gpio_mode(GPIO50_nPIOR_MD); + pxa_gpio_mode(GPIO51_nPIOW_MD); + pxa_gpio_mode(GPIO52_nPCE_1_MD); + pxa_gpio_mode(GPIO53_nPCE_2_MD); + pxa_gpio_mode(GPIO54_pSKTSEL_MD); + pxa_gpio_mode(GPIO55_nPREG_MD); + pxa_gpio_mode(GPIO56_nPWAIT_MD); + pxa_gpio_mode(GPIO57_nIOIS16_MD); + + return sa1111_pcmcia_hw_init(skt); +} + +static int +lubbock_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, + const socket_state_t *state) +{ + unsigned int pa_dwr_mask, pa_dwr_set, misc_mask, misc_set; + int ret = 0; + + pa_dwr_mask = pa_dwr_set = misc_mask = misc_set = 0; + + /* Lubbock uses the Maxim MAX1602, with the following connections: + * + * Socket 0 (PCMCIA): + * MAX1602 Lubbock Register + * Pin Signal + * ----- ------- ---------------------- + * A0VPP S0_PWR0 SA-1111 GPIO A<0> + * A1VPP S0_PWR1 SA-1111 GPIO A<1> + * A0VCC S0_PWR2 SA-1111 GPIO A<2> + * A1VCC S0_PWR3 SA-1111 GPIO A<3> + * VX VCC + * VY +3.3V + * 12IN +12V + * CODE +3.3V Cirrus Code, CODE = High (VY) + * + * Socket 1 (CF): + * MAX1602 Lubbock Register + * Pin Signal + * ----- ------- ---------------------- + * A0VPP GND VPP is not connected + * A1VPP GND VPP is not connected + * A0VCC S1_PWR0 MISC_WR<14> + * A1VCC S1_PWR1 MISC_WR<15> + * VX VCC + * VY +3.3V + * 12IN GND VPP is not connected + * CODE +3.3V Cirrus Code, CODE = High (VY) + * + */ + + again: + switch (skt->nr) { + case 0: + pa_dwr_mask = GPIO_A0 | GPIO_A1 | GPIO_A2 | GPIO_A3; + + switch (state->Vcc) { + case 0: /* Hi-Z */ + break; + + case 33: /* VY */ + pa_dwr_set |= GPIO_A3; + break; + + case 50: /* VX */ + pa_dwr_set |= GPIO_A2; + break; + + default: + printk(KERN_ERR "%s(): unrecognized Vcc %u\n", + __FUNCTION__, state->Vcc); + ret = -1; + } + + switch (state->Vpp) { + case 0: /* Hi-Z */ + break; + + case 120: /* 12IN */ + pa_dwr_set |= GPIO_A1; + break; + + default: /* VCC */ + if (state->Vpp == state->Vcc) + pa_dwr_set |= GPIO_A0; + else { + printk(KERN_ERR "%s(): unrecognized Vpp %u\n", + __FUNCTION__, state->Vpp); + ret = -1; + break; + } + } + break; + + case 1: + misc_mask = (1 << 15) | (1 << 14); + + switch (state->Vcc) { + case 0: /* Hi-Z */ + break; + + case 33: /* VY */ + misc_set |= 1 << 15; + break; + + case 50: /* VX */ + misc_set |= 1 << 14; + break; + + default: + printk(KERN_ERR "%s(): unrecognized Vcc %u\n", + __FUNCTION__, state->Vcc); + ret = -1; + break; + } + + if (state->Vpp != state->Vcc && state->Vpp != 0) { + printk(KERN_ERR "%s(): CF slot cannot support Vpp %u\n", + __FUNCTION__, state->Vpp); + ret = -1; + break; + } + break; + + default: + ret = -1; + } + + if (ret == 0) + ret = sa1111_pcmcia_configure_socket(skt, state); + + if (ret == 0) { + lubbock_set_misc_wr(misc_mask, misc_set); + sa1111_set_io(SA1111_DEV(skt->dev), pa_dwr_mask, pa_dwr_set); + } + +#if 1 + if (ret == 0 && state->Vcc == 33) { + struct pcmcia_state new_state; + + /* + * HACK ALERT: + * We can't sense the voltage properly on Lubbock before + * actually applying some power to the socket (catch 22). + * Resense the socket Voltage Sense pins after applying + * socket power. + * + * Note: It takes about 2.5ms for the MAX1602 VCC output + * to rise. + */ + mdelay(3); + + sa1111_pcmcia_socket_state(skt, &new_state); + + if (!new_state.vs_3v && !new_state.vs_Xv) { + /* + * Switch to 5V, Configure socket with 5V voltage + */ + lubbock_set_misc_wr(misc_mask, 0); + sa1111_set_io(SA1111_DEV(skt->dev), pa_dwr_mask, 0); + + /* + * It takes about 100ms to turn off Vcc. + */ + mdelay(100); + + /* + * We need to hack around the const qualifier as + * well to keep this ugly workaround localized and + * not force it to the rest of the code. Barf bags + * avaliable in the seat pocket in front of you! + */ + ((socket_state_t *)state)->Vcc = 50; + ((socket_state_t *)state)->Vpp = 50; + goto again; + } + } +#endif + + return ret; +} + +static struct pcmcia_low_level lubbock_pcmcia_ops = { + .owner = THIS_MODULE, + .hw_init = lubbock_pcmcia_hw_init, + .hw_shutdown = sa1111_pcmcia_hw_shutdown, + .socket_state = sa1111_pcmcia_socket_state, + .configure_socket = lubbock_pcmcia_configure_socket, + .socket_init = sa1111_pcmcia_socket_init, + .socket_suspend = sa1111_pcmcia_socket_suspend, + .first = 0, + .nr = 2, +}; + +#include "pxa2xx_base.h" + +int __init pcmcia_lubbock_init(struct sa1111_dev *sadev) +{ + int ret = -ENODEV; + + if (machine_is_lubbock()) { + /* + * Set GPIO_A<3:0> to be outputs for the MAX1600, + * and switch to standby mode. + */ + sa1111_set_io_dir(sadev, GPIO_A0|GPIO_A1|GPIO_A2|GPIO_A3, 0, 0); + sa1111_set_io(sadev, GPIO_A0|GPIO_A1|GPIO_A2|GPIO_A3, 0); + sa1111_set_sleep_io(sadev, GPIO_A0|GPIO_A1|GPIO_A2|GPIO_A3, 0); + + /* Set CF Socket 1 power to standby mode. */ + lubbock_set_misc_wr((1 << 15) | (1 << 14), 0); + + sadev->dev.platform_data = &lubbock_pcmcia_ops; + ret = pxa2xx_drv_pcmcia_probe(&sadev->dev); + } + + return ret; +} + +MODULE_LICENSE("GPL"); diff --git a/drivers/pcmcia/pxa2xx_mainstone.c b/drivers/pcmcia/pxa2xx_mainstone.c new file mode 100644 index 000000000000..5309734e1687 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_mainstone.c @@ -0,0 +1,202 @@ +/* + * linux/drivers/pcmcia/pxa2xx_mainstone.c + * + * Mainstone PCMCIA specific routines. + * + * Created: May 12, 2004 + * Author: Nicolas Pitre + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/device.h> + +#include <pcmcia/ss.h> + +#include <asm/hardware.h> +#include <asm/irq.h> + +#include <asm/arch/pxa-regs.h> +#include <asm/arch/mainstone.h> + +#include "soc_common.h" + + +static struct pcmcia_irqs irqs[] = { + { 0, MAINSTONE_S0_CD_IRQ, "PCMCIA0 CD" }, + { 1, MAINSTONE_S1_CD_IRQ, "PCMCIA1 CD" }, + { 0, MAINSTONE_S0_STSCHG_IRQ, "PCMCIA0 STSCHG" }, + { 1, MAINSTONE_S1_STSCHG_IRQ, "PCMCIA1 STSCHG" }, +}; + +static int mst_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + /* + * Setup default state of GPIO outputs + * before we enable them as outputs. + */ + GPSR(GPIO48_nPOE) = + GPIO_bit(GPIO48_nPOE) | + GPIO_bit(GPIO49_nPWE) | + GPIO_bit(GPIO50_nPIOR) | + GPIO_bit(GPIO51_nPIOW) | + GPIO_bit(GPIO85_nPCE_1) | + GPIO_bit(GPIO54_nPCE_2); + + pxa_gpio_mode(GPIO48_nPOE_MD); + pxa_gpio_mode(GPIO49_nPWE_MD); + pxa_gpio_mode(GPIO50_nPIOR_MD); + pxa_gpio_mode(GPIO51_nPIOW_MD); + pxa_gpio_mode(GPIO85_nPCE_1_MD); + pxa_gpio_mode(GPIO54_nPCE_2_MD); + pxa_gpio_mode(GPIO79_pSKTSEL_MD); + pxa_gpio_mode(GPIO55_nPREG_MD); + pxa_gpio_mode(GPIO56_nPWAIT_MD); + pxa_gpio_mode(GPIO57_nIOIS16_MD); + + skt->irq = (skt->nr == 0) ? MAINSTONE_S0_IRQ : MAINSTONE_S1_IRQ; + return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static void mst_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static unsigned long mst_pcmcia_status[2]; + +static void mst_pcmcia_socket_state(struct soc_pcmcia_socket *skt, + struct pcmcia_state *state) +{ + unsigned long status, flip; + + status = (skt->nr == 0) ? MST_PCMCIA0 : MST_PCMCIA1; + flip = (status ^ mst_pcmcia_status[skt->nr]) & MST_PCMCIA_nSTSCHG_BVD1; + + /* + * Workaround for STSCHG which can't be deasserted: + * We therefore disable/enable corresponding IRQs + * as needed to avoid IRQ locks. + */ + if (flip) { + mst_pcmcia_status[skt->nr] = status; + if (status & MST_PCMCIA_nSTSCHG_BVD1) + enable_irq( (skt->nr == 0) ? MAINSTONE_S0_STSCHG_IRQ + : MAINSTONE_S1_STSCHG_IRQ ); + else + disable_irq( (skt->nr == 0) ? MAINSTONE_S0_STSCHG_IRQ + : MAINSTONE_S1_STSCHG_IRQ ); + } + + state->detect = (status & MST_PCMCIA_nCD) ? 0 : 1; + state->ready = (status & MST_PCMCIA_nIRQ) ? 1 : 0; + state->bvd1 = (status & MST_PCMCIA_nSTSCHG_BVD1) ? 1 : 0; + state->bvd2 = (status & MST_PCMCIA_nSPKR_BVD2) ? 1 : 0; + state->vs_3v = (status & MST_PCMCIA_nVS1) ? 0 : 1; + state->vs_Xv = (status & MST_PCMCIA_nVS2) ? 0 : 1; + state->wrprot = 0; /* not available */ +} + +static int mst_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, + const socket_state_t *state) +{ + unsigned long power = 0; + int ret = 0; + + switch (state->Vcc) { + case 0: power |= MST_PCMCIA_PWR_VCC_0; break; + case 33: power |= MST_PCMCIA_PWR_VCC_33; break; + case 50: power |= MST_PCMCIA_PWR_VCC_50; break; + default: + printk(KERN_ERR "%s(): bad Vcc %u\n", + __FUNCTION__, state->Vcc); + ret = -1; + } + + switch (state->Vpp) { + case 0: power |= MST_PCMCIA_PWR_VPP_0; break; + case 120: power |= MST_PCMCIA_PWR_VPP_120; break; + default: + if(state->Vpp == state->Vcc) { + power |= MST_PCMCIA_PWR_VPP_VCC; + } else { + printk(KERN_ERR "%s(): bad Vpp %u\n", + __FUNCTION__, state->Vpp); + ret = -1; + } + } + + if (state->flags & SS_RESET) + power |= MST_PCMCIA_RESET; + + switch (skt->nr) { + case 0: MST_PCMCIA0 = power; break; + case 1: MST_PCMCIA1 = power; break; + default: ret = -1; + } + + return ret; +} + +static void mst_pcmcia_socket_init(struct soc_pcmcia_socket *skt) +{ +} + +static void mst_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) +{ +} + +static struct pcmcia_low_level mst_pcmcia_ops = { + .owner = THIS_MODULE, + .hw_init = mst_pcmcia_hw_init, + .hw_shutdown = mst_pcmcia_hw_shutdown, + .socket_state = mst_pcmcia_socket_state, + .configure_socket = mst_pcmcia_configure_socket, + .socket_init = mst_pcmcia_socket_init, + .socket_suspend = mst_pcmcia_socket_suspend, + .nr = 2, +}; + +static struct platform_device *mst_pcmcia_device; + +static int __init mst_pcmcia_init(void) +{ + int ret; + + mst_pcmcia_device = kmalloc(sizeof(*mst_pcmcia_device), GFP_KERNEL); + if (!mst_pcmcia_device) + return -ENOMEM; + memset(mst_pcmcia_device, 0, sizeof(*mst_pcmcia_device)); + mst_pcmcia_device->name = "pxa2xx-pcmcia"; + mst_pcmcia_device->dev.platform_data = &mst_pcmcia_ops; + + ret = platform_device_register(mst_pcmcia_device); + if (ret) + kfree(mst_pcmcia_device); + + return ret; +} + +static void __exit mst_pcmcia_exit(void) +{ + /* + * This call is supposed to free our mst_pcmcia_device. + * Unfortunately platform_device don't have a free method, and + * we can't assume it's free of any reference at this point so we + * can't free it either. + */ + platform_device_unregister(mst_pcmcia_device); +} + +module_init(mst_pcmcia_init); +module_exit(mst_pcmcia_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/pcmcia/pxa2xx_sharpsl.c b/drivers/pcmcia/pxa2xx_sharpsl.c new file mode 100644 index 000000000000..42efe218867a --- /dev/null +++ b/drivers/pcmcia/pxa2xx_sharpsl.c @@ -0,0 +1,264 @@ +/* + * Sharp SL-C7xx Series PCMCIA routines + * + * Copyright (c) 2004-2005 Richard Purdie + * + * Based on Sharp's 2.4 kernel patches and pxa2xx_mainstone.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/device.h> + +#include <asm/hardware.h> +#include <asm/irq.h> + +#include <asm/hardware/scoop.h> +#include <asm/arch/corgi.h> +#include <asm/arch/pxa-regs.h> + +#include "soc_common.h" + +#define NO_KEEP_VS 0x0001 + +static unsigned char keep_vs; +static unsigned char keep_rd; + +static struct pcmcia_irqs irqs[] = { + { 0, CORGI_IRQ_GPIO_CF_CD, "PCMCIA0 CD"}, +}; + +static void sharpsl_pcmcia_init_reset(void) +{ + reset_scoop(&corgiscoop_device.dev); + keep_vs = NO_KEEP_VS; + keep_rd = 0; +} + +static int sharpsl_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + int ret; + + /* + * Setup default state of GPIO outputs + * before we enable them as outputs. + */ + GPSR(GPIO48_nPOE) = + GPIO_bit(GPIO48_nPOE) | + GPIO_bit(GPIO49_nPWE) | + GPIO_bit(GPIO50_nPIOR) | + GPIO_bit(GPIO51_nPIOW) | + GPIO_bit(GPIO52_nPCE_1) | + GPIO_bit(GPIO53_nPCE_2); + + pxa_gpio_mode(GPIO48_nPOE_MD); + pxa_gpio_mode(GPIO49_nPWE_MD); + pxa_gpio_mode(GPIO50_nPIOR_MD); + pxa_gpio_mode(GPIO51_nPIOW_MD); + pxa_gpio_mode(GPIO52_nPCE_1_MD); + pxa_gpio_mode(GPIO53_nPCE_2_MD); + pxa_gpio_mode(GPIO54_pSKTSEL_MD); + pxa_gpio_mode(GPIO55_nPREG_MD); + pxa_gpio_mode(GPIO56_nPWAIT_MD); + pxa_gpio_mode(GPIO57_nIOIS16_MD); + + /* Register interrupts */ + ret = soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); + + if (ret) { + printk(KERN_ERR "Request for Compact Flash IRQ failed\n"); + return ret; + } + + /* Enable interrupt */ + write_scoop_reg(&corgiscoop_device.dev, SCOOP_IMR, 0x00C0); + write_scoop_reg(&corgiscoop_device.dev, SCOOP_MCR, 0x0101); + keep_vs = NO_KEEP_VS; + + skt->irq = CORGI_IRQ_GPIO_CF_IRQ; + + return 0; +} + +static void sharpsl_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); + + /* CF_BUS_OFF */ + sharpsl_pcmcia_init_reset(); +} + + +static void sharpsl_pcmcia_socket_state(struct soc_pcmcia_socket *skt, + struct pcmcia_state *state) +{ + unsigned short cpr, csr; + + cpr = read_scoop_reg(&corgiscoop_device.dev, SCOOP_CPR); + + write_scoop_reg(&corgiscoop_device.dev, SCOOP_IRM, 0x00FF); + write_scoop_reg(&corgiscoop_device.dev, SCOOP_ISR, 0x0000); + write_scoop_reg(&corgiscoop_device.dev, SCOOP_IRM, 0x0000); + csr = read_scoop_reg(&corgiscoop_device.dev, SCOOP_CSR); + if (csr & 0x0004) { + /* card eject */ + write_scoop_reg(&corgiscoop_device.dev, SCOOP_CDR, 0x0000); + keep_vs = NO_KEEP_VS; + } + else if (!(keep_vs & NO_KEEP_VS)) { + /* keep vs1,vs2 */ + write_scoop_reg(&corgiscoop_device.dev, SCOOP_CDR, 0x0000); + csr |= keep_vs; + } + else if (cpr & 0x0003) { + /* power on */ + write_scoop_reg(&corgiscoop_device.dev, SCOOP_CDR, 0x0000); + keep_vs = (csr & 0x00C0); + } + else { + /* card detect */ + write_scoop_reg(&corgiscoop_device.dev, SCOOP_CDR, 0x0002); + } + + state->detect = (csr & 0x0004) ? 0 : 1; + state->ready = (csr & 0x0002) ? 1 : 0; + state->bvd1 = (csr & 0x0010) ? 1 : 0; + state->bvd2 = (csr & 0x0020) ? 1 : 0; + state->wrprot = (csr & 0x0008) ? 1 : 0; + state->vs_3v = (csr & 0x0040) ? 0 : 1; + state->vs_Xv = (csr & 0x0080) ? 0 : 1; + + if ((cpr & 0x0080) && ((cpr & 0x8040) != 0x8040)) { + printk(KERN_ERR "sharpsl_pcmcia_socket_state(): CPR=%04X, Low voltage!\n", cpr); + } + +} + + +static int sharpsl_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, + const socket_state_t *state) +{ + unsigned long flags; + + unsigned short cpr, ncpr, ccr, nccr, mcr, nmcr, imr, nimr; + + switch (state->Vcc) { + case 0: break; + case 33: break; + case 50: break; + default: + printk(KERN_ERR "sharpsl_pcmcia_configure_socket(): bad Vcc %u\n", state->Vcc); + return -1; + } + + if ((state->Vpp!=state->Vcc) && (state->Vpp!=0)) { + printk(KERN_ERR "CF slot cannot support Vpp %u\n", state->Vpp); + return -1; + } + + local_irq_save(flags); + + nmcr = (mcr = read_scoop_reg(&corgiscoop_device.dev, SCOOP_MCR)) & ~0x0010; + ncpr = (cpr = read_scoop_reg(&corgiscoop_device.dev, SCOOP_CPR)) & ~0x0083; + nccr = (ccr = read_scoop_reg(&corgiscoop_device.dev, SCOOP_CCR)) & ~0x0080; + nimr = (imr = read_scoop_reg(&corgiscoop_device.dev, SCOOP_IMR)) & ~0x003E; + + ncpr |= (state->Vcc == 33) ? 0x0001 : + (state->Vcc == 50) ? 0x0002 : 0; + nmcr |= (state->flags&SS_IOCARD) ? 0x0010 : 0; + ncpr |= (state->flags&SS_OUTPUT_ENA) ? 0x0080 : 0; + nccr |= (state->flags&SS_RESET)? 0x0080: 0; + nimr |= ((skt->status&SS_DETECT) ? 0x0004 : 0)| + ((skt->status&SS_READY) ? 0x0002 : 0)| + ((skt->status&SS_BATDEAD)? 0x0010 : 0)| + ((skt->status&SS_BATWARN)? 0x0020 : 0)| + ((skt->status&SS_STSCHG) ? 0x0010 : 0)| + ((skt->status&SS_WRPROT) ? 0x0008 : 0); + + if (!(ncpr & 0x0003)) { + keep_rd = 0; + } else if (!keep_rd) { + if (nccr & 0x0080) + keep_rd = 1; + else + nccr |= 0x0080; + } + + if (mcr != nmcr) + write_scoop_reg(&corgiscoop_device.dev, SCOOP_MCR, nmcr); + if (cpr != ncpr) + write_scoop_reg(&corgiscoop_device.dev, SCOOP_CPR, ncpr); + if (ccr != nccr) + write_scoop_reg(&corgiscoop_device.dev, SCOOP_CCR, nccr); + if (imr != nimr) + write_scoop_reg(&corgiscoop_device.dev, SCOOP_IMR, nimr); + + local_irq_restore(flags); + + return 0; +} + +static void sharpsl_pcmcia_socket_init(struct soc_pcmcia_socket *skt) +{ +} + +static void sharpsl_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) +{ +} + +static struct pcmcia_low_level sharpsl_pcmcia_ops = { + .owner = THIS_MODULE, + .hw_init = sharpsl_pcmcia_hw_init, + .hw_shutdown = sharpsl_pcmcia_hw_shutdown, + .socket_state = sharpsl_pcmcia_socket_state, + .configure_socket = sharpsl_pcmcia_configure_socket, + .socket_init = sharpsl_pcmcia_socket_init, + .socket_suspend = sharpsl_pcmcia_socket_suspend, + .first = 0, + .nr = 1, +}; + +static struct platform_device *sharpsl_pcmcia_device; + +static int __init sharpsl_pcmcia_init(void) +{ + int ret; + + sharpsl_pcmcia_device = kmalloc(sizeof(*sharpsl_pcmcia_device), GFP_KERNEL); + if (!sharpsl_pcmcia_device) + return -ENOMEM; + memset(sharpsl_pcmcia_device, 0, sizeof(*sharpsl_pcmcia_device)); + sharpsl_pcmcia_device->name = "pxa2xx-pcmcia"; + sharpsl_pcmcia_device->dev.platform_data = &sharpsl_pcmcia_ops; + + ret = platform_device_register(sharpsl_pcmcia_device); + if (ret) + kfree(sharpsl_pcmcia_device); + + return ret; +} + +static void __exit sharpsl_pcmcia_exit(void) +{ + /* + * This call is supposed to free our sharpsl_pcmcia_device. + * Unfortunately platform_device don't have a free method, and + * we can't assume it's free of any reference at this point so we + * can't free it either. + */ + platform_device_unregister(sharpsl_pcmcia_device); +} + +module_init(sharpsl_pcmcia_init); +module_exit(sharpsl_pcmcia_exit); + +MODULE_DESCRIPTION("Sharp SL Series PCMCIA Support"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pcmcia/ricoh.h b/drivers/pcmcia/ricoh.h new file mode 100644 index 000000000000..01098c841f87 --- /dev/null +++ b/drivers/pcmcia/ricoh.h @@ -0,0 +1,206 @@ +/* + * ricoh.h 1.9 1999/10/25 20:03:34 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in which + * case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_RICOH_H +#define _LINUX_RICOH_H + + +#define RF5C_MODE_CTL 0x1f /* Mode control */ +#define RF5C_PWR_CTL 0x2f /* Mixed voltage control */ +#define RF5C_CHIP_ID 0x3a /* Chip identification */ +#define RF5C_MODE_CTL_3 0x3b /* Mode control 3 */ + +/* I/O window address offset */ +#define RF5C_IO_OFF(w) (0x36+((w)<<1)) + +/* Flags for RF5C_MODE_CTL */ +#define RF5C_MODE_ATA 0x01 /* ATA mode */ +#define RF5C_MODE_LED_ENA 0x02 /* IRQ 12 is LED */ +#define RF5C_MODE_CA21 0x04 +#define RF5C_MODE_CA22 0x08 +#define RF5C_MODE_CA23 0x10 +#define RF5C_MODE_CA24 0x20 +#define RF5C_MODE_CA25 0x40 +#define RF5C_MODE_3STATE_BIT7 0x80 + +/* Flags for RF5C_PWR_CTL */ +#define RF5C_PWR_VCC_3V 0x01 +#define RF5C_PWR_IREQ_HIGH 0x02 +#define RF5C_PWR_INPACK_ENA 0x04 +#define RF5C_PWR_5V_DET 0x08 +#define RF5C_PWR_TC_SEL 0x10 /* Terminal Count: irq 11 or 15 */ +#define RF5C_PWR_DREQ_LOW 0x20 +#define RF5C_PWR_DREQ_OFF 0x00 /* DREQ steering control */ +#define RF5C_PWR_DREQ_INPACK 0x40 +#define RF5C_PWR_DREQ_SPKR 0x80 +#define RF5C_PWR_DREQ_IOIS16 0xc0 + +/* Values for RF5C_CHIP_ID */ +#define RF5C_CHIP_RF5C296 0x32 +#define RF5C_CHIP_RF5C396 0xb2 + +/* Flags for RF5C_MODE_CTL_3 */ +#define RF5C_MCTL3_DISABLE 0x01 /* Disable PCMCIA interface */ +#define RF5C_MCTL3_DMA_ENA 0x02 + +/* Register definitions for Ricoh PCI-to-CardBus bridges */ + +/* Extra bits in CB_BRIDGE_CONTROL */ +#define RL5C46X_BCR_3E0_ENA 0x0800 +#define RL5C46X_BCR_3E2_ENA 0x1000 + +/* Bridge Configuration Register */ +#define RL5C4XX_CONFIG 0x80 /* 16 bit */ +#define RL5C4XX_CONFIG_IO_1_MODE 0x0200 +#define RL5C4XX_CONFIG_IO_0_MODE 0x0100 +#define RL5C4XX_CONFIG_PREFETCH 0x0001 + +/* Misc Control Register */ +#define RL5C4XX_MISC 0x0082 /* 16 bit */ +#define RL5C4XX_MISC_HW_SUSPEND_ENA 0x0002 +#define RL5C4XX_MISC_VCCEN_POL 0x0100 +#define RL5C4XX_MISC_VPPEN_POL 0x0200 +#define RL5C46X_MISC_SUSPEND 0x0001 +#define RL5C46X_MISC_PWR_SAVE_2 0x0004 +#define RL5C46X_MISC_IFACE_BUSY 0x0008 +#define RL5C46X_MISC_B_LOCK 0x0010 +#define RL5C46X_MISC_A_LOCK 0x0020 +#define RL5C46X_MISC_PCI_LOCK 0x0040 +#define RL5C47X_MISC_IFACE_BUSY 0x0004 +#define RL5C47X_MISC_PCI_INT_MASK 0x0018 +#define RL5C47X_MISC_PCI_INT_DIS 0x0020 +#define RL5C47X_MISC_SUBSYS_WR 0x0040 +#define RL5C47X_MISC_SRIRQ_ENA 0x0080 +#define RL5C47X_MISC_5V_DISABLE 0x0400 +#define RL5C47X_MISC_LED_POL 0x0800 + +/* 16-bit Interface Control Register */ +#define RL5C4XX_16BIT_CTL 0x0084 /* 16 bit */ +#define RL5C4XX_16CTL_IO_TIMING 0x0100 +#define RL5C4XX_16CTL_MEM_TIMING 0x0200 +#define RL5C46X_16CTL_LEVEL_1 0x0010 +#define RL5C46X_16CTL_LEVEL_2 0x0020 + +/* 16-bit IO and memory timing registers */ +#define RL5C4XX_16BIT_IO_0 0x0088 /* 16 bit */ +#define RL5C4XX_16BIT_MEM_0 0x008a /* 16 bit */ +#define RL5C4XX_SETUP_MASK 0x0007 +#define RL5C4XX_SETUP_SHIFT 0 +#define RL5C4XX_CMD_MASK 0x01f0 +#define RL5C4XX_CMD_SHIFT 4 +#define RL5C4XX_HOLD_MASK 0x1c00 +#define RL5C4XX_HOLD_SHIFT 10 +#define RL5C4XX_MISC_CONTROL 0x2F /* 8 bit */ +#define RL5C4XX_ZV_ENABLE 0x08 + +#ifdef __YENTA_H + +#define rl_misc(socket) ((socket)->private[0]) +#define rl_ctl(socket) ((socket)->private[1]) +#define rl_io(socket) ((socket)->private[2]) +#define rl_mem(socket) ((socket)->private[3]) +#define rl_config(socket) ((socket)->private[4]) + +static void ricoh_zoom_video(struct pcmcia_socket *sock, int onoff) +{ + u8 reg; + struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket); + + reg = config_readb(socket, RL5C4XX_MISC_CONTROL); + if (onoff) + /* Zoom zoom, we will all go together, zoom zoom, zoom zoom */ + reg |= RL5C4XX_ZV_ENABLE; + else + reg &= ~RL5C4XX_ZV_ENABLE; + + config_writeb(socket, RL5C4XX_MISC_CONTROL, reg); +} + +static void ricoh_set_zv(struct yenta_socket *socket) +{ + if(socket->dev->vendor == PCI_VENDOR_ID_RICOH) + { + switch(socket->dev->device) + { + /* There may be more .. */ + case PCI_DEVICE_ID_RICOH_RL5C478: + socket->socket.zoom_video = ricoh_zoom_video; + break; + } + } +} + +static void ricoh_save_state(struct yenta_socket *socket) +{ + rl_misc(socket) = config_readw(socket, RL5C4XX_MISC); + rl_ctl(socket) = config_readw(socket, RL5C4XX_16BIT_CTL); + rl_io(socket) = config_readw(socket, RL5C4XX_16BIT_IO_0); + rl_mem(socket) = config_readw(socket, RL5C4XX_16BIT_MEM_0); + rl_config(socket) = config_readw(socket, RL5C4XX_CONFIG); +} + +static void ricoh_restore_state(struct yenta_socket *socket) +{ + config_writew(socket, RL5C4XX_MISC, rl_misc(socket)); + config_writew(socket, RL5C4XX_16BIT_CTL, rl_ctl(socket)); + config_writew(socket, RL5C4XX_16BIT_IO_0, rl_io(socket)); + config_writew(socket, RL5C4XX_16BIT_MEM_0, rl_mem(socket)); + config_writew(socket, RL5C4XX_CONFIG, rl_config(socket)); +} + + +/* + * Magic Ricoh initialization code.. + */ +static int ricoh_override(struct yenta_socket *socket) +{ + u16 config, ctl; + + config = config_readw(socket, RL5C4XX_CONFIG); + + /* Set the default timings, don't trust the original values */ + ctl = RL5C4XX_16CTL_IO_TIMING | RL5C4XX_16CTL_MEM_TIMING; + + if(socket->dev->device < PCI_DEVICE_ID_RICOH_RL5C475) { + ctl |= RL5C46X_16CTL_LEVEL_1 | RL5C46X_16CTL_LEVEL_2; + } else { + config |= RL5C4XX_CONFIG_PREFETCH; + } + + config_writew(socket, RL5C4XX_16BIT_CTL, ctl); + config_writew(socket, RL5C4XX_CONFIG, config); + + ricoh_set_zv(socket); + + return 0; +} + +#endif /* CONFIG_CARDBUS */ + +#endif /* _LINUX_RICOH_H */ diff --git a/drivers/pcmcia/rsrc_mgr.c b/drivers/pcmcia/rsrc_mgr.c new file mode 100644 index 000000000000..b6843f8d300d --- /dev/null +++ b/drivers/pcmcia/rsrc_mgr.c @@ -0,0 +1,163 @@ +/* + * rsrc_mgr.c -- Resource management routines and/or wrappers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include "cs_internal.h" + + +#ifdef CONFIG_PCMCIA_PROBE + +static int adjust_irq(struct pcmcia_socket *s, adjust_t *adj) +{ + int irq; + u32 mask; + + irq = adj->resource.irq.IRQ; + if ((irq < 0) || (irq > 15)) + return CS_BAD_IRQ; + + if (adj->Action != REMOVE_MANAGED_RESOURCE) + return 0; + + mask = 1 << irq; + + if (!(s->irq_mask & mask)) + return 0; + + s->irq_mask &= ~mask; + + return 0; +} + +#else + +static inline int adjust_irq(struct pcmcia_socket *s, adjust_t *adj) { + return CS_SUCCESS; +} + +#endif + + +int pcmcia_adjust_resource_info(adjust_t *adj) +{ + struct pcmcia_socket *s; + int ret = CS_UNSUPPORTED_FUNCTION; + unsigned long flags; + + down_read(&pcmcia_socket_list_rwsem); + list_for_each_entry(s, &pcmcia_socket_list, socket_list) { + + if (adj->Resource == RES_IRQ) + ret = adjust_irq(s, adj); + + else if (s->resource_ops->adjust_resource) { + + /* you can't use the old interface if the new + * one was used before */ + spin_lock_irqsave(&s->lock, flags); + if ((s->resource_setup_done) && + !(s->resource_setup_old)) { + spin_unlock_irqrestore(&s->lock, flags); + continue; + } else if (!(s->resource_setup_old)) + s->resource_setup_old = 1; + spin_unlock_irqrestore(&s->lock, flags); + + ret = s->resource_ops->adjust_resource(s, adj); + if (!ret) { + /* as there's no way we know this is the + * last call to adjust_resource_info, we + * always need to assume this is the latest + * one... */ + spin_lock_irqsave(&s->lock, flags); + s->resource_setup_done = 1; + spin_unlock_irqrestore(&s->lock, flags); + } + } + } + up_read(&pcmcia_socket_list_rwsem); + + return (ret); +} +EXPORT_SYMBOL(pcmcia_adjust_resource_info); + +void pcmcia_validate_mem(struct pcmcia_socket *s) +{ + if (s->resource_ops->validate_mem) + s->resource_ops->validate_mem(s); +} +EXPORT_SYMBOL(pcmcia_validate_mem); + +int adjust_io_region(struct resource *res, unsigned long r_start, + unsigned long r_end, struct pcmcia_socket *s) +{ + if (s->resource_ops->adjust_io_region) + return s->resource_ops->adjust_io_region(res, r_start, r_end, s); + return -ENOMEM; +} + +struct resource *find_io_region(unsigned long base, int num, + unsigned long align, struct pcmcia_socket *s) +{ + if (s->resource_ops->find_io) + return s->resource_ops->find_io(base, num, align, s); + return NULL; +} + +struct resource *find_mem_region(u_long base, u_long num, u_long align, + int low, struct pcmcia_socket *s) +{ + if (s->resource_ops->find_mem) + return s->resource_ops->find_mem(base, num, align, low, s); + return NULL; +} + +void release_resource_db(struct pcmcia_socket *s) +{ + if (s->resource_ops->exit) + s->resource_ops->exit(s); +} + + +static int static_init(struct pcmcia_socket *s) +{ + unsigned long flags; + + /* the good thing about SS_CAP_STATIC_MAP sockets is + * that they don't need a resource database */ + + spin_lock_irqsave(&s->lock, flags); + s->resource_setup_done = 1; + spin_unlock_irqrestore(&s->lock, flags); + + return 0; +} + + +struct pccard_resource_ops pccard_static_ops = { + .validate_mem = NULL, + .adjust_io_region = NULL, + .find_io = NULL, + .find_mem = NULL, + .adjust_resource = NULL, + .init = static_init, + .exit = NULL, +}; +EXPORT_SYMBOL(pccard_static_ops); diff --git a/drivers/pcmcia/rsrc_nonstatic.c b/drivers/pcmcia/rsrc_nonstatic.c new file mode 100644 index 000000000000..5876bab7c14c --- /dev/null +++ b/drivers/pcmcia/rsrc_nonstatic.c @@ -0,0 +1,985 @@ +/* + * rsrc_nonstatic.c -- Resource management routines for !SS_CAP_STATIC_MAP sockets + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/pci.h> +#include <linux/device.h> + +#include <asm/irq.h> +#include <asm/io.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" + +MODULE_AUTHOR("David A. Hinds, Dominik Brodowski"); +MODULE_LICENSE("GPL"); + +/* Parameters that can be set with 'insmod' */ + +#define INT_MODULE_PARM(n, v) static int n = v; module_param(n, int, 0444) + +INT_MODULE_PARM(probe_mem, 1); /* memory probe? */ +#ifdef CONFIG_PCMCIA_PROBE +INT_MODULE_PARM(probe_io, 1); /* IO port probe? */ +INT_MODULE_PARM(mem_limit, 0x10000); +#endif + +/* for io_db and mem_db */ +struct resource_map { + u_long base, num; + struct resource_map *next; +}; + +struct socket_data { + struct resource_map mem_db; + struct resource_map io_db; + unsigned int rsrc_mem_probe; +}; + +static DECLARE_MUTEX(rsrc_sem); +#define MEM_PROBE_LOW (1 << 0) +#define MEM_PROBE_HIGH (1 << 1) + + +/*====================================================================== + + Linux resource management extensions + +======================================================================*/ + +static struct resource * +make_resource(unsigned long b, unsigned long n, int flags, char *name) +{ + struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL); + + if (res) { + memset(res, 0, sizeof(*res)); + res->name = name; + res->start = b; + res->end = b + n - 1; + res->flags = flags; + } + return res; +} + +static struct resource * +claim_region(struct pcmcia_socket *s, unsigned long base, unsigned long size, + int type, char *name) +{ + struct resource *res, *parent; + + parent = type & IORESOURCE_MEM ? &iomem_resource : &ioport_resource; + res = make_resource(base, size, type | IORESOURCE_BUSY, name); + + if (res) { +#ifdef CONFIG_PCI + if (s && s->cb_dev) + parent = pci_find_parent_resource(s->cb_dev, res); +#endif + if (!parent || request_resource(parent, res)) { + kfree(res); + res = NULL; + } + } + return res; +} + +static void free_region(struct resource *res) +{ + if (res) { + release_resource(res); + kfree(res); + } +} + +/*====================================================================== + + These manage the internal databases of available resources. + +======================================================================*/ + +static int add_interval(struct resource_map *map, u_long base, u_long num) +{ + struct resource_map *p, *q; + + for (p = map; ; p = p->next) { + if ((p != map) && (p->base+p->num-1 >= base)) + return -1; + if ((p->next == map) || (p->next->base > base+num-1)) + break; + } + q = kmalloc(sizeof(struct resource_map), GFP_KERNEL); + if (!q) return CS_OUT_OF_RESOURCE; + q->base = base; q->num = num; + q->next = p->next; p->next = q; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int sub_interval(struct resource_map *map, u_long base, u_long num) +{ + struct resource_map *p, *q; + + for (p = map; ; p = q) { + q = p->next; + if (q == map) + break; + if ((q->base+q->num > base) && (base+num > q->base)) { + if (q->base >= base) { + if (q->base+q->num <= base+num) { + /* Delete whole block */ + p->next = q->next; + kfree(q); + /* don't advance the pointer yet */ + q = p; + } else { + /* Cut off bit from the front */ + q->num = q->base + q->num - base - num; + q->base = base + num; + } + } else if (q->base+q->num <= base+num) { + /* Cut off bit from the end */ + q->num = base - q->base; + } else { + /* Split the block into two pieces */ + p = kmalloc(sizeof(struct resource_map), GFP_KERNEL); + if (!p) return CS_OUT_OF_RESOURCE; + p->base = base+num; + p->num = q->base+q->num - p->base; + q->num = base - q->base; + p->next = q->next ; q->next = p; + } + } + } + return CS_SUCCESS; +} + +/*====================================================================== + + These routines examine a region of IO or memory addresses to + determine what ranges might be genuinely available. + +======================================================================*/ + +#ifdef CONFIG_PCMCIA_PROBE +static void do_io_probe(struct pcmcia_socket *s, kio_addr_t base, kio_addr_t num) +{ + struct resource *res; + struct socket_data *s_data = s->resource_data; + kio_addr_t i, j, bad; + int any; + u_char *b, hole, most; + + printk(KERN_INFO "cs: IO port probe %#lx-%#lx:", + base, base+num-1); + + /* First, what does a floating port look like? */ + b = kmalloc(256, GFP_KERNEL); + if (!b) { + printk(KERN_ERR "do_io_probe: unable to kmalloc 256 bytes"); + return; + } + memset(b, 0, 256); + for (i = base, most = 0; i < base+num; i += 8) { + res = claim_region(NULL, i, 8, IORESOURCE_IO, "PCMCIA IO probe"); + if (!res) + continue; + hole = inb(i); + for (j = 1; j < 8; j++) + if (inb(i+j) != hole) break; + free_region(res); + if ((j == 8) && (++b[hole] > b[most])) + most = hole; + if (b[most] == 127) break; + } + kfree(b); + + bad = any = 0; + for (i = base; i < base+num; i += 8) { + res = claim_region(NULL, i, 8, IORESOURCE_IO, "PCMCIA IO probe"); + if (!res) + continue; + for (j = 0; j < 8; j++) + if (inb(i+j) != most) break; + free_region(res); + if (j < 8) { + if (!any) + printk(" excluding"); + if (!bad) + bad = any = i; + } else { + if (bad) { + sub_interval(&s_data->io_db, bad, i-bad); + printk(" %#lx-%#lx", bad, i-1); + bad = 0; + } + } + } + if (bad) { + if ((num > 16) && (bad == base) && (i == base+num)) { + printk(" nothing: probe failed.\n"); + return; + } else { + sub_interval(&s_data->io_db, bad, i-bad); + printk(" %#lx-%#lx", bad, i-1); + } + } + + printk(any ? "\n" : " clean.\n"); +} +#endif + +/*====================================================================== + + This is tricky... when we set up CIS memory, we try to validate + the memory window space allocations. + +======================================================================*/ + +/* Validation function for cards with a valid CIS */ +static int readable(struct pcmcia_socket *s, struct resource *res, cisinfo_t *info) +{ + int ret = -1; + + s->cis_mem.res = res; + s->cis_virt = ioremap(res->start, s->map_size); + if (s->cis_virt) { + ret = pccard_validate_cis(s, BIND_FN_ALL, info); + /* invalidate mapping and CIS cache */ + iounmap(s->cis_virt); + s->cis_virt = NULL; + destroy_cis_cache(s); + } + s->cis_mem.res = NULL; + if ((ret != 0) || (info->Chains == 0)) + return 0; + return 1; +} + +/* Validation function for simple memory cards */ +static int checksum(struct pcmcia_socket *s, struct resource *res) +{ + pccard_mem_map map; + int i, a = 0, b = -1, d; + void __iomem *virt; + + virt = ioremap(res->start, s->map_size); + if (virt) { + map.map = 0; + map.flags = MAP_ACTIVE; + map.speed = 0; + map.res = res; + map.card_start = 0; + s->ops->set_mem_map(s, &map); + + /* Don't bother checking every word... */ + for (i = 0; i < s->map_size; i += 44) { + d = readl(virt+i); + a += d; + b &= d; + } + + map.flags = 0; + s->ops->set_mem_map(s, &map); + + iounmap(virt); + } + + return (b == -1) ? -1 : (a>>1); +} + +static int +cis_readable(struct pcmcia_socket *s, unsigned long base, unsigned long size) +{ + struct resource *res1, *res2; + cisinfo_t info1, info2; + int ret = 0; + + res1 = claim_region(s, base, size/2, IORESOURCE_MEM, "cs memory probe"); + res2 = claim_region(s, base + size/2, size/2, IORESOURCE_MEM, "cs memory probe"); + + if (res1 && res2) { + ret = readable(s, res1, &info1); + ret += readable(s, res2, &info2); + } + + free_region(res2); + free_region(res1); + + return (ret == 2) && (info1.Chains == info2.Chains); +} + +static int +checksum_match(struct pcmcia_socket *s, unsigned long base, unsigned long size) +{ + struct resource *res1, *res2; + int a = -1, b = -1; + + res1 = claim_region(s, base, size/2, IORESOURCE_MEM, "cs memory probe"); + res2 = claim_region(s, base + size/2, size/2, IORESOURCE_MEM, "cs memory probe"); + + if (res1 && res2) { + a = checksum(s, res1); + b = checksum(s, res2); + } + + free_region(res2); + free_region(res1); + + return (a == b) && (a >= 0); +} + +/*====================================================================== + + The memory probe. If the memory list includes a 64K-aligned block + below 1MB, we probe in 64K chunks, and as soon as we accumulate at + least mem_limit free space, we quit. + +======================================================================*/ + +static int do_mem_probe(u_long base, u_long num, struct pcmcia_socket *s) +{ + struct socket_data *s_data = s->resource_data; + u_long i, j, bad, fail, step; + + printk(KERN_INFO "cs: memory probe 0x%06lx-0x%06lx:", + base, base+num-1); + bad = fail = 0; + step = (num < 0x20000) ? 0x2000 : ((num>>4) & ~0x1fff); + /* cis_readable wants to map 2x map_size */ + if (step < 2 * s->map_size) + step = 2 * s->map_size; + for (i = j = base; i < base+num; i = j + step) { + if (!fail) { + for (j = i; j < base+num; j += step) { + if (cis_readable(s, j, step)) + break; + } + fail = ((i == base) && (j == base+num)); + } + if (fail) { + for (j = i; j < base+num; j += 2*step) + if (checksum_match(s, j, step) && + checksum_match(s, j + step, step)) + break; + } + if (i != j) { + if (!bad) printk(" excluding"); + printk(" %#05lx-%#05lx", i, j-1); + sub_interval(&s_data->mem_db, i, j-i); + bad += j-i; + } + } + printk(bad ? "\n" : " clean.\n"); + return (num - bad); +} + +#ifdef CONFIG_PCMCIA_PROBE + +static u_long inv_probe(struct resource_map *m, struct pcmcia_socket *s) +{ + struct socket_data *s_data = s->resource_data; + u_long ok; + if (m == &s_data->mem_db) + return 0; + ok = inv_probe(m->next, s); + if (ok) { + if (m->base >= 0x100000) + sub_interval(&s_data->mem_db, m->base, m->num); + return ok; + } + if (m->base < 0x100000) + return 0; + return do_mem_probe(m->base, m->num, s); +} + +static void validate_mem(struct pcmcia_socket *s, unsigned int probe_mask) +{ + struct resource_map *m, mm; + static u_char order[] = { 0xd0, 0xe0, 0xc0, 0xf0 }; + u_long b, i, ok = 0; + struct socket_data *s_data = s->resource_data; + + /* We do up to four passes through the list */ + if (probe_mask & MEM_PROBE_HIGH) { + if (inv_probe(s_data->mem_db.next, s) > 0) + return; + printk(KERN_NOTICE "cs: warning: no high memory space " + "available!\n"); + } + if ((probe_mask & MEM_PROBE_LOW) == 0) + return; + for (m = s_data->mem_db.next; m != &s_data->mem_db; m = mm.next) { + mm = *m; + /* Only probe < 1 MB */ + if (mm.base >= 0x100000) continue; + if ((mm.base | mm.num) & 0xffff) { + ok += do_mem_probe(mm.base, mm.num, s); + continue; + } + /* Special probe for 64K-aligned block */ + for (i = 0; i < 4; i++) { + b = order[i] << 12; + if ((b >= mm.base) && (b+0x10000 <= mm.base+mm.num)) { + if (ok >= mem_limit) + sub_interval(&s_data->mem_db, b, 0x10000); + else + ok += do_mem_probe(b, 0x10000, s); + } + } + } +} + +#else /* CONFIG_PCMCIA_PROBE */ + +static void validate_mem(struct pcmcia_socket *s, unsigned int probe_mask) +{ + struct resource_map *m, mm; + struct socket_data *s_data = s->resource_data; + + for (m = s_data->mem_db.next; m != &s_data->mem_db; m = mm.next) { + mm = *m; + if (do_mem_probe(mm.base, mm.num, s)) + break; + } +} + +#endif /* CONFIG_PCMCIA_PROBE */ + + +/* + * Locking note: Must be called with skt_sem held! + */ +static void pcmcia_nonstatic_validate_mem(struct pcmcia_socket *s) +{ + struct socket_data *s_data = s->resource_data; + if (probe_mem) { + unsigned int probe_mask; + + down(&rsrc_sem); + + probe_mask = MEM_PROBE_LOW; + if (s->features & SS_CAP_PAGE_REGS) + probe_mask = MEM_PROBE_HIGH; + + if (probe_mask & ~s_data->rsrc_mem_probe) { + s_data->rsrc_mem_probe |= probe_mask; + + if (s->state & SOCKET_PRESENT) + validate_mem(s, probe_mask); + } + + up(&rsrc_sem); + } +} + +struct pcmcia_align_data { + unsigned long mask; + unsigned long offset; + struct resource_map *map; +}; + +static void +pcmcia_common_align(void *align_data, struct resource *res, + unsigned long size, unsigned long align) +{ + struct pcmcia_align_data *data = align_data; + unsigned long start; + /* + * Ensure that we have the correct start address + */ + start = (res->start & ~data->mask) + data->offset; + if (start < res->start) + start += data->mask + 1; + res->start = start; +} + +static void +pcmcia_align(void *align_data, struct resource *res, + unsigned long size, unsigned long align) +{ + struct pcmcia_align_data *data = align_data; + struct resource_map *m; + + pcmcia_common_align(data, res, size, align); + + for (m = data->map->next; m != data->map; m = m->next) { + unsigned long start = m->base; + unsigned long end = m->base + m->num - 1; + + /* + * If the lower resources are not available, try aligning + * to this entry of the resource database to see if it'll + * fit here. + */ + if (res->start < start) { + res->start = start; + pcmcia_common_align(data, res, size, align); + } + + /* + * If we're above the area which was passed in, there's + * no point proceeding. + */ + if (res->start >= res->end) + break; + + if ((res->start + size - 1) <= end) + break; + } + + /* + * If we failed to find something suitable, ensure we fail. + */ + if (m == data->map) + res->start = res->end; +} + +/* + * Adjust an existing IO region allocation, but making sure that we don't + * encroach outside the resources which the user supplied. + */ +static int nonstatic_adjust_io_region(struct resource *res, unsigned long r_start, + unsigned long r_end, struct pcmcia_socket *s) +{ + struct resource_map *m; + struct socket_data *s_data = s->resource_data; + int ret = -ENOMEM; + + down(&rsrc_sem); + for (m = s_data->io_db.next; m != &s_data->io_db; m = m->next) { + unsigned long start = m->base; + unsigned long end = m->base + m->num - 1; + + if (start > r_start || r_end > end) + continue; + + ret = adjust_resource(res, r_start, r_end - r_start + 1); + break; + } + up(&rsrc_sem); + + return ret; +} + +/*====================================================================== + + These find ranges of I/O ports or memory addresses that are not + currently allocated by other devices. + + The 'align' field should reflect the number of bits of address + that need to be preserved from the initial value of *base. It + should be a power of two, greater than or equal to 'num'. A value + of 0 means that all bits of *base are significant. *base should + also be strictly less than 'align'. + +======================================================================*/ + +struct resource *nonstatic_find_io_region(unsigned long base, int num, + unsigned long align, struct pcmcia_socket *s) +{ + struct resource *res = make_resource(0, num, IORESOURCE_IO, s->dev.class_id); + struct socket_data *s_data = s->resource_data; + struct pcmcia_align_data data; + unsigned long min = base; + int ret; + + if (align == 0) + align = 0x10000; + + data.mask = align - 1; + data.offset = base & data.mask; + data.map = &s_data->io_db; + + down(&rsrc_sem); +#ifdef CONFIG_PCI + if (s->cb_dev) { + ret = pci_bus_alloc_resource(s->cb_dev->bus, res, num, 1, + min, 0, pcmcia_align, &data); + } else +#endif + ret = allocate_resource(&ioport_resource, res, num, min, ~0UL, + 1, pcmcia_align, &data); + up(&rsrc_sem); + + if (ret != 0) { + kfree(res); + res = NULL; + } + return res; +} + +struct resource * nonstatic_find_mem_region(u_long base, u_long num, u_long align, + int low, struct pcmcia_socket *s) +{ + struct resource *res = make_resource(0, num, IORESOURCE_MEM, s->dev.class_id); + struct socket_data *s_data = s->resource_data; + struct pcmcia_align_data data; + unsigned long min, max; + int ret, i; + + low = low || !(s->features & SS_CAP_PAGE_REGS); + + data.mask = align - 1; + data.offset = base & data.mask; + data.map = &s_data->mem_db; + + for (i = 0; i < 2; i++) { + if (low) { + max = 0x100000UL; + min = base < max ? base : 0; + } else { + max = ~0UL; + min = 0x100000UL + base; + } + + down(&rsrc_sem); +#ifdef CONFIG_PCI + if (s->cb_dev) { + ret = pci_bus_alloc_resource(s->cb_dev->bus, res, num, + 1, min, 0, + pcmcia_align, &data); + } else +#endif + ret = allocate_resource(&iomem_resource, res, num, min, + max, 1, pcmcia_align, &data); + up(&rsrc_sem); + if (ret == 0 || low) + break; + low = 1; + } + + if (ret != 0) { + kfree(res); + res = NULL; + } + return res; +} + + +static int adjust_memory(struct pcmcia_socket *s, adjust_t *adj) +{ + u_long base, num; + struct socket_data *data = s->resource_data; + int ret; + + base = adj->resource.memory.Base; + num = adj->resource.memory.Size; + if ((num == 0) || (base+num-1 < base)) + return CS_BAD_SIZE; + + ret = CS_SUCCESS; + + down(&rsrc_sem); + switch (adj->Action) { + case ADD_MANAGED_RESOURCE: + ret = add_interval(&data->mem_db, base, num); + break; + case REMOVE_MANAGED_RESOURCE: + ret = sub_interval(&data->mem_db, base, num); + if (ret == CS_SUCCESS) { + struct pcmcia_socket *socket; + down_read(&pcmcia_socket_list_rwsem); + list_for_each_entry(socket, &pcmcia_socket_list, socket_list) + release_cis_mem(socket); + up_read(&pcmcia_socket_list_rwsem); + } + break; + default: + ret = CS_UNSUPPORTED_FUNCTION; + } + up(&rsrc_sem); + + return ret; +} + + +static int adjust_io(struct pcmcia_socket *s, adjust_t *adj) +{ + struct socket_data *data = s->resource_data; + kio_addr_t base, num; + int ret = CS_SUCCESS; + + base = adj->resource.io.BasePort; + num = adj->resource.io.NumPorts; + if ((base < 0) || (base > 0xffff)) + return CS_BAD_BASE; + if ((num <= 0) || (base+num > 0x10000) || (base+num <= base)) + return CS_BAD_SIZE; + + down(&rsrc_sem); + switch (adj->Action) { + case ADD_MANAGED_RESOURCE: + if (add_interval(&data->io_db, base, num) != 0) { + ret = CS_IN_USE; + break; + } +#ifdef CONFIG_PCMCIA_PROBE + if (probe_io) + do_io_probe(s, base, num); +#endif + break; + case REMOVE_MANAGED_RESOURCE: + sub_interval(&data->io_db, base, num); + break; + default: + ret = CS_UNSUPPORTED_FUNCTION; + break; + } + up(&rsrc_sem); + + return ret; +} + + +static int nonstatic_adjust_resource_info(struct pcmcia_socket *s, adjust_t *adj) +{ + switch (adj->Resource) { + case RES_MEMORY_RANGE: + return adjust_memory(s, adj); + case RES_IO_RANGE: + return adjust_io(s, adj); + } + return CS_UNSUPPORTED_FUNCTION; +} + +static int nonstatic_init(struct pcmcia_socket *s) +{ + struct socket_data *data; + + data = kmalloc(sizeof(struct socket_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + memset(data, 0, sizeof(struct socket_data)); + + data->mem_db.next = &data->mem_db; + data->io_db.next = &data->io_db; + + s->resource_data = (void *) data; + + return 0; +} + +static void nonstatic_release_resource_db(struct pcmcia_socket *s) +{ + struct socket_data *data = s->resource_data; + struct resource_map *p, *q; + + down(&rsrc_sem); + for (p = data->mem_db.next; p != &data->mem_db; p = q) { + q = p->next; + kfree(p); + } + for (p = data->io_db.next; p != &data->io_db; p = q) { + q = p->next; + kfree(p); + } + up(&rsrc_sem); +} + + +struct pccard_resource_ops pccard_nonstatic_ops = { + .validate_mem = pcmcia_nonstatic_validate_mem, + .adjust_io_region = nonstatic_adjust_io_region, + .find_io = nonstatic_find_io_region, + .find_mem = nonstatic_find_mem_region, + .adjust_resource = nonstatic_adjust_resource_info, + .init = nonstatic_init, + .exit = nonstatic_release_resource_db, +}; +EXPORT_SYMBOL(pccard_nonstatic_ops); + + +/* sysfs interface to the resource database */ + +static ssize_t show_io_db(struct class_device *class_dev, char *buf) +{ + struct pcmcia_socket *s = class_get_devdata(class_dev); + struct socket_data *data; + struct resource_map *p; + ssize_t ret = 0; + + down(&rsrc_sem); + data = s->resource_data; + + for (p = data->io_db.next; p != &data->io_db; p = p->next) { + if (ret > (PAGE_SIZE - 10)) + continue; + ret += snprintf (&buf[ret], (PAGE_SIZE - ret - 1), + "0x%08lx - 0x%08lx\n", + ((unsigned long) p->base), + ((unsigned long) p->base + p->num - 1)); + } + + up(&rsrc_sem); + return (ret); +} + +static ssize_t store_io_db(struct class_device *class_dev, const char *buf, size_t count) +{ + struct pcmcia_socket *s = class_get_devdata(class_dev); + unsigned long start_addr, end_addr; + unsigned int add = 1; + adjust_t adj; + ssize_t ret = 0; + + ret = sscanf (buf, "+ 0x%lx - 0x%lx", &start_addr, &end_addr); + if (ret != 2) { + ret = sscanf (buf, "- 0x%lx - 0x%lx", &start_addr, &end_addr); + add = 0; + if (ret != 2) { + ret = sscanf (buf, "0x%lx - 0x%lx", &start_addr, &end_addr); + add = 1; + if (ret != 2) + return -EINVAL; + } + } + if (end_addr <= start_addr) + return -EINVAL; + + adj.Action = add ? ADD_MANAGED_RESOURCE : REMOVE_MANAGED_RESOURCE; + adj.Resource = RES_IO_RANGE; + adj.resource.io.BasePort = start_addr; + adj.resource.io.NumPorts = end_addr - start_addr + 1; + + ret = adjust_io(s, &adj); + + return ret ? ret : count; +} +static CLASS_DEVICE_ATTR(available_resources_io, 0600, show_io_db, store_io_db); + +static ssize_t show_mem_db(struct class_device *class_dev, char *buf) +{ + struct pcmcia_socket *s = class_get_devdata(class_dev); + struct socket_data *data; + struct resource_map *p; + ssize_t ret = 0; + + down(&rsrc_sem); + data = s->resource_data; + + for (p = data->mem_db.next; p != &data->mem_db; p = p->next) { + if (ret > (PAGE_SIZE - 10)) + continue; + ret += snprintf (&buf[ret], (PAGE_SIZE - ret - 1), + "0x%08lx - 0x%08lx\n", + ((unsigned long) p->base), + ((unsigned long) p->base + p->num - 1)); + } + + up(&rsrc_sem); + return (ret); +} + +static ssize_t store_mem_db(struct class_device *class_dev, const char *buf, size_t count) +{ + struct pcmcia_socket *s = class_get_devdata(class_dev); + unsigned long start_addr, end_addr; + unsigned int add = 1; + adjust_t adj; + ssize_t ret = 0; + + ret = sscanf (buf, "+ 0x%lx - 0x%lx", &start_addr, &end_addr); + if (ret != 2) { + ret = sscanf (buf, "- 0x%lx - 0x%lx", &start_addr, &end_addr); + add = 0; + if (ret != 2) { + ret = sscanf (buf, "0x%lx - 0x%lx", &start_addr, &end_addr); + add = 1; + if (ret != 2) + return -EINVAL; + } + } + if (end_addr <= start_addr) + return -EINVAL; + + adj.Action = add ? ADD_MANAGED_RESOURCE : REMOVE_MANAGED_RESOURCE; + adj.Resource = RES_MEMORY_RANGE; + adj.resource.memory.Base = start_addr; + adj.resource.memory.Size = end_addr - start_addr + 1; + + ret = adjust_memory(s, &adj); + + return ret ? ret : count; +} +static CLASS_DEVICE_ATTR(available_resources_mem, 0600, show_mem_db, store_mem_db); + +static struct class_device_attribute *pccard_rsrc_attributes[] = { + &class_device_attr_available_resources_io, + &class_device_attr_available_resources_mem, + NULL, +}; + +static int __devinit pccard_sysfs_add_rsrc(struct class_device *class_dev) +{ + struct pcmcia_socket *s = class_get_devdata(class_dev); + struct class_device_attribute **attr; + int ret = 0; + if (s->resource_ops != &pccard_nonstatic_ops) + return 0; + + for (attr = pccard_rsrc_attributes; *attr; attr++) { + ret = class_device_create_file(class_dev, *attr); + if (ret) + break; + } + + return ret; +} + +static void __devexit pccard_sysfs_remove_rsrc(struct class_device *class_dev) +{ + struct pcmcia_socket *s = class_get_devdata(class_dev); + struct class_device_attribute **attr; + + if (s->resource_ops != &pccard_nonstatic_ops) + return; + + for (attr = pccard_rsrc_attributes; *attr; attr++) + class_device_remove_file(class_dev, *attr); +} + +static struct class_interface pccard_rsrc_interface = { + .class = &pcmcia_socket_class, + .add = &pccard_sysfs_add_rsrc, + .remove = __devexit_p(&pccard_sysfs_remove_rsrc), +}; + +static int __init nonstatic_sysfs_init(void) +{ + return class_interface_register(&pccard_rsrc_interface); +} + +static void __exit nonstatic_sysfs_exit(void) +{ + class_interface_unregister(&pccard_rsrc_interface); +} + +module_init(nonstatic_sysfs_init); +module_exit(nonstatic_sysfs_exit); diff --git a/drivers/pcmcia/sa1100_assabet.c b/drivers/pcmcia/sa1100_assabet.c new file mode 100644 index 000000000000..7c57fdd3c8d7 --- /dev/null +++ b/drivers/pcmcia/sa1100_assabet.c @@ -0,0 +1,141 @@ +/* + * drivers/pcmcia/sa1100_assabet.c + * + * PCMCIA implementation routines for Assabet + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/init.h> + +#include <asm/hardware.h> +#include <asm/mach-types.h> +#include <asm/irq.h> +#include <asm/signal.h> +#include <asm/arch/assabet.h> + +#include "sa1100_generic.h" + +static struct pcmcia_irqs irqs[] = { + { 1, ASSABET_IRQ_GPIO_CF_CD, "CF CD" }, + { 1, ASSABET_IRQ_GPIO_CF_BVD2, "CF BVD2" }, + { 1, ASSABET_IRQ_GPIO_CF_BVD1, "CF BVD1" }, +}; + +static int assabet_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + skt->irq = ASSABET_IRQ_GPIO_CF_IRQ; + + return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +/* + * Release all resources. + */ +static void assabet_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static void +assabet_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) +{ + unsigned long levels = GPLR; + + state->detect = (levels & ASSABET_GPIO_CF_CD) ? 0 : 1; + state->ready = (levels & ASSABET_GPIO_CF_IRQ) ? 1 : 0; + state->bvd1 = (levels & ASSABET_GPIO_CF_BVD1) ? 1 : 0; + state->bvd2 = (levels & ASSABET_GPIO_CF_BVD2) ? 1 : 0; + state->wrprot = 0; /* Not available on Assabet. */ + state->vs_3v = 1; /* Can only apply 3.3V on Assabet. */ + state->vs_Xv = 0; +} + +static int +assabet_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_t *state) +{ + unsigned int mask; + + switch (state->Vcc) { + case 0: + mask = 0; + break; + + case 50: + printk(KERN_WARNING "%s(): CS asked for 5V, applying 3.3V...\n", + __FUNCTION__); + + case 33: /* Can only apply 3.3V to the CF slot. */ + mask = ASSABET_BCR_CF_PWR; + break; + + default: + printk(KERN_ERR "%s(): unrecognized Vcc %u\n", __FUNCTION__, + state->Vcc); + return -1; + } + + /* Silently ignore Vpp, output enable, speaker enable. */ + + if (state->flags & SS_RESET) + mask |= ASSABET_BCR_CF_RST; + + ASSABET_BCR_frob(ASSABET_BCR_CF_RST | ASSABET_BCR_CF_PWR, mask); + + return 0; +} + +/* + * Enable card status IRQs on (re-)initialisation. This can + * be called at initialisation, power management event, or + * pcmcia event. + */ +static void assabet_pcmcia_socket_init(struct soc_pcmcia_socket *skt) +{ + /* + * Enable CF bus + */ + ASSABET_BCR_clear(ASSABET_BCR_CF_BUS_OFF); + + soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +/* + * Disable card status IRQs on suspend. + */ +static void assabet_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs)); + + /* + * Tristate the CF bus signals. Also assert CF + * reset as per user guide page 4-11. + */ + ASSABET_BCR_set(ASSABET_BCR_CF_BUS_OFF | ASSABET_BCR_CF_RST); +} + +static struct pcmcia_low_level assabet_pcmcia_ops = { + .owner = THIS_MODULE, + + .hw_init = assabet_pcmcia_hw_init, + .hw_shutdown = assabet_pcmcia_hw_shutdown, + + .socket_state = assabet_pcmcia_socket_state, + .configure_socket = assabet_pcmcia_configure_socket, + + .socket_init = assabet_pcmcia_socket_init, + .socket_suspend = assabet_pcmcia_socket_suspend, +}; + +int __init pcmcia_assabet_init(struct device *dev) +{ + int ret = -ENODEV; + + if (machine_is_assabet() && !machine_has_neponset()) + ret = sa11xx_drv_pcmcia_probe(dev, &assabet_pcmcia_ops, 1, 1); + + return ret; +} diff --git a/drivers/pcmcia/sa1100_badge4.c b/drivers/pcmcia/sa1100_badge4.c new file mode 100644 index 000000000000..c6b262b653d3 --- /dev/null +++ b/drivers/pcmcia/sa1100_badge4.c @@ -0,0 +1,169 @@ +/* + * linux/drivers/pcmcia/sa1100_badge4.c + * + * BadgePAD 4 PCMCIA specific routines + * + * Christopher Hoover <ch@hpl.hp.com> + * + * Copyright (C) 2002 Hewlett-Packard Company + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/init.h> + +#include <asm/hardware.h> +#include <asm/mach-types.h> +#include <asm/arch/badge4.h> +#include <asm/hardware/sa1111.h> + +#include "sa1111_generic.h" + +/* + * BadgePAD 4 Details + * + * PCM Vcc: + * + * PCM Vcc on BadgePAD 4 can be jumpered for 3v3 (short pins 1 and 3 + * on JP6) or 5v0 (short pins 3 and 5 on JP6). + * + * PCM Vpp: + * + * PCM Vpp on BadgePAD 4 can be jumpered for 12v0 (short pins 4 and 6 + * on JP6) or tied to PCM Vcc (short pins 2 and 4 on JP6). N.B., + * 12v0 operation requires that the power supply actually supply 12v0 + * via pin 7 of JP7. + * + * CF Vcc: + * + * CF Vcc on BadgePAD 4 can be jumpered either for 3v3 (short pins 1 + * and 2 on JP10) or 5v0 (short pins 2 and 3 on JP10). + * + * Unfortunately there's no way programmatically to determine how a + * given board is jumpered. This code assumes a default jumpering + * as described below. + * + * If the defaults aren't correct, you may override them with a pcmv + * setup argument: pcmv=<pcm vcc>,<pcm vpp>,<cf vcc>. The units are + * tenths of volts; e.g. pcmv=33,120,50 indicates 3v3 PCM Vcc, 12v0 + * PCM Vpp, and 5v0 CF Vcc. + * + */ + +static int badge4_pcmvcc = 50; /* pins 3 and 5 jumpered on JP6 */ +static int badge4_pcmvpp = 50; /* pins 2 and 4 jumpered on JP6 */ +static int badge4_cfvcc = 33; /* pins 1 and 2 jumpered on JP10 */ + +static void complain_about_jumpering(const char *whom, + const char *supply, + int given, int wanted) +{ + printk(KERN_ERR + "%s: %s %d.%dV wanted but board is jumpered for %s %d.%dV operation" + "; re-jumper the board and/or use pcmv=xx,xx,xx\n", + whom, supply, + wanted / 10, wanted % 10, + supply, + given / 10, given % 10); +} + +static int +badge4_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_t *state) +{ + int ret; + + switch (skt->nr) { + case 0: + if ((state->Vcc != 0) && + (state->Vcc != badge4_pcmvcc)) { + complain_about_jumpering(__FUNCTION__, "pcmvcc", + badge4_pcmvcc, state->Vcc); + // Apply power regardless of the jumpering. + // return -1; + } + if ((state->Vpp != 0) && + (state->Vpp != badge4_pcmvpp)) { + complain_about_jumpering(__FUNCTION__, "pcmvpp", + badge4_pcmvpp, state->Vpp); + return -1; + } + break; + + case 1: + if ((state->Vcc != 0) && + (state->Vcc != badge4_cfvcc)) { + complain_about_jumpering(__FUNCTION__, "cfvcc", + badge4_cfvcc, state->Vcc); + return -1; + } + break; + + default: + return -1; + } + + ret = sa1111_pcmcia_configure_socket(skt, state); + if (ret == 0) { + unsigned long flags; + int need5V; + + local_irq_save(flags); + + need5V = ((state->Vcc == 50) || (state->Vpp == 50)); + + badge4_set_5V(BADGE4_5V_PCMCIA_SOCK(skt->nr), need5V); + + local_irq_restore(flags); + } + + return 0; +} + +static struct pcmcia_low_level badge4_pcmcia_ops = { + .owner = THIS_MODULE, + .init = sa1111_pcmcia_hw_init, + .shutdown = sa1111_pcmcia_hw_shutdown, + .socket_state = sa1111_pcmcia_socket_state, + .configure_socket = badge4_pcmcia_configure_socket, + + .socket_init = sa1111_pcmcia_socket_init, + .socket_suspend = sa1111_pcmcia_socket_suspend, +}; + +int pcmcia_badge4_init(struct device *dev) +{ + int ret = -ENODEV; + + if (machine_is_badge4()) { + printk(KERN_INFO + "%s: badge4_pcmvcc=%d, badge4_pcmvpp=%d, badge4_cfvcc=%d\n", + __FUNCTION__, + badge4_pcmvcc, badge4_pcmvpp, badge4_cfvcc); + + ret = sa11xx_drv_pcmcia_probe(dev, &badge4_pcmcia_ops, 0, 2); + } + + return ret; +} + +static int __init pcmv_setup(char *s) +{ + int v[4]; + + s = get_options(s, ARRAY_SIZE(v), v); + + if (v[0] >= 1) badge4_pcmvcc = v[1]; + if (v[0] >= 2) badge4_pcmvpp = v[2]; + if (v[0] >= 3) badge4_cfvcc = v[3]; + + return 1; +} + +__setup("pcmv=", pcmv_setup); diff --git a/drivers/pcmcia/sa1100_cerf.c b/drivers/pcmcia/sa1100_cerf.c new file mode 100644 index 000000000000..2b3c2895b43d --- /dev/null +++ b/drivers/pcmcia/sa1100_cerf.c @@ -0,0 +1,110 @@ +/* + * drivers/pcmcia/sa1100_cerf.c + * + * PCMCIA implementation routines for CerfBoard + * Based off the Assabet. + * + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include <asm/hardware.h> +#include <asm/mach-types.h> +#include <asm/irq.h> +#include <asm/arch/cerf.h> +#include "sa1100_generic.h" + +#define CERF_SOCKET 1 + +static struct pcmcia_irqs irqs[] = { + { CERF_SOCKET, CERF_IRQ_GPIO_CF_CD, "CF_CD" }, + { CERF_SOCKET, CERF_IRQ_GPIO_CF_BVD2, "CF_BVD2" }, + { CERF_SOCKET, CERF_IRQ_GPIO_CF_BVD1, "CF_BVD1" } +}; + +static int cerf_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + skt->irq = CERF_IRQ_GPIO_CF_IRQ; + + return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static void cerf_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static void +cerf_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) +{ + unsigned long levels = GPLR; + + state->detect = (levels & CERF_GPIO_CF_CD) ?0:1; + state->ready = (levels & CERF_GPIO_CF_IRQ) ?1:0; + state->bvd1 = (levels & CERF_GPIO_CF_BVD1)?1:0; + state->bvd2 = (levels & CERF_GPIO_CF_BVD2)?1:0; + state->wrprot = 0; + state->vs_3v = 1; + state->vs_Xv = 0; +} + +static int +cerf_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, + const socket_state_t *state) +{ + switch (state->Vcc) { + case 0: + case 50: + case 33: + break; + + default: + printk(KERN_ERR "%s(): unrecognized Vcc %u\n", + __FUNCTION__, state->Vcc); + return -1; + } + + if (state->flags & SS_RESET) { + GPSR = CERF_GPIO_CF_RESET; + } else { + GPCR = CERF_GPIO_CF_RESET; + } + + return 0; +} + +static void cerf_pcmcia_socket_init(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static void cerf_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static struct pcmcia_low_level cerf_pcmcia_ops = { + .owner = THIS_MODULE, + .hw_init = cerf_pcmcia_hw_init, + .hw_shutdown = cerf_pcmcia_hw_shutdown, + .socket_state = cerf_pcmcia_socket_state, + .configure_socket = cerf_pcmcia_configure_socket, + + .socket_init = cerf_pcmcia_socket_init, + .socket_suspend = cerf_pcmcia_socket_suspend, +}; + +int __init pcmcia_cerf_init(struct device *dev) +{ + int ret = -ENODEV; + + if (machine_is_cerf()) + ret = sa11xx_drv_pcmcia_probe(dev, &cerf_pcmcia_ops, CERF_SOCKET, 1); + + return ret; +} diff --git a/drivers/pcmcia/sa1100_generic.c b/drivers/pcmcia/sa1100_generic.c new file mode 100644 index 000000000000..874990ebe147 --- /dev/null +++ b/drivers/pcmcia/sa1100_generic.c @@ -0,0 +1,131 @@ +/*====================================================================== + + Device driver for the PCMCIA control functionality of StrongARM + SA-1100 microprocessors. + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is John G. Dorsey + <john+@cs.cmu.edu>. Portions created by John G. Dorsey are + Copyright (C) 1999 John G. Dorsey. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU Public License version 2 (the "GPL"), in which + case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/config.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/ss.h> + +#include "sa1100_generic.h" + +static int (*sa11x0_pcmcia_hw_init[])(struct device *dev) = { +#ifdef CONFIG_SA1100_ASSABET + pcmcia_assabet_init, +#endif +#ifdef CONFIG_SA1100_CERF + pcmcia_cerf_init, +#endif +#ifdef CONFIG_SA1100_H3600 + pcmcia_h3600_init, +#endif +#ifdef CONFIG_SA1100_SHANNON + pcmcia_shannon_init, +#endif +#ifdef CONFIG_SA1100_SIMPAD + pcmcia_simpad_init, +#endif +}; + +static int sa11x0_drv_pcmcia_probe(struct device *dev) +{ + int i, ret = -ENODEV; + + /* + * Initialise any "on-board" PCMCIA sockets. + */ + for (i = 0; i < ARRAY_SIZE(sa11x0_pcmcia_hw_init); i++) { + ret = sa11x0_pcmcia_hw_init[i](dev); + if (ret == 0) + break; + } + + return ret; +} + +static int sa11x0_drv_pcmcia_suspend(struct device *dev, u32 state, u32 level) +{ + int ret = 0; + if (level == SUSPEND_SAVE_STATE) + ret = pcmcia_socket_dev_suspend(dev, state); + return ret; +} + +static int sa11x0_drv_pcmcia_resume(struct device *dev, u32 level) +{ + int ret = 0; + if (level == RESUME_RESTORE_STATE) + ret = pcmcia_socket_dev_resume(dev); + return ret; +} + +static struct device_driver sa11x0_pcmcia_driver = { + .probe = sa11x0_drv_pcmcia_probe, + .remove = soc_common_drv_pcmcia_remove, + .name = "sa11x0-pcmcia", + .bus = &platform_bus_type, + .suspend = sa11x0_drv_pcmcia_suspend, + .resume = sa11x0_drv_pcmcia_resume, +}; + +/* sa11x0_pcmcia_init() + * ^^^^^^^^^^^^^^^^^^^^ + * + * This routine performs low-level PCMCIA initialization and then + * registers this socket driver with Card Services. + * + * Returns: 0 on success, -ve error code on failure + */ +static int __init sa11x0_pcmcia_init(void) +{ + return driver_register(&sa11x0_pcmcia_driver); +} + +/* sa11x0_pcmcia_exit() + * ^^^^^^^^^^^^^^^^^^^^ + * Invokes the low-level kernel service to free IRQs associated with this + * socket controller and reset GPIO edge detection. + */ +static void __exit sa11x0_pcmcia_exit(void) +{ + driver_unregister(&sa11x0_pcmcia_driver); +} + +MODULE_AUTHOR("John Dorsey <john+@cs.cmu.edu>"); +MODULE_DESCRIPTION("Linux PCMCIA Card Services: SA-11x0 Socket Controller"); +MODULE_LICENSE("Dual MPL/GPL"); + +module_init(sa11x0_pcmcia_init); +module_exit(sa11x0_pcmcia_exit); diff --git a/drivers/pcmcia/sa1100_generic.h b/drivers/pcmcia/sa1100_generic.h new file mode 100644 index 000000000000..794f96a35bba --- /dev/null +++ b/drivers/pcmcia/sa1100_generic.h @@ -0,0 +1,24 @@ +#include "soc_common.h" +#include "sa11xx_base.h" + +/* + * Declaration for all machine specific init/exit functions. + */ +extern int pcmcia_adsbitsy_init(struct device *); +extern int pcmcia_assabet_init(struct device *); +extern int pcmcia_badge4_init(struct device *); +extern int pcmcia_cerf_init(struct device *); +extern int pcmcia_flexanet_init(struct device *); +extern int pcmcia_freebird_init(struct device *); +extern int pcmcia_gcplus_init(struct device *); +extern int pcmcia_graphicsmaster_init(struct device *); +extern int pcmcia_h3600_init(struct device *); +extern int pcmcia_pangolin_init(struct device *); +extern int pcmcia_pfs168_init(struct device *); +extern int pcmcia_shannon_init(struct device *); +extern int pcmcia_simpad_init(struct device *); +extern int pcmcia_stork_init(struct device *); +extern int pcmcia_system3_init(struct device *); +extern int pcmcia_trizeps_init(struct device *); +extern int pcmcia_xp860_init(struct device *); +extern int pcmcia_yopy_init(struct device *); diff --git a/drivers/pcmcia/sa1100_h3600.c b/drivers/pcmcia/sa1100_h3600.c new file mode 100644 index 000000000000..64fd5e37f2d2 --- /dev/null +++ b/drivers/pcmcia/sa1100_h3600.c @@ -0,0 +1,142 @@ +/* + * drivers/pcmcia/sa1100_h3600.c + * + * PCMCIA implementation routines for H3600 + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/delay.h> + +#include <asm/hardware.h> +#include <asm/irq.h> +#include <asm/mach-types.h> +#include <asm/arch/h3600.h> + +#include "sa1100_generic.h" + +static struct pcmcia_irqs irqs[] = { + { 0, IRQ_GPIO_H3600_PCMCIA_CD0, "PCMCIA CD0" }, + { 1, IRQ_GPIO_H3600_PCMCIA_CD1, "PCMCIA CD1" } +}; + +static int h3600_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + skt->irq = skt->nr ? IRQ_GPIO_H3600_PCMCIA_IRQ1 + : IRQ_GPIO_H3600_PCMCIA_IRQ0; + + + return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static void h3600_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); + + /* Disable CF bus: */ + clr_h3600_egpio(IPAQ_EGPIO_OPT_NVRAM_ON); + clr_h3600_egpio(IPAQ_EGPIO_OPT_ON); + set_h3600_egpio(IPAQ_EGPIO_OPT_RESET); +} + +static void +h3600_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) +{ + unsigned long levels = GPLR; + + switch (skt->nr) { + case 0: + state->detect = levels & GPIO_H3600_PCMCIA_CD0 ? 0 : 1; + state->ready = levels & GPIO_H3600_PCMCIA_IRQ0 ? 1 : 0; + state->bvd1 = 0; + state->bvd2 = 0; + state->wrprot = 0; /* Not available on H3600. */ + state->vs_3v = 0; + state->vs_Xv = 0; + break; + + case 1: + state->detect = levels & GPIO_H3600_PCMCIA_CD1 ? 0 : 1; + state->ready = levels & GPIO_H3600_PCMCIA_IRQ1 ? 1 : 0; + state->bvd1 = 0; + state->bvd2 = 0; + state->wrprot = 0; /* Not available on H3600. */ + state->vs_3v = 0; + state->vs_Xv = 0; + break; + } +} + +static int +h3600_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_t *state) +{ + if (state->Vcc != 0 && state->Vcc != 33 && state->Vcc != 50) { + printk(KERN_ERR "h3600_pcmcia: unrecognized Vcc %u.%uV\n", + state->Vcc / 10, state->Vcc % 10); + return -1; + } + + if (state->flags & SS_RESET) + set_h3600_egpio(IPAQ_EGPIO_CARD_RESET); + else + clr_h3600_egpio(IPAQ_EGPIO_CARD_RESET); + + /* Silently ignore Vpp, output enable, speaker enable. */ + + return 0; +} + +static void h3600_pcmcia_socket_init(struct soc_pcmcia_socket *skt) +{ + /* Enable CF bus: */ + set_h3600_egpio(IPAQ_EGPIO_OPT_NVRAM_ON); + set_h3600_egpio(IPAQ_EGPIO_OPT_ON); + clr_h3600_egpio(IPAQ_EGPIO_OPT_RESET); + + msleep(10); + + soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static void h3600_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs)); + + /* + * FIXME: This doesn't fit well. We don't have the mechanism in + * the generic PCMCIA layer to deal with the idea of two sockets + * on one bus. We rely on the cs.c behaviour shutting down + * socket 0 then socket 1. + */ + if (skt->nr == 1) { + clr_h3600_egpio(IPAQ_EGPIO_OPT_ON); + clr_h3600_egpio(IPAQ_EGPIO_OPT_NVRAM_ON); + /* hmm, does this suck power? */ + set_h3600_egpio(IPAQ_EGPIO_OPT_RESET); + } +} + +struct pcmcia_low_level h3600_pcmcia_ops = { + .owner = THIS_MODULE, + .hw_init = h3600_pcmcia_hw_init, + .hw_shutdown = h3600_pcmcia_hw_shutdown, + .socket_state = h3600_pcmcia_socket_state, + .configure_socket = h3600_pcmcia_configure_socket, + + .socket_init = h3600_pcmcia_socket_init, + .socket_suspend = h3600_pcmcia_socket_suspend, +}; + +int __init pcmcia_h3600_init(struct device *dev) +{ + int ret = -ENODEV; + + if (machine_is_h3600()) + ret = sa11xx_drv_pcmcia_probe(dev, &h3600_pcmcia_ops, 0, 2); + + return ret; +} diff --git a/drivers/pcmcia/sa1100_jornada720.c b/drivers/pcmcia/sa1100_jornada720.c new file mode 100644 index 000000000000..0a387106acb0 --- /dev/null +++ b/drivers/pcmcia/sa1100_jornada720.c @@ -0,0 +1,124 @@ +/* + * drivers/pcmcia/sa1100_jornada720.c + * + * Jornada720 PCMCIA specific routines + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/init.h> + +#include <asm/hardware.h> +#include <asm/hardware/sa1111.h> +#include <asm/mach-types.h> + +#include "sa1111_generic.h" + +#define SOCKET0_POWER GPIO_GPIO0 +#define SOCKET0_3V GPIO_GPIO2 +#define SOCKET1_POWER (GPIO_GPIO1 | GPIO_GPIO3) +#warning *** Does SOCKET1_3V actually do anything? +#define SOCKET1_3V GPIO_GPIO3 + +static int jornada720_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + /* + * What is all this crap for? + */ + GRER |= 0x00000002; + /* Set GPIO_A<3:1> to be outputs for PCMCIA/CF power controller: */ + PA_DDR = 0; + PA_DWR = 0; + PA_SDR = 0; + PA_SSR = 0; + + PB_DDR = 0; + PB_DWR = 0x01; + PB_SDR = 0; + PB_SSR = 0; + + PC_DDR = 0x88; + PC_DWR = 0x20; + PC_SDR = 0; + PC_SSR = 0; + + return sa1111_pcmcia_hw_init(skt); +} + +static int +jornada720_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_t *state) +{ + unsigned int pa_dwr_mask, pa_dwr_set; + int ret; + +printk("%s(): config socket %d vcc %d vpp %d\n", __FUNCTION__, + skt->nr, state->Vcc, state->Vpp); + + switch (skt->nr) { + case 0: + pa_dwr_mask = SOCKET0_POWER | SOCKET0_3V; + + switch (state->Vcc) { + default: + case 0: pa_dwr_set = 0; break; + case 33: pa_dwr_set = SOCKET0_POWER | SOCKET0_3V; break; + case 50: pa_dwr_set = SOCKET0_POWER; break; + } + break; + + case 1: + pa_dwr_mask = SOCKET1_POWER; + + switch (state->Vcc) { + default: + case 0: pa_dwr_set = 0; break; + case 33: pa_dwr_set = SOCKET1_POWER; break; + case 50: pa_dwr_set = SOCKET1_POWER; break; + } + break; + + default: + return -1; + } + + if (state->Vpp != state->Vcc && state->Vpp != 0) { + printk(KERN_ERR "%s(): slot cannot support VPP %u\n", + __FUNCTION__, state->Vpp); + return -1; + } + + ret = sa1111_pcmcia_configure_socket(skt, state); + if (ret == 0) { + unsigned long flags; + + local_irq_save(flags); + PA_DWR = (PA_DWR & ~pa_dwr_mask) | pa_dwr_set; + local_irq_restore(flags); + } + + return ret; +} + +static struct pcmcia_low_level jornada720_pcmcia_ops = { + .owner = THIS_MODULE, + .hw_init = jornada720_pcmcia_hw_init, + .hw_shutdown = sa1111_pcmcia_hw_shutdown, + .socket_state = sa1111_pcmcia_socket_state, + .configure_socket = jornada720_pcmcia_configure_socket, + + .socket_init = sa1111_pcmcia_socket_init, + .socket_suspend = sa1111_pcmcia_socket_suspend, +}; + +int __init pcmcia_jornada720_init(struct device *dev) +{ + int ret = -ENODEV; + + if (machine_is_jornada720()) + ret = sa11xx_drv_pcmcia_probe(dev, &jornada720_pcmcia_ops, 0, 2); + + return ret; +} diff --git a/drivers/pcmcia/sa1100_neponset.c b/drivers/pcmcia/sa1100_neponset.c new file mode 100644 index 000000000000..5e34b3e8e5db --- /dev/null +++ b/drivers/pcmcia/sa1100_neponset.c @@ -0,0 +1,143 @@ +/* + * linux/drivers/pcmcia/sa1100_neponset.c + * + * Neponset PCMCIA specific routines + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/init.h> + +#include <asm/hardware.h> +#include <asm/mach-types.h> +#include <asm/arch/neponset.h> +#include <asm/hardware/sa1111.h> + +#include "sa1111_generic.h" + +/* + * Neponset uses the Maxim MAX1600, with the following connections: + * + * MAX1600 Neponset + * + * A0VCC SA-1111 GPIO A<1> + * A1VCC SA-1111 GPIO A<0> + * A0VPP CPLD NCR A0VPP + * A1VPP CPLD NCR A1VPP + * B0VCC SA-1111 GPIO A<2> + * B1VCC SA-1111 GPIO A<3> + * B0VPP ground (slot B is CF) + * B1VPP ground (slot B is CF) + * + * VX VCC (5V) + * VY VCC3_3 (3.3V) + * 12INA 12V + * 12INB ground (slot B is CF) + * + * The MAX1600 CODE pin is tied to ground, placing the device in + * "Standard Intel code" mode. Refer to the Maxim data sheet for + * the corresponding truth table. + */ + +static int +neponset_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_t *state) +{ + unsigned int ncr_mask, ncr_set, pa_dwr_mask, pa_dwr_set; + int ret; + + switch (skt->nr) { + case 0: + pa_dwr_mask = GPIO_A0 | GPIO_A1; + ncr_mask = NCR_A0VPP | NCR_A1VPP; + + if (state->Vpp == 0) + ncr_set = 0; + else if (state->Vpp == 120) + ncr_set = NCR_A1VPP; + else if (state->Vpp == state->Vcc) + ncr_set = NCR_A0VPP; + else { + printk(KERN_ERR "%s(): unrecognized VPP %u\n", + __FUNCTION__, state->Vpp); + return -1; + } + break; + + case 1: + pa_dwr_mask = GPIO_A2 | GPIO_A3; + ncr_mask = 0; + ncr_set = 0; + + if (state->Vpp != state->Vcc && state->Vpp != 0) { + printk(KERN_ERR "%s(): CF slot cannot support VPP %u\n", + __FUNCTION__, state->Vpp); + return -1; + } + break; + + default: + return -1; + } + + /* + * pa_dwr_set is the mask for selecting Vcc on both sockets. + * pa_dwr_mask selects which bits (and therefore socket) we change. + */ + switch (state->Vcc) { + default: + case 0: pa_dwr_set = 0; break; + case 33: pa_dwr_set = GPIO_A1|GPIO_A2; break; + case 50: pa_dwr_set = GPIO_A0|GPIO_A3; break; + } + + ret = sa1111_pcmcia_configure_socket(skt, state); + if (ret == 0) { + unsigned long flags; + + local_irq_save(flags); + NCR_0 = (NCR_0 & ~ncr_mask) | ncr_set; + + local_irq_restore(flags); + sa1111_set_io(SA1111_DEV(skt->dev), pa_dwr_mask, pa_dwr_set); + } + + return 0; +} + +static void neponset_pcmcia_socket_init(struct soc_pcmcia_socket *skt) +{ + if (skt->nr == 0) + NCR_0 &= ~(NCR_A0VPP | NCR_A1VPP); + + sa1111_pcmcia_socket_init(skt); +} + +static struct pcmcia_low_level neponset_pcmcia_ops = { + .owner = THIS_MODULE, + .hw_init = sa1111_pcmcia_hw_init, + .hw_shutdown = sa1111_pcmcia_hw_shutdown, + .socket_state = sa1111_pcmcia_socket_state, + .configure_socket = neponset_pcmcia_configure_socket, + .socket_init = neponset_pcmcia_socket_init, + .socket_suspend = sa1111_pcmcia_socket_suspend, +}; + +int __init pcmcia_neponset_init(struct sa1111_dev *sadev) +{ + int ret = -ENODEV; + + if (machine_is_assabet()) { + /* + * Set GPIO_A<3:0> to be outputs for the MAX1600, + * and switch to standby mode. + */ + sa1111_set_io_dir(sadev, GPIO_A0|GPIO_A1|GPIO_A2|GPIO_A3, 0, 0); + sa1111_set_io(sadev, GPIO_A0|GPIO_A1|GPIO_A2|GPIO_A3, 0); + sa1111_set_sleep_io(sadev, GPIO_A0|GPIO_A1|GPIO_A2|GPIO_A3, 0); + ret = sa11xx_drv_pcmcia_probe(&sadev->dev, &neponset_pcmcia_ops, 0, 2); + } + + return ret; +} diff --git a/drivers/pcmcia/sa1100_shannon.c b/drivers/pcmcia/sa1100_shannon.c new file mode 100644 index 000000000000..7bc9e59c761f --- /dev/null +++ b/drivers/pcmcia/sa1100_shannon.c @@ -0,0 +1,125 @@ +/* + * drivers/pcmcia/sa1100_shannon.c + * + * PCMCIA implementation routines for Shannon + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/device.h> +#include <linux/init.h> + +#include <asm/hardware.h> +#include <asm/mach-types.h> +#include <asm/arch/shannon.h> +#include <asm/irq.h> +#include "sa1100_generic.h" + +static struct pcmcia_irqs irqs[] = { + { 0, SHANNON_IRQ_GPIO_EJECT_0, "PCMCIA_CD_0" }, + { 1, SHANNON_IRQ_GPIO_EJECT_1, "PCMCIA_CD_1" }, +}; + +static int shannon_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + /* All those are inputs */ + GPDR &= ~(SHANNON_GPIO_EJECT_0 | SHANNON_GPIO_EJECT_1 | + SHANNON_GPIO_RDY_0 | SHANNON_GPIO_RDY_1); + GAFR &= ~(SHANNON_GPIO_EJECT_0 | SHANNON_GPIO_EJECT_1 | + SHANNON_GPIO_RDY_0 | SHANNON_GPIO_RDY_1); + + skt->irq = skt->nr ? SHANNON_IRQ_GPIO_RDY_1 : SHANNON_IRQ_GPIO_RDY_0; + + return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static void shannon_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static void +shannon_pcmcia_socket_state(struct soc_pcmcia_socket *skt, + struct pcmcia_state *state) +{ + unsigned long levels = GPLR; + + switch (skt->nr) { + case 0: + state->detect = (levels & SHANNON_GPIO_EJECT_0) ? 0 : 1; + state->ready = (levels & SHANNON_GPIO_RDY_0) ? 1 : 0; + state->wrprot = 0; /* Not available on Shannon. */ + state->bvd1 = 1; + state->bvd2 = 1; + state->vs_3v = 1; /* FIXME Can only apply 3.3V on Shannon. */ + state->vs_Xv = 0; + break; + + case 1: + state->detect = (levels & SHANNON_GPIO_EJECT_1) ? 0 : 1; + state->ready = (levels & SHANNON_GPIO_RDY_1) ? 1 : 0; + state->wrprot = 0; /* Not available on Shannon. */ + state->bvd1 = 1; + state->bvd2 = 1; + state->vs_3v = 1; /* FIXME Can only apply 3.3V on Shannon. */ + state->vs_Xv = 0; + break; + } +} + +static int +shannon_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, + const socket_state_t *state) +{ + switch (state->Vcc) { + case 0: /* power off */ + printk(KERN_WARNING "%s(): CS asked for 0V, still applying 3.3V..\n", __FUNCTION__); + break; + case 50: + printk(KERN_WARNING "%s(): CS asked for 5V, applying 3.3V..\n", __FUNCTION__); + case 33: + break; + default: + printk(KERN_ERR "%s(): unrecognized Vcc %u\n", + __FUNCTION__, state->Vcc); + return -1; + } + + printk(KERN_WARNING "%s(): Warning, Can't perform reset\n", __FUNCTION__); + + /* Silently ignore Vpp, output enable, speaker enable. */ + + return 0; +} + +static void shannon_pcmcia_socket_init(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static void shannon_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static struct pcmcia_low_level shannon_pcmcia_ops = { + .owner = THIS_MODULE, + .hw_init = shannon_pcmcia_hw_init, + .hw_shutdown = shannon_pcmcia_hw_shutdown, + .socket_state = shannon_pcmcia_socket_state, + .configure_socket = shannon_pcmcia_configure_socket, + + .socket_init = shannon_pcmcia_socket_init, + .socket_suspend = shannon_pcmcia_socket_suspend, +}; + +int __init pcmcia_shannon_init(struct device *dev) +{ + int ret = -ENODEV; + + if (machine_is_shannon()) + ret = sa11xx_drv_pcmcia_probe(dev, &shannon_pcmcia_ops, 0, 2); + + return ret; +} diff --git a/drivers/pcmcia/sa1100_simpad.c b/drivers/pcmcia/sa1100_simpad.c new file mode 100644 index 000000000000..c2ecf1185e9e --- /dev/null +++ b/drivers/pcmcia/sa1100_simpad.c @@ -0,0 +1,135 @@ +/* + * drivers/pcmcia/sa1100_simpad.c + * + * PCMCIA implementation routines for simpad + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/device.h> +#include <linux/init.h> + +#include <asm/hardware.h> +#include <asm/mach-types.h> +#include <asm/irq.h> +#include <asm/arch/simpad.h> +#include "sa1100_generic.h" + +extern long get_cs3_shadow(void); +extern void set_cs3_bit(int value); +extern void clear_cs3_bit(int value); + +static struct pcmcia_irqs irqs[] = { + { 1, IRQ_GPIO_CF_CD, "CF_CD" }, +}; + +static int simpad_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + + clear_cs3_bit(VCC_3V_EN|VCC_5V_EN|EN0|EN1); + + skt->irq = IRQ_GPIO_CF_IRQ; + + return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static void simpad_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); + + /* Disable CF bus: */ + //set_cs3_bit(PCMCIA_BUFF_DIS); + clear_cs3_bit(PCMCIA_RESET); +} + +static void +simpad_pcmcia_socket_state(struct soc_pcmcia_socket *skt, + struct pcmcia_state *state) +{ + unsigned long levels = GPLR; + long cs3reg = get_cs3_shadow(); + + state->detect=((levels & GPIO_CF_CD)==0)?1:0; + state->ready=(levels & GPIO_CF_IRQ)?1:0; + state->bvd1=1; /* Not available on Simpad. */ + state->bvd2=1; /* Not available on Simpad. */ + state->wrprot=0; /* Not available on Simpad. */ + + if((cs3reg & 0x0c) == 0x0c) { + state->vs_3v=0; + state->vs_Xv=0; + } else { + state->vs_3v=1; + state->vs_Xv=0; + } +} + +static int +simpad_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, + const socket_state_t *state) +{ + unsigned long flags; + + local_irq_save(flags); + + /* Murphy: see table of MIC2562a-1 */ + switch (state->Vcc) { + case 0: + clear_cs3_bit(VCC_3V_EN|VCC_5V_EN|EN0|EN1); + break; + + case 33: + clear_cs3_bit(VCC_3V_EN|EN1); + set_cs3_bit(VCC_5V_EN|EN0); + break; + + case 50: + clear_cs3_bit(VCC_5V_EN|EN1); + set_cs3_bit(VCC_3V_EN|EN0); + break; + + default: + printk(KERN_ERR "%s(): unrecognized Vcc %u\n", + __FUNCTION__, state->Vcc); + clear_cs3_bit(VCC_3V_EN|VCC_5V_EN|EN0|EN1); + local_irq_restore(flags); + return -1; + } + + + local_irq_restore(flags); + + return 0; +} + +static void simpad_pcmcia_socket_init(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static void simpad_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs)); + set_cs3_bit(PCMCIA_RESET); +} + +static struct pcmcia_low_level simpad_pcmcia_ops = { + .owner = THIS_MODULE, + .hw_init = simpad_pcmcia_hw_init, + .hw_shutdown = simpad_pcmcia_hw_shutdown, + .socket_state = simpad_pcmcia_socket_state, + .configure_socket = simpad_pcmcia_configure_socket, + .socket_init = simpad_pcmcia_socket_init, + .socket_suspend = simpad_pcmcia_socket_suspend, +}; + +int __init pcmcia_simpad_init(struct device *dev) +{ + int ret = -ENODEV; + + if (machine_is_simpad()) + ret = sa11xx_drv_pcmcia_probe(dev, &simpad_pcmcia_ops, 1, 1); + + return ret; +} diff --git a/drivers/pcmcia/sa1111_generic.c b/drivers/pcmcia/sa1111_generic.c new file mode 100644 index 000000000000..bd9d6b2fad68 --- /dev/null +++ b/drivers/pcmcia/sa1111_generic.c @@ -0,0 +1,196 @@ +/* + * linux/drivers/pcmcia/sa1111_generic.c + * + * We implement the generic parts of a SA1111 PCMCIA driver. This + * basically means we handle everything except controlling the + * power. Power is machine specific... + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/init.h> + +#include <pcmcia/ss.h> + +#include <asm/hardware.h> +#include <asm/hardware/sa1111.h> +#include <asm/io.h> +#include <asm/irq.h> + +#include "sa1111_generic.h" + +static struct pcmcia_irqs irqs[] = { + { 0, IRQ_S0_CD_VALID, "SA1111 PCMCIA card detect" }, + { 0, IRQ_S0_BVD1_STSCHG, "SA1111 PCMCIA BVD1" }, + { 1, IRQ_S1_CD_VALID, "SA1111 CF card detect" }, + { 1, IRQ_S1_BVD1_STSCHG, "SA1111 CF BVD1" }, +}; + +int sa1111_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + if (skt->irq == NO_IRQ) + skt->irq = skt->nr ? IRQ_S1_READY_NINT : IRQ_S0_READY_NINT; + + return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +void sa1111_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +void sa1111_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state) +{ + struct sa1111_dev *sadev = SA1111_DEV(skt->dev); + unsigned long status = sa1111_readl(sadev->mapbase + SA1111_PCSR); + + switch (skt->nr) { + case 0: + state->detect = status & PCSR_S0_DETECT ? 0 : 1; + state->ready = status & PCSR_S0_READY ? 1 : 0; + state->bvd1 = status & PCSR_S0_BVD1 ? 1 : 0; + state->bvd2 = status & PCSR_S0_BVD2 ? 1 : 0; + state->wrprot = status & PCSR_S0_WP ? 1 : 0; + state->vs_3v = status & PCSR_S0_VS1 ? 0 : 1; + state->vs_Xv = status & PCSR_S0_VS2 ? 0 : 1; + break; + + case 1: + state->detect = status & PCSR_S1_DETECT ? 0 : 1; + state->ready = status & PCSR_S1_READY ? 1 : 0; + state->bvd1 = status & PCSR_S1_BVD1 ? 1 : 0; + state->bvd2 = status & PCSR_S1_BVD2 ? 1 : 0; + state->wrprot = status & PCSR_S1_WP ? 1 : 0; + state->vs_3v = status & PCSR_S1_VS1 ? 0 : 1; + state->vs_Xv = status & PCSR_S1_VS2 ? 0 : 1; + break; + } +} + +int sa1111_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_t *state) +{ + struct sa1111_dev *sadev = SA1111_DEV(skt->dev); + unsigned int pccr_skt_mask, pccr_set_mask, val; + unsigned long flags; + + switch (skt->nr) { + case 0: + pccr_skt_mask = PCCR_S0_RST|PCCR_S0_FLT|PCCR_S0_PWAITEN|PCCR_S0_PSE; + break; + + case 1: + pccr_skt_mask = PCCR_S1_RST|PCCR_S1_FLT|PCCR_S1_PWAITEN|PCCR_S1_PSE; + break; + + default: + return -1; + } + + pccr_set_mask = 0; + + if (state->Vcc != 0) + pccr_set_mask |= PCCR_S0_PWAITEN|PCCR_S1_PWAITEN; + if (state->Vcc == 50) + pccr_set_mask |= PCCR_S0_PSE|PCCR_S1_PSE; + if (state->flags & SS_RESET) + pccr_set_mask |= PCCR_S0_RST|PCCR_S1_RST; + if (state->flags & SS_OUTPUT_ENA) + pccr_set_mask |= PCCR_S0_FLT|PCCR_S1_FLT; + + local_irq_save(flags); + val = sa1111_readl(sadev->mapbase + SA1111_PCCR); + val &= ~pccr_skt_mask; + val |= pccr_set_mask & pccr_skt_mask; + sa1111_writel(val, sadev->mapbase + SA1111_PCCR); + local_irq_restore(flags); + + return 0; +} + +void sa1111_pcmcia_socket_init(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +void sa1111_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt) +{ + soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs)); +} + +static int pcmcia_probe(struct sa1111_dev *dev) +{ + char *base; + + if (!request_mem_region(dev->res.start, 512, + SA1111_DRIVER_NAME(dev))) + return -EBUSY; + + base = dev->mapbase; + + /* + * Initialise the suspend state. + */ + sa1111_writel(PCSSR_S0_SLEEP | PCSSR_S1_SLEEP, base + SA1111_PCSSR); + sa1111_writel(PCCR_S0_FLT | PCCR_S1_FLT, base + SA1111_PCCR); + +#ifdef CONFIG_SA1100_BADGE4 + pcmcia_badge4_init(&dev->dev); +#endif +#ifdef CONFIG_SA1100_JORNADA720 + pcmcia_jornada720_init(&dev->dev); +#endif +#ifdef CONFIG_ARCH_LUBBOCK + pcmcia_lubbock_init(dev); +#endif +#ifdef CONFIG_ASSABET_NEPONSET + pcmcia_neponset_init(dev); +#endif + return 0; +} + +static int __devexit pcmcia_remove(struct sa1111_dev *dev) +{ + soc_common_drv_pcmcia_remove(&dev->dev); + release_mem_region(dev->res.start, 512); + return 0; +} + +static int pcmcia_suspend(struct sa1111_dev *dev, u32 state) +{ + return pcmcia_socket_dev_suspend(&dev->dev, state); +} + +static int pcmcia_resume(struct sa1111_dev *dev) +{ + return pcmcia_socket_dev_resume(&dev->dev); +} + +static struct sa1111_driver pcmcia_driver = { + .drv = { + .name = "sa1111-pcmcia", + }, + .devid = SA1111_DEVID_PCMCIA, + .probe = pcmcia_probe, + .remove = __devexit_p(pcmcia_remove), + .suspend = pcmcia_suspend, + .resume = pcmcia_resume, +}; + +static int __init sa1111_drv_pcmcia_init(void) +{ + return sa1111_driver_register(&pcmcia_driver); +} + +static void __exit sa1111_drv_pcmcia_exit(void) +{ + sa1111_driver_unregister(&pcmcia_driver); +} + +module_init(sa1111_drv_pcmcia_init); +module_exit(sa1111_drv_pcmcia_exit); + +MODULE_DESCRIPTION("SA1111 PCMCIA card socket driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pcmcia/sa1111_generic.h b/drivers/pcmcia/sa1111_generic.h new file mode 100644 index 000000000000..10ced4a210d7 --- /dev/null +++ b/drivers/pcmcia/sa1111_generic.h @@ -0,0 +1,15 @@ +#include "soc_common.h" +#include "sa11xx_base.h" + +extern int sa1111_pcmcia_hw_init(struct soc_pcmcia_socket *); +extern void sa1111_pcmcia_hw_shutdown(struct soc_pcmcia_socket *); +extern void sa1111_pcmcia_socket_state(struct soc_pcmcia_socket *, struct pcmcia_state *); +extern int sa1111_pcmcia_configure_socket(struct soc_pcmcia_socket *, const socket_state_t *); +extern void sa1111_pcmcia_socket_init(struct soc_pcmcia_socket *); +extern void sa1111_pcmcia_socket_suspend(struct soc_pcmcia_socket *); + +extern int pcmcia_badge4_init(struct device *); +extern int pcmcia_jornada720_init(struct device *); +extern int pcmcia_lubbock_init(struct sa1111_dev *); +extern int pcmcia_neponset_init(struct sa1111_dev *); + diff --git a/drivers/pcmcia/sa11xx_base.c b/drivers/pcmcia/sa11xx_base.c new file mode 100644 index 000000000000..db04ffb6f68c --- /dev/null +++ b/drivers/pcmcia/sa11xx_base.c @@ -0,0 +1,200 @@ +/*====================================================================== + + Device driver for the PCMCIA control functionality of StrongARM + SA-1100 microprocessors. + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is John G. Dorsey + <john+@cs.cmu.edu>. Portions created by John G. Dorsey are + Copyright (C) 1999 John G. Dorsey. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU Public License version 2 (the "GPL"), in which + case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/config.h> +#include <linux/cpufreq.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> + +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include "soc_common.h" +#include "sa11xx_base.h" + + +/* + * sa1100_pcmcia_default_mecr_timing + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * Calculate MECR clock wait states for given CPU clock + * speed and command wait state. This function can be over- + * written by a board specific version. + * + * The default is to simply calculate the BS values as specified in + * the INTEL SA1100 development manual + * "Expansion Memory (PCMCIA) Configuration Register (MECR)" + * that's section 10.2.5 in _my_ version of the manual ;) + */ +static unsigned int +sa1100_pcmcia_default_mecr_timing(struct soc_pcmcia_socket *skt, + unsigned int cpu_speed, + unsigned int cmd_time) +{ + return sa1100_pcmcia_mecr_bs(cmd_time, cpu_speed); +} + +/* sa1100_pcmcia_set_mecr() + * ^^^^^^^^^^^^^^^^^^^^^^^^ + * + * set MECR value for socket <sock> based on this sockets + * io, mem and attribute space access speed. + * Call board specific BS value calculation to allow boards + * to tweak the BS values. + */ +static int +sa1100_pcmcia_set_mecr(struct soc_pcmcia_socket *skt, unsigned int cpu_clock) +{ + struct soc_pcmcia_timing timing; + u32 mecr, old_mecr; + unsigned long flags; + unsigned int bs_io, bs_mem, bs_attr; + + soc_common_pcmcia_get_timing(skt, &timing); + + bs_io = skt->ops->get_timing(skt, cpu_clock, timing.io); + bs_mem = skt->ops->get_timing(skt, cpu_clock, timing.mem); + bs_attr = skt->ops->get_timing(skt, cpu_clock, timing.attr); + + local_irq_save(flags); + + old_mecr = mecr = MECR; + MECR_FAST_SET(mecr, skt->nr, 0); + MECR_BSIO_SET(mecr, skt->nr, bs_io); + MECR_BSA_SET(mecr, skt->nr, bs_attr); + MECR_BSM_SET(mecr, skt->nr, bs_mem); + if (old_mecr != mecr) + MECR = mecr; + + local_irq_restore(flags); + + debug(skt, 2, "FAST %X BSM %X BSA %X BSIO %X\n", + MECR_FAST_GET(mecr, skt->nr), + MECR_BSM_GET(mecr, skt->nr), MECR_BSA_GET(mecr, skt->nr), + MECR_BSIO_GET(mecr, skt->nr)); + + return 0; +} + +#ifdef CONFIG_CPU_FREQ +static int +sa1100_pcmcia_frequency_change(struct soc_pcmcia_socket *skt, + unsigned long val, + struct cpufreq_freqs *freqs) +{ + switch (val) { + case CPUFREQ_PRECHANGE: + if (freqs->new > freqs->old) + sa1100_pcmcia_set_mecr(skt, freqs->new); + break; + + case CPUFREQ_POSTCHANGE: + if (freqs->new < freqs->old) + sa1100_pcmcia_set_mecr(skt, freqs->new); + break; + case CPUFREQ_RESUMECHANGE: + sa1100_pcmcia_set_mecr(skt, freqs->new); + break; + } + + return 0; +} + +#endif + +static int +sa1100_pcmcia_set_timing(struct soc_pcmcia_socket *skt) +{ + return sa1100_pcmcia_set_mecr(skt, cpufreq_get(0)); +} + +static int +sa1100_pcmcia_show_timing(struct soc_pcmcia_socket *skt, char *buf) +{ + struct soc_pcmcia_timing timing; + unsigned int clock = cpufreq_get(0); + unsigned long mecr = MECR; + char *p = buf; + + soc_common_pcmcia_get_timing(skt, &timing); + + p+=sprintf(p, "I/O : %u (%u)\n", timing.io, + sa1100_pcmcia_cmd_time(clock, MECR_BSIO_GET(mecr, skt->nr))); + + p+=sprintf(p, "attribute: %u (%u)\n", timing.attr, + sa1100_pcmcia_cmd_time(clock, MECR_BSA_GET(mecr, skt->nr))); + + p+=sprintf(p, "common : %u (%u)\n", timing.mem, + sa1100_pcmcia_cmd_time(clock, MECR_BSM_GET(mecr, skt->nr))); + + return p - buf; +} + +int sa11xx_drv_pcmcia_probe(struct device *dev, struct pcmcia_low_level *ops, + int first, int nr) +{ + /* + * set default MECR calculation if the board specific + * code did not specify one... + */ + if (!ops->get_timing) + ops->get_timing = sa1100_pcmcia_default_mecr_timing; + + /* Provide our SA11x0 specific timing routines. */ + ops->set_timing = sa1100_pcmcia_set_timing; + ops->show_timing = sa1100_pcmcia_show_timing; +#ifdef CONFIG_CPU_FREQ + ops->frequency_change = sa1100_pcmcia_frequency_change; +#endif + + return soc_common_drv_pcmcia_probe(dev, ops, first, nr); +} +EXPORT_SYMBOL(sa11xx_drv_pcmcia_probe); + +static int __init sa11xx_pcmcia_init(void) +{ + return 0; +} +module_init(sa11xx_pcmcia_init); + +static void __exit sa11xx_pcmcia_exit(void) {} + +module_exit(sa11xx_pcmcia_exit); + +MODULE_AUTHOR("John Dorsey <john+@cs.cmu.edu>"); +MODULE_DESCRIPTION("Linux PCMCIA Card Services: SA-11xx core socket driver"); +MODULE_LICENSE("Dual MPL/GPL"); diff --git a/drivers/pcmcia/sa11xx_base.h b/drivers/pcmcia/sa11xx_base.h new file mode 100644 index 000000000000..7bc208280527 --- /dev/null +++ b/drivers/pcmcia/sa11xx_base.h @@ -0,0 +1,123 @@ +/*====================================================================== + + Device driver for the PCMCIA control functionality of StrongARM + SA-1100 microprocessors. + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is John G. Dorsey + <john+@cs.cmu.edu>. Portions created by John G. Dorsey are + Copyright (C) 1999 John G. Dorsey. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU Public License version 2 (the "GPL"), in which + case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#if !defined(_PCMCIA_SA1100_H) +# define _PCMCIA_SA1100_H + +/* SA-1100 PCMCIA Memory and I/O timing + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * The SA-1110 Developer's Manual, section 10.2.5, says the following: + * + * "To calculate the recommended BS_xx value for each address space: + * divide the command width time (the greater of twIOWR and twIORD, + * or the greater of twWE and twOE) by processor cycle time; divide + * by 2; divide again by 3 (number of BCLK's per command assertion); + * round up to the next whole number; and subtract 1." + */ + +/* MECR: Expansion Memory Configuration Register + * (SA-1100 Developers Manual, p.10-13; SA-1110 Developers Manual, p.10-24) + * + * MECR layout is: + * + * FAST1 BSM1<4:0> BSA1<4:0> BSIO1<4:0> FAST0 BSM0<4:0> BSA0<4:0> BSIO0<4:0> + * + * (This layout is actually true only for the SA-1110; the FASTn bits are + * reserved on the SA-1100.) + */ + +#define MECR_SOCKET_0_SHIFT (0) +#define MECR_SOCKET_1_SHIFT (16) + +#define MECR_BS_MASK (0x1f) +#define MECR_FAST_MODE_MASK (0x01) + +#define MECR_BSIO_SHIFT (0) +#define MECR_BSA_SHIFT (5) +#define MECR_BSM_SHIFT (10) +#define MECR_FAST_SHIFT (15) + +#define MECR_SET(mecr, sock, shift, mask, bs) \ +((mecr)=((mecr)&~(((mask)<<(shift))<<\ + ((sock)==0?MECR_SOCKET_0_SHIFT:MECR_SOCKET_1_SHIFT)))|\ + (((bs)<<(shift))<<((sock)==0?MECR_SOCKET_0_SHIFT:MECR_SOCKET_1_SHIFT))) + +#define MECR_GET(mecr, sock, shift, mask) \ +((((mecr)>>(((sock)==0)?MECR_SOCKET_0_SHIFT:MECR_SOCKET_1_SHIFT))>>\ + (shift))&(mask)) + +#define MECR_BSIO_SET(mecr, sock, bs) \ +MECR_SET((mecr), (sock), MECR_BSIO_SHIFT, MECR_BS_MASK, (bs)) + +#define MECR_BSIO_GET(mecr, sock) \ +MECR_GET((mecr), (sock), MECR_BSIO_SHIFT, MECR_BS_MASK) + +#define MECR_BSA_SET(mecr, sock, bs) \ +MECR_SET((mecr), (sock), MECR_BSA_SHIFT, MECR_BS_MASK, (bs)) + +#define MECR_BSA_GET(mecr, sock) \ +MECR_GET((mecr), (sock), MECR_BSA_SHIFT, MECR_BS_MASK) + +#define MECR_BSM_SET(mecr, sock, bs) \ +MECR_SET((mecr), (sock), MECR_BSM_SHIFT, MECR_BS_MASK, (bs)) + +#define MECR_BSM_GET(mecr, sock) \ +MECR_GET((mecr), (sock), MECR_BSM_SHIFT, MECR_BS_MASK) + +#define MECR_FAST_SET(mecr, sock, fast) \ +MECR_SET((mecr), (sock), MECR_FAST_SHIFT, MECR_FAST_MODE_MASK, (fast)) + +#define MECR_FAST_GET(mecr, sock) \ +MECR_GET((mecr), (sock), MECR_FAST_SHIFT, MECR_FAST_MODE_MASK) + + +/* This function implements the BS value calculation for setting the MECR + * using integer arithmetic: + */ +static inline unsigned int sa1100_pcmcia_mecr_bs(unsigned int pcmcia_cycle_ns, + unsigned int cpu_clock_khz){ + unsigned int t = ((pcmcia_cycle_ns * cpu_clock_khz) / 6) - 1000000; + return (t / 1000000) + (((t % 1000000) == 0) ? 0 : 1); +} + +/* This function returns the (approximate) command assertion period, in + * nanoseconds, for a given CPU clock frequency and MECR BS value: + */ +static inline unsigned int sa1100_pcmcia_cmd_time(unsigned int cpu_clock_khz, + unsigned int pcmcia_mecr_bs){ + return (((10000000 * 2) / cpu_clock_khz) * (3 * (pcmcia_mecr_bs + 1))) / 10; +} + + +extern int sa11xx_drv_pcmcia_probe(struct device *dev, struct pcmcia_low_level *ops, int first, int nr); + +#endif /* !defined(_PCMCIA_SA1100_H) */ diff --git a/drivers/pcmcia/soc_common.c b/drivers/pcmcia/soc_common.c new file mode 100644 index 000000000000..888b70e6a484 --- /dev/null +++ b/drivers/pcmcia/soc_common.c @@ -0,0 +1,850 @@ +/*====================================================================== + + Common support code for the PCMCIA control functionality of + integrated SOCs like the SA-11x0 and PXA2xx microprocessors. + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is John G. Dorsey + <john+@cs.cmu.edu>. Portions created by John G. Dorsey are + Copyright (C) 1999 John G. Dorsey. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU Public License version 2 (the "GPL"), in which + case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/timer.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/cpufreq.h> + +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include "soc_common.h" + +/* FIXME: platform dependent resource declaration has to move out of this file */ +#ifdef CONFIG_ARCH_PXA +#include <asm/arch/pxa-regs.h> +#endif + +#ifdef DEBUG + +static int pc_debug; +module_param(pc_debug, int, 0644); + +void soc_pcmcia_debug(struct soc_pcmcia_socket *skt, const char *func, + int lvl, const char *fmt, ...) +{ + va_list args; + if (pc_debug > lvl) { + printk(KERN_DEBUG "skt%u: %s: ", skt->nr, func); + va_start(args, fmt); + printk(fmt, args); + va_end(args); + } +} + +#endif + +#define to_soc_pcmcia_socket(x) container_of(x, struct soc_pcmcia_socket, socket) + +static unsigned short +calc_speed(unsigned short *spds, int num, unsigned short dflt) +{ + unsigned short speed = 0; + int i; + + for (i = 0; i < num; i++) + if (speed < spds[i]) + speed = spds[i]; + if (speed == 0) + speed = dflt; + + return speed; +} + +void soc_common_pcmcia_get_timing(struct soc_pcmcia_socket *skt, struct soc_pcmcia_timing *timing) +{ + timing->io = calc_speed(skt->spd_io, MAX_IO_WIN, SOC_PCMCIA_IO_ACCESS); + timing->mem = calc_speed(skt->spd_mem, MAX_WIN, SOC_PCMCIA_3V_MEM_ACCESS); + timing->attr = calc_speed(skt->spd_attr, MAX_WIN, SOC_PCMCIA_3V_MEM_ACCESS); +} +EXPORT_SYMBOL(soc_common_pcmcia_get_timing); + +static unsigned int soc_common_pcmcia_skt_state(struct soc_pcmcia_socket *skt) +{ + struct pcmcia_state state; + unsigned int stat; + + memset(&state, 0, sizeof(struct pcmcia_state)); + + skt->ops->socket_state(skt, &state); + + stat = state.detect ? SS_DETECT : 0; + stat |= state.ready ? SS_READY : 0; + stat |= state.wrprot ? SS_WRPROT : 0; + stat |= state.vs_3v ? SS_3VCARD : 0; + stat |= state.vs_Xv ? SS_XVCARD : 0; + + /* The power status of individual sockets is not available + * explicitly from the hardware, so we just remember the state + * and regurgitate it upon request: + */ + stat |= skt->cs_state.Vcc ? SS_POWERON : 0; + + if (skt->cs_state.flags & SS_IOCARD) + stat |= state.bvd1 ? SS_STSCHG : 0; + else { + if (state.bvd1 == 0) + stat |= SS_BATDEAD; + else if (state.bvd2 == 0) + stat |= SS_BATWARN; + } + return stat; +} + +/* + * soc_common_pcmcia_config_skt + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * Convert PCMCIA socket state to our socket configure structure. + */ +static int +soc_common_pcmcia_config_skt(struct soc_pcmcia_socket *skt, socket_state_t *state) +{ + int ret; + + ret = skt->ops->configure_socket(skt, state); + if (ret == 0) { + /* + * This really needs a better solution. The IRQ + * may or may not be claimed by the driver. + */ + if (skt->irq_state != 1 && state->io_irq) { + skt->irq_state = 1; + set_irq_type(skt->irq, IRQT_FALLING); + } else if (skt->irq_state == 1 && state->io_irq == 0) { + skt->irq_state = 0; + set_irq_type(skt->irq, IRQT_NOEDGE); + } + + skt->cs_state = *state; + } + + if (ret < 0) + printk(KERN_ERR "soc_common_pcmcia: unable to configure " + "socket %d\n", skt->nr); + + return ret; +} + +/* soc_common_pcmcia_sock_init() + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * (Re-)Initialise the socket, turning on status interrupts + * and PCMCIA bus. This must wait for power to stabilise + * so that the card status signals report correctly. + * + * Returns: 0 + */ +static int soc_common_pcmcia_sock_init(struct pcmcia_socket *sock) +{ + struct soc_pcmcia_socket *skt = to_soc_pcmcia_socket(sock); + + debug(skt, 2, "initializing socket\n"); + + skt->ops->socket_init(skt); + return 0; +} + + +/* + * soc_common_pcmcia_suspend() + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * + * Remove power on the socket, disable IRQs from the card. + * Turn off status interrupts, and disable the PCMCIA bus. + * + * Returns: 0 + */ +static int soc_common_pcmcia_suspend(struct pcmcia_socket *sock) +{ + struct soc_pcmcia_socket *skt = to_soc_pcmcia_socket(sock); + + debug(skt, 2, "suspending socket\n"); + + skt->ops->socket_suspend(skt); + + return 0; +} + +static DEFINE_SPINLOCK(status_lock); + +static void soc_common_check_status(struct soc_pcmcia_socket *skt) +{ + unsigned int events; + + debug(skt, 4, "entering PCMCIA monitoring thread\n"); + + do { + unsigned int status; + unsigned long flags; + + status = soc_common_pcmcia_skt_state(skt); + + spin_lock_irqsave(&status_lock, flags); + events = (status ^ skt->status) & skt->cs_state.csc_mask; + skt->status = status; + spin_unlock_irqrestore(&status_lock, flags); + + debug(skt, 4, "events: %s%s%s%s%s%s\n", + events == 0 ? "<NONE>" : "", + events & SS_DETECT ? "DETECT " : "", + events & SS_READY ? "READY " : "", + events & SS_BATDEAD ? "BATDEAD " : "", + events & SS_BATWARN ? "BATWARN " : "", + events & SS_STSCHG ? "STSCHG " : ""); + + if (events) + pcmcia_parse_events(&skt->socket, events); + } while (events); +} + +/* Let's poll for events in addition to IRQs since IRQ only is unreliable... */ +static void soc_common_pcmcia_poll_event(unsigned long dummy) +{ + struct soc_pcmcia_socket *skt = (struct soc_pcmcia_socket *)dummy; + debug(skt, 4, "polling for events\n"); + + mod_timer(&skt->poll_timer, jiffies + SOC_PCMCIA_POLL_PERIOD); + + soc_common_check_status(skt); +} + + +/* + * Service routine for socket driver interrupts (requested by the + * low-level PCMCIA init() operation via soc_common_pcmcia_thread()). + * The actual interrupt-servicing work is performed by + * soc_common_pcmcia_thread(), largely because the Card Services event- + * handling code performs scheduling operations which cannot be + * executed from within an interrupt context. + */ +static irqreturn_t soc_common_pcmcia_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + struct soc_pcmcia_socket *skt = dev; + + debug(skt, 3, "servicing IRQ %d\n", irq); + + soc_common_check_status(skt); + + return IRQ_HANDLED; +} + + +/* + * Implements the get_status() operation for the in-kernel PCMCIA + * service (formerly SS_GetStatus in Card Services). Essentially just + * fills in bits in `status' according to internal driver state or + * the value of the voltage detect chipselect register. + * + * As a debugging note, during card startup, the PCMCIA core issues + * three set_socket() commands in a row the first with RESET deasserted, + * the second with RESET asserted, and the last with RESET deasserted + * again. Following the third set_socket(), a get_status() command will + * be issued. The kernel is looking for the SS_READY flag (see + * setup_socket(), reset_socket(), and unreset_socket() in cs.c). + * + * Returns: 0 + */ +static int +soc_common_pcmcia_get_status(struct pcmcia_socket *sock, unsigned int *status) +{ + struct soc_pcmcia_socket *skt = to_soc_pcmcia_socket(sock); + + skt->status = soc_common_pcmcia_skt_state(skt); + *status = skt->status; + + return 0; +} + + +/* + * Implements the get_socket() operation for the in-kernel PCMCIA + * service (formerly SS_GetSocket in Card Services). Not a very + * exciting routine. + * + * Returns: 0 + */ +static int +soc_common_pcmcia_get_socket(struct pcmcia_socket *sock, socket_state_t *state) +{ + struct soc_pcmcia_socket *skt = to_soc_pcmcia_socket(sock); + + debug(skt, 2, "\n"); + + *state = skt->cs_state; + + return 0; +} + +/* + * Implements the set_socket() operation for the in-kernel PCMCIA + * service (formerly SS_SetSocket in Card Services). We more or + * less punt all of this work and let the kernel handle the details + * of power configuration, reset, &c. We also record the value of + * `state' in order to regurgitate it to the PCMCIA core later. + * + * Returns: 0 + */ +static int +soc_common_pcmcia_set_socket(struct pcmcia_socket *sock, socket_state_t *state) +{ + struct soc_pcmcia_socket *skt = to_soc_pcmcia_socket(sock); + + debug(skt, 2, "mask: %s%s%s%s%s%sflags: %s%s%s%s%s%sVcc %d Vpp %d irq %d\n", + (state->csc_mask==0)?"<NONE> ":"", + (state->csc_mask&SS_DETECT)?"DETECT ":"", + (state->csc_mask&SS_READY)?"READY ":"", + (state->csc_mask&SS_BATDEAD)?"BATDEAD ":"", + (state->csc_mask&SS_BATWARN)?"BATWARN ":"", + (state->csc_mask&SS_STSCHG)?"STSCHG ":"", + (state->flags==0)?"<NONE> ":"", + (state->flags&SS_PWR_AUTO)?"PWR_AUTO ":"", + (state->flags&SS_IOCARD)?"IOCARD ":"", + (state->flags&SS_RESET)?"RESET ":"", + (state->flags&SS_SPKR_ENA)?"SPKR_ENA ":"", + (state->flags&SS_OUTPUT_ENA)?"OUTPUT_ENA ":"", + state->Vcc, state->Vpp, state->io_irq); + + return soc_common_pcmcia_config_skt(skt, state); +} + + +/* + * Implements the set_io_map() operation for the in-kernel PCMCIA + * service (formerly SS_SetIOMap in Card Services). We configure + * the map speed as requested, but override the address ranges + * supplied by Card Services. + * + * Returns: 0 on success, -1 on error + */ +static int +soc_common_pcmcia_set_io_map(struct pcmcia_socket *sock, struct pccard_io_map *map) +{ + struct soc_pcmcia_socket *skt = to_soc_pcmcia_socket(sock); + unsigned short speed = map->speed; + + debug(skt, 2, "map %u speed %u start 0x%08x stop 0x%08x\n", + map->map, map->speed, map->start, map->stop); + debug(skt, 2, "flags: %s%s%s%s%s%s%s%s\n", + (map->flags==0)?"<NONE>":"", + (map->flags&MAP_ACTIVE)?"ACTIVE ":"", + (map->flags&MAP_16BIT)?"16BIT ":"", + (map->flags&MAP_AUTOSZ)?"AUTOSZ ":"", + (map->flags&MAP_0WS)?"0WS ":"", + (map->flags&MAP_WRPROT)?"WRPROT ":"", + (map->flags&MAP_USE_WAIT)?"USE_WAIT ":"", + (map->flags&MAP_PREFETCH)?"PREFETCH ":""); + + if (map->map >= MAX_IO_WIN) { + printk(KERN_ERR "%s(): map (%d) out of range\n", __FUNCTION__, + map->map); + return -1; + } + + if (map->flags & MAP_ACTIVE) { + if (speed == 0) + speed = SOC_PCMCIA_IO_ACCESS; + } else { + speed = 0; + } + + skt->spd_io[map->map] = speed; + skt->ops->set_timing(skt); + + if (map->stop == 1) + map->stop = PAGE_SIZE-1; + + map->stop -= map->start; + map->stop += skt->socket.io_offset; + map->start = skt->socket.io_offset; + + return 0; +} + + +/* + * Implements the set_mem_map() operation for the in-kernel PCMCIA + * service (formerly SS_SetMemMap in Card Services). We configure + * the map speed as requested, but override the address ranges + * supplied by Card Services. + * + * Returns: 0 on success, -1 on error + */ +static int +soc_common_pcmcia_set_mem_map(struct pcmcia_socket *sock, struct pccard_mem_map *map) +{ + struct soc_pcmcia_socket *skt = to_soc_pcmcia_socket(sock); + struct resource *res; + unsigned short speed = map->speed; + + debug(skt, 2, "map %u speed %u card_start %08x\n", + map->map, map->speed, map->card_start); + debug(skt, 2, "flags: %s%s%s%s%s%s%s%s\n", + (map->flags==0)?"<NONE>":"", + (map->flags&MAP_ACTIVE)?"ACTIVE ":"", + (map->flags&MAP_16BIT)?"16BIT ":"", + (map->flags&MAP_AUTOSZ)?"AUTOSZ ":"", + (map->flags&MAP_0WS)?"0WS ":"", + (map->flags&MAP_WRPROT)?"WRPROT ":"", + (map->flags&MAP_ATTRIB)?"ATTRIB ":"", + (map->flags&MAP_USE_WAIT)?"USE_WAIT ":""); + + if (map->map >= MAX_WIN) + return -EINVAL; + + if (map->flags & MAP_ACTIVE) { + if (speed == 0) + speed = 300; + } else { + speed = 0; + } + + if (map->flags & MAP_ATTRIB) { + res = &skt->res_attr; + skt->spd_attr[map->map] = speed; + skt->spd_mem[map->map] = 0; + } else { + res = &skt->res_mem; + skt->spd_attr[map->map] = 0; + skt->spd_mem[map->map] = speed; + } + + skt->ops->set_timing(skt); + + map->static_start = res->start + map->card_start; + + return 0; +} + +struct bittbl { + unsigned int mask; + const char *name; +}; + +static struct bittbl status_bits[] = { + { SS_WRPROT, "SS_WRPROT" }, + { SS_BATDEAD, "SS_BATDEAD" }, + { SS_BATWARN, "SS_BATWARN" }, + { SS_READY, "SS_READY" }, + { SS_DETECT, "SS_DETECT" }, + { SS_POWERON, "SS_POWERON" }, + { SS_STSCHG, "SS_STSCHG" }, + { SS_3VCARD, "SS_3VCARD" }, + { SS_XVCARD, "SS_XVCARD" }, +}; + +static struct bittbl conf_bits[] = { + { SS_PWR_AUTO, "SS_PWR_AUTO" }, + { SS_IOCARD, "SS_IOCARD" }, + { SS_RESET, "SS_RESET" }, + { SS_DMA_MODE, "SS_DMA_MODE" }, + { SS_SPKR_ENA, "SS_SPKR_ENA" }, + { SS_OUTPUT_ENA, "SS_OUTPUT_ENA" }, +}; + +static void +dump_bits(char **p, const char *prefix, unsigned int val, struct bittbl *bits, int sz) +{ + char *b = *p; + int i; + + b += sprintf(b, "%-9s:", prefix); + for (i = 0; i < sz; i++) + if (val & bits[i].mask) + b += sprintf(b, " %s", bits[i].name); + *b++ = '\n'; + *p = b; +} + +/* + * Implements the /sys/class/pcmcia_socket/??/status file. + * + * Returns: the number of characters added to the buffer + */ +static ssize_t show_status(struct class_device *class_dev, char *buf) +{ + struct soc_pcmcia_socket *skt = + container_of(class_dev, struct soc_pcmcia_socket, socket.dev); + char *p = buf; + + p+=sprintf(p, "slot : %d\n", skt->nr); + + dump_bits(&p, "status", skt->status, + status_bits, ARRAY_SIZE(status_bits)); + dump_bits(&p, "csc_mask", skt->cs_state.csc_mask, + status_bits, ARRAY_SIZE(status_bits)); + dump_bits(&p, "cs_flags", skt->cs_state.flags, + conf_bits, ARRAY_SIZE(conf_bits)); + + p+=sprintf(p, "Vcc : %d\n", skt->cs_state.Vcc); + p+=sprintf(p, "Vpp : %d\n", skt->cs_state.Vpp); + p+=sprintf(p, "IRQ : %d (%d)\n", skt->cs_state.io_irq, skt->irq); + if (skt->ops->show_timing) + p+=skt->ops->show_timing(skt, p); + + return p-buf; +} +static CLASS_DEVICE_ATTR(status, S_IRUGO, show_status, NULL); + + +static struct pccard_operations soc_common_pcmcia_operations = { + .init = soc_common_pcmcia_sock_init, + .suspend = soc_common_pcmcia_suspend, + .get_status = soc_common_pcmcia_get_status, + .get_socket = soc_common_pcmcia_get_socket, + .set_socket = soc_common_pcmcia_set_socket, + .set_io_map = soc_common_pcmcia_set_io_map, + .set_mem_map = soc_common_pcmcia_set_mem_map, +}; + + +int soc_pcmcia_request_irqs(struct soc_pcmcia_socket *skt, + struct pcmcia_irqs *irqs, int nr) +{ + int i, res = 0; + + for (i = 0; i < nr; i++) { + if (irqs[i].sock != skt->nr) + continue; + res = request_irq(irqs[i].irq, soc_common_pcmcia_interrupt, + SA_INTERRUPT, irqs[i].str, skt); + if (res) + break; + set_irq_type(irqs[i].irq, IRQT_NOEDGE); + } + + if (res) { + printk(KERN_ERR "PCMCIA: request for IRQ%d failed (%d)\n", + irqs[i].irq, res); + + while (i--) + if (irqs[i].sock == skt->nr) + free_irq(irqs[i].irq, skt); + } + return res; +} +EXPORT_SYMBOL(soc_pcmcia_request_irqs); + +void soc_pcmcia_free_irqs(struct soc_pcmcia_socket *skt, + struct pcmcia_irqs *irqs, int nr) +{ + int i; + + for (i = 0; i < nr; i++) + if (irqs[i].sock == skt->nr) + free_irq(irqs[i].irq, skt); +} +EXPORT_SYMBOL(soc_pcmcia_free_irqs); + +void soc_pcmcia_disable_irqs(struct soc_pcmcia_socket *skt, + struct pcmcia_irqs *irqs, int nr) +{ + int i; + + for (i = 0; i < nr; i++) + if (irqs[i].sock == skt->nr) + set_irq_type(irqs[i].irq, IRQT_NOEDGE); +} +EXPORT_SYMBOL(soc_pcmcia_disable_irqs); + +void soc_pcmcia_enable_irqs(struct soc_pcmcia_socket *skt, + struct pcmcia_irqs *irqs, int nr) +{ + int i; + + for (i = 0; i < nr; i++) + if (irqs[i].sock == skt->nr) { + set_irq_type(irqs[i].irq, IRQT_RISING); + set_irq_type(irqs[i].irq, IRQT_BOTHEDGE); + } +} +EXPORT_SYMBOL(soc_pcmcia_enable_irqs); + + +LIST_HEAD(soc_pcmcia_sockets); +DECLARE_MUTEX(soc_pcmcia_sockets_lock); + +static const char *skt_names[] = { + "PCMCIA socket 0", + "PCMCIA socket 1", +}; + +struct skt_dev_info { + int nskt; + struct soc_pcmcia_socket skt[0]; +}; + +#define SKT_DEV_INFO_SIZE(n) \ + (sizeof(struct skt_dev_info) + (n)*sizeof(struct soc_pcmcia_socket)) + +#ifdef CONFIG_CPU_FREQ +static int +soc_pcmcia_notifier(struct notifier_block *nb, unsigned long val, void *data) +{ + struct soc_pcmcia_socket *skt; + struct cpufreq_freqs *freqs = data; + int ret = 0; + + down(&soc_pcmcia_sockets_lock); + list_for_each_entry(skt, &soc_pcmcia_sockets, node) + if ( skt->ops->frequency_change ) + ret += skt->ops->frequency_change(skt, val, freqs); + up(&soc_pcmcia_sockets_lock); + + return ret; +} + +static struct notifier_block soc_pcmcia_notifier_block = { + .notifier_call = soc_pcmcia_notifier +}; + +static int soc_pcmcia_cpufreq_register(void) +{ + int ret; + + ret = cpufreq_register_notifier(&soc_pcmcia_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + if (ret < 0) + printk(KERN_ERR "Unable to register CPU frequency change " + "notifier for PCMCIA (%d)\n", ret); + return ret; +} + +static void soc_pcmcia_cpufreq_unregister(void) +{ + cpufreq_unregister_notifier(&soc_pcmcia_notifier_block, CPUFREQ_TRANSITION_NOTIFIER); +} + +#else +#define soc_pcmcia_cpufreq_register() +#define soc_pcmcia_cpufreq_unregister() +#endif + +int soc_common_drv_pcmcia_probe(struct device *dev, struct pcmcia_low_level *ops, int first, int nr) +{ + struct skt_dev_info *sinfo; + struct soc_pcmcia_socket *skt; + int ret, i; + + down(&soc_pcmcia_sockets_lock); + + sinfo = kmalloc(SKT_DEV_INFO_SIZE(nr), GFP_KERNEL); + if (!sinfo) { + ret = -ENOMEM; + goto out; + } + + memset(sinfo, 0, SKT_DEV_INFO_SIZE(nr)); + sinfo->nskt = nr; + + /* + * Initialise the per-socket structure. + */ + for (i = 0; i < nr; i++) { + skt = &sinfo->skt[i]; + + skt->socket.ops = &soc_common_pcmcia_operations; + skt->socket.owner = ops->owner; + skt->socket.dev.dev = dev; + + init_timer(&skt->poll_timer); + skt->poll_timer.function = soc_common_pcmcia_poll_event; + skt->poll_timer.data = (unsigned long)skt; + skt->poll_timer.expires = jiffies + SOC_PCMCIA_POLL_PERIOD; + + skt->nr = first + i; + skt->irq = NO_IRQ; + skt->dev = dev; + skt->ops = ops; + + skt->res_skt.start = _PCMCIA(skt->nr); + skt->res_skt.end = _PCMCIA(skt->nr) + PCMCIASp - 1; + skt->res_skt.name = skt_names[skt->nr]; + skt->res_skt.flags = IORESOURCE_MEM; + + ret = request_resource(&iomem_resource, &skt->res_skt); + if (ret) + goto out_err_1; + + skt->res_io.start = _PCMCIAIO(skt->nr); + skt->res_io.end = _PCMCIAIO(skt->nr) + PCMCIAIOSp - 1; + skt->res_io.name = "io"; + skt->res_io.flags = IORESOURCE_MEM | IORESOURCE_BUSY; + + ret = request_resource(&skt->res_skt, &skt->res_io); + if (ret) + goto out_err_2; + + skt->res_mem.start = _PCMCIAMem(skt->nr); + skt->res_mem.end = _PCMCIAMem(skt->nr) + PCMCIAMemSp - 1; + skt->res_mem.name = "memory"; + skt->res_mem.flags = IORESOURCE_MEM; + + ret = request_resource(&skt->res_skt, &skt->res_mem); + if (ret) + goto out_err_3; + + skt->res_attr.start = _PCMCIAAttr(skt->nr); + skt->res_attr.end = _PCMCIAAttr(skt->nr) + PCMCIAAttrSp - 1; + skt->res_attr.name = "attribute"; + skt->res_attr.flags = IORESOURCE_MEM; + + ret = request_resource(&skt->res_skt, &skt->res_attr); + if (ret) + goto out_err_4; + + skt->virt_io = ioremap(skt->res_io.start, 0x10000); + if (skt->virt_io == NULL) { + ret = -ENOMEM; + goto out_err_5; + } + + if ( list_empty(&soc_pcmcia_sockets) ) + soc_pcmcia_cpufreq_register(); + + list_add(&skt->node, &soc_pcmcia_sockets); + + /* + * We initialize default socket timing here, because + * we are not guaranteed to see a SetIOMap operation at + * runtime. + */ + ops->set_timing(skt); + + ret = ops->hw_init(skt); + if (ret) + goto out_err_6; + + skt->socket.features = SS_CAP_STATIC_MAP|SS_CAP_PCCARD; + skt->socket.resource_ops = &pccard_static_ops; + skt->socket.irq_mask = 0; + skt->socket.map_size = PAGE_SIZE; + skt->socket.pci_irq = skt->irq; + skt->socket.io_offset = (unsigned long)skt->virt_io; + + skt->status = soc_common_pcmcia_skt_state(skt); + + ret = pcmcia_register_socket(&skt->socket); + if (ret) + goto out_err_7; + + WARN_ON(skt->socket.sock != i); + + add_timer(&skt->poll_timer); + + class_device_create_file(&skt->socket.dev, &class_device_attr_status); + } + + dev_set_drvdata(dev, sinfo); + ret = 0; + goto out; + + do { + skt = &sinfo->skt[i]; + + del_timer_sync(&skt->poll_timer); + pcmcia_unregister_socket(&skt->socket); + + out_err_7: + flush_scheduled_work(); + + ops->hw_shutdown(skt); + out_err_6: + list_del(&skt->node); + iounmap(skt->virt_io); + out_err_5: + release_resource(&skt->res_attr); + out_err_4: + release_resource(&skt->res_mem); + out_err_3: + release_resource(&skt->res_io); + out_err_2: + release_resource(&skt->res_skt); + out_err_1: + i--; + } while (i > 0); + + kfree(sinfo); + + out: + up(&soc_pcmcia_sockets_lock); + return ret; +} + +int soc_common_drv_pcmcia_remove(struct device *dev) +{ + struct skt_dev_info *sinfo = dev_get_drvdata(dev); + int i; + + dev_set_drvdata(dev, NULL); + + down(&soc_pcmcia_sockets_lock); + for (i = 0; i < sinfo->nskt; i++) { + struct soc_pcmcia_socket *skt = &sinfo->skt[i]; + + del_timer_sync(&skt->poll_timer); + + pcmcia_unregister_socket(&skt->socket); + + flush_scheduled_work(); + + skt->ops->hw_shutdown(skt); + + soc_common_pcmcia_config_skt(skt, &dead_socket); + + list_del(&skt->node); + iounmap(skt->virt_io); + skt->virt_io = NULL; + release_resource(&skt->res_attr); + release_resource(&skt->res_mem); + release_resource(&skt->res_io); + release_resource(&skt->res_skt); + } + if ( list_empty(&soc_pcmcia_sockets) ) + soc_pcmcia_cpufreq_unregister(); + + up(&soc_pcmcia_sockets_lock); + + kfree(sinfo); + + return 0; +} diff --git a/drivers/pcmcia/soc_common.h b/drivers/pcmcia/soc_common.h new file mode 100644 index 000000000000..700a155fbc78 --- /dev/null +++ b/drivers/pcmcia/soc_common.h @@ -0,0 +1,194 @@ +/* + * linux/drivers/pcmcia/soc_common.h + * + * Copyright (C) 2000 John G Dorsey <john+@cs.cmu.edu> + * + * This file contains definitions for the PCMCIA support code common to + * integrated SOCs like the SA-11x0 and PXA2xx microprocessors. + */ +#ifndef _ASM_ARCH_PCMCIA +#define _ASM_ARCH_PCMCIA + +/* include the world */ +#include <linux/cpufreq.h> +#include <pcmcia/version.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" + + +struct device; +struct pcmcia_low_level; + +/* + * This structure encapsulates per-socket state which we might need to + * use when responding to a Card Services query of some kind. + */ +struct soc_pcmcia_socket { + struct pcmcia_socket socket; + + /* + * Info from low level handler + */ + struct device *dev; + unsigned int nr; + unsigned int irq; + + /* + * Core PCMCIA state + */ + struct pcmcia_low_level *ops; + + unsigned int status; + socket_state_t cs_state; + + unsigned short spd_io[MAX_IO_WIN]; + unsigned short spd_mem[MAX_WIN]; + unsigned short spd_attr[MAX_WIN]; + + struct resource res_skt; + struct resource res_io; + struct resource res_mem; + struct resource res_attr; + void __iomem *virt_io; + + unsigned int irq_state; + + struct timer_list poll_timer; + struct list_head node; +}; + +struct pcmcia_state { + unsigned detect: 1, + ready: 1, + bvd1: 1, + bvd2: 1, + wrprot: 1, + vs_3v: 1, + vs_Xv: 1; +}; + +struct pcmcia_low_level { + struct module *owner; + + /* first socket in system */ + int first; + /* nr of sockets */ + int nr; + + int (*hw_init)(struct soc_pcmcia_socket *); + void (*hw_shutdown)(struct soc_pcmcia_socket *); + + void (*socket_state)(struct soc_pcmcia_socket *, struct pcmcia_state *); + int (*configure_socket)(struct soc_pcmcia_socket *, const socket_state_t *); + + /* + * Enable card status IRQs on (re-)initialisation. This can + * be called at initialisation, power management event, or + * pcmcia event. + */ + void (*socket_init)(struct soc_pcmcia_socket *); + + /* + * Disable card status IRQs and PCMCIA bus on suspend. + */ + void (*socket_suspend)(struct soc_pcmcia_socket *); + + /* + * Hardware specific timing routines. + * If provided, the get_timing routine overrides the SOC default. + */ + unsigned int (*get_timing)(struct soc_pcmcia_socket *, unsigned int, unsigned int); + int (*set_timing)(struct soc_pcmcia_socket *); + int (*show_timing)(struct soc_pcmcia_socket *, char *); + +#ifdef CONFIG_CPU_FREQ + /* + * CPUFREQ support. + */ + int (*frequency_change)(struct soc_pcmcia_socket *, unsigned long, struct cpufreq_freqs *); +#endif +}; + + +struct pcmcia_irqs { + int sock; + int irq; + const char *str; +}; + +struct soc_pcmcia_timing { + unsigned short io; + unsigned short mem; + unsigned short attr; +}; + +extern int soc_pcmcia_request_irqs(struct soc_pcmcia_socket *skt, struct pcmcia_irqs *irqs, int nr); +extern void soc_pcmcia_free_irqs(struct soc_pcmcia_socket *skt, struct pcmcia_irqs *irqs, int nr); +extern void soc_pcmcia_disable_irqs(struct soc_pcmcia_socket *skt, struct pcmcia_irqs *irqs, int nr); +extern void soc_pcmcia_enable_irqs(struct soc_pcmcia_socket *skt, struct pcmcia_irqs *irqs, int nr); +extern void soc_common_pcmcia_get_timing(struct soc_pcmcia_socket *, struct soc_pcmcia_timing *); + + +extern struct list_head soc_pcmcia_sockets; +extern struct semaphore soc_pcmcia_sockets_lock; + +extern int soc_common_drv_pcmcia_probe(struct device *dev, struct pcmcia_low_level *ops, int first, int nr); +extern int soc_common_drv_pcmcia_remove(struct device *dev); + + +#ifdef DEBUG + +extern void soc_pcmcia_debug(struct soc_pcmcia_socket *skt, const char *func, + int lvl, const char *fmt, ...); + +#define debug(skt, lvl, fmt, arg...) \ + soc_pcmcia_debug(skt, __func__, lvl, fmt , ## arg) + +#else +#define debug(skt, lvl, fmt, arg...) do { } while (0) +#endif + + +/* + * The PC Card Standard, Release 7, section 4.13.4, says that twIORD + * has a minimum value of 165ns. Section 4.13.5 says that twIOWR has + * a minimum value of 165ns, as well. Section 4.7.2 (describing + * common and attribute memory write timing) says that twWE has a + * minimum value of 150ns for a 250ns cycle time (for 5V operation; + * see section 4.7.4), or 300ns for a 600ns cycle time (for 3.3V + * operation, also section 4.7.4). Section 4.7.3 says that taOE + * has a maximum value of 150ns for a 300ns cycle time (for 5V + * operation), or 300ns for a 600ns cycle time (for 3.3V operation). + * + * When configuring memory maps, Card Services appears to adopt the policy + * that a memory access time of "0" means "use the default." The default + * PCMCIA I/O command width time is 165ns. The default PCMCIA 5V attribute + * and memory command width time is 150ns; the PCMCIA 3.3V attribute and + * memory command width time is 300ns. + */ +#define SOC_PCMCIA_IO_ACCESS (165) +#define SOC_PCMCIA_5V_MEM_ACCESS (150) +#define SOC_PCMCIA_3V_MEM_ACCESS (300) +#define SOC_PCMCIA_ATTR_MEM_ACCESS (300) + +/* + * The socket driver actually works nicely in interrupt-driven form, + * so the (relatively infrequent) polling is "just to be sure." + */ +#define SOC_PCMCIA_POLL_PERIOD (2*HZ) + + +/* I/O pins replacing memory pins + * (PCMCIA System Architecture, 2nd ed., by Don Anderson, p.75) + * + * These signals change meaning when going from memory-only to + * memory-or-I/O interface: + */ +#define iostschg bvd1 +#define iospkr bvd2 + +#endif diff --git a/drivers/pcmcia/socket_sysfs.c b/drivers/pcmcia/socket_sysfs.c new file mode 100644 index 000000000000..8eed03938214 --- /dev/null +++ b/drivers/pcmcia/socket_sysfs.c @@ -0,0 +1,228 @@ +/* + * socket_sysfs.c -- most of socket-related sysfs output + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * (C) 2003 - 2004 Dominik Brodowski + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/config.h> +#include <linux/string.h> +#include <linux/major.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/pci.h> +#include <linux/device.h> +#include <asm/system.h> +#include <asm/irq.h> + +#define IN_CARD_SERVICES +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/ds.h> +#include "cs_internal.h" + +#define to_socket(_dev) container_of(_dev, struct pcmcia_socket, dev) + +static ssize_t pccard_show_type(struct class_device *dev, char *buf) +{ + int val; + struct pcmcia_socket *s = to_socket(dev); + + if (!(s->state & SOCKET_PRESENT)) + return -ENODEV; + s->ops->get_status(s, &val); + if (val & SS_CARDBUS) + return sprintf(buf, "32-bit\n"); + if (val & SS_DETECT) + return sprintf(buf, "16-bit\n"); + return sprintf(buf, "invalid\n"); +} +static CLASS_DEVICE_ATTR(card_type, 0400, pccard_show_type, NULL); + +static ssize_t pccard_show_voltage(struct class_device *dev, char *buf) +{ + int val; + struct pcmcia_socket *s = to_socket(dev); + + if (!(s->state & SOCKET_PRESENT)) + return -ENODEV; + s->ops->get_status(s, &val); + if (val & SS_3VCARD) + return sprintf(buf, "3.3V\n"); + if (val & SS_XVCARD) + return sprintf(buf, "X.XV\n"); + return sprintf(buf, "5.0V\n"); +} +static CLASS_DEVICE_ATTR(card_voltage, 0400, pccard_show_voltage, NULL); + +static ssize_t pccard_show_vpp(struct class_device *dev, char *buf) +{ + struct pcmcia_socket *s = to_socket(dev); + if (!(s->state & SOCKET_PRESENT)) + return -ENODEV; + return sprintf(buf, "%d.%dV\n", s->socket.Vpp / 10, s->socket.Vpp % 10); +} +static CLASS_DEVICE_ATTR(card_vpp, 0400, pccard_show_vpp, NULL); + +static ssize_t pccard_show_vcc(struct class_device *dev, char *buf) +{ + struct pcmcia_socket *s = to_socket(dev); + if (!(s->state & SOCKET_PRESENT)) + return -ENODEV; + return sprintf(buf, "%d.%dV\n", s->socket.Vcc / 10, s->socket.Vcc % 10); +} +static CLASS_DEVICE_ATTR(card_vcc, 0400, pccard_show_vcc, NULL); + + +static ssize_t pccard_store_insert(struct class_device *dev, const char *buf, size_t count) +{ + ssize_t ret; + struct pcmcia_socket *s = to_socket(dev); + + if (!count) + return -EINVAL; + + ret = pcmcia_insert_card(s); + + return ret ? ret : count; +} +static CLASS_DEVICE_ATTR(card_insert, 0200, NULL, pccard_store_insert); + +static ssize_t pccard_store_eject(struct class_device *dev, const char *buf, size_t count) +{ + ssize_t ret; + struct pcmcia_socket *s = to_socket(dev); + + if (!count) + return -EINVAL; + + ret = pcmcia_eject_card(s); + + return ret ? ret : count; +} +static CLASS_DEVICE_ATTR(card_eject, 0200, NULL, pccard_store_eject); + + +static ssize_t pccard_show_irq_mask(struct class_device *dev, char *buf) +{ + struct pcmcia_socket *s = to_socket(dev); + return sprintf(buf, "0x%04x\n", s->irq_mask); +} + +static ssize_t pccard_store_irq_mask(struct class_device *dev, const char *buf, size_t count) +{ + ssize_t ret; + struct pcmcia_socket *s = to_socket(dev); + u32 mask; + + if (!count) + return -EINVAL; + + ret = sscanf (buf, "0x%x\n", &mask); + + if (ret == 1) { + s->irq_mask &= mask; + ret = 0; + } + + return ret ? ret : count; +} +static CLASS_DEVICE_ATTR(card_irq_mask, 0600, pccard_show_irq_mask, pccard_store_irq_mask); + + +static ssize_t pccard_show_resource(struct class_device *dev, char *buf) +{ + struct pcmcia_socket *s = to_socket(dev); + return sprintf(buf, "%s\n", s->resource_setup_done ? "yes" : "no"); +} + +static ssize_t pccard_store_resource(struct class_device *dev, const char *buf, size_t count) +{ + unsigned long flags; + struct pcmcia_socket *s = to_socket(dev); + + if (!count) + return -EINVAL; + + spin_lock_irqsave(&s->lock, flags); + if (!s->resource_setup_done) { + s->resource_setup_done = 1; + spin_unlock_irqrestore(&s->lock, flags); + + down(&s->skt_sem); + if ((s->callback) && + (s->state & SOCKET_PRESENT) && + !(s->state & SOCKET_CARDBUS)) { + if (try_module_get(s->callback->owner)) { + s->callback->resources_done(s); + module_put(s->callback->owner); + } + } + up(&s->skt_sem); + + return count; + } + spin_unlock_irqrestore(&s->lock, flags); + + return count; +} +static CLASS_DEVICE_ATTR(available_resources_setup_done, 0600, pccard_show_resource, pccard_store_resource); + + +static struct class_device_attribute *pccard_socket_attributes[] = { + &class_device_attr_card_type, + &class_device_attr_card_voltage, + &class_device_attr_card_vpp, + &class_device_attr_card_vcc, + &class_device_attr_card_insert, + &class_device_attr_card_eject, + &class_device_attr_card_irq_mask, + &class_device_attr_available_resources_setup_done, + NULL, +}; + +static int __devinit pccard_sysfs_add_socket(struct class_device *class_dev) +{ + struct class_device_attribute **attr; + int ret = 0; + + for (attr = pccard_socket_attributes; *attr; attr++) { + ret = class_device_create_file(class_dev, *attr); + if (ret) + break; + } + + return ret; +} + +static void __devexit pccard_sysfs_remove_socket(struct class_device *class_dev) +{ + struct class_device_attribute **attr; + + for (attr = pccard_socket_attributes; *attr; attr++) + class_device_remove_file(class_dev, *attr); +} + +struct class_interface pccard_sysfs_interface = { + .class = &pcmcia_socket_class, + .add = &pccard_sysfs_add_socket, + .remove = __devexit_p(&pccard_sysfs_remove_socket), +}; diff --git a/drivers/pcmcia/tcic.c b/drivers/pcmcia/tcic.c new file mode 100644 index 000000000000..aacbbb5f055d --- /dev/null +++ b/drivers/pcmcia/tcic.c @@ -0,0 +1,903 @@ +/*====================================================================== + + Device driver for Databook TCIC-2 PCMCIA controller + + tcic.c 1.111 2000/02/15 04:13:12 + + The contents of this file are subject to the Mozilla Public + License Version 1.1 (the "License"); you may not use this file + except in compliance with the License. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS + IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + implied. See the License for the specific language governing + rights and limitations under the License. + + The initial developer of the original code is David A. Hinds + <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + + Alternatively, the contents of this file may be used under the + terms of the GNU General Public License version 2 (the "GPL"), in which + case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/device.h> +#include <linux/bitops.h> + +#include <asm/io.h> +#include <asm/system.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/ss.h> +#include "tcic.h" + +#ifdef DEBUG +static int pc_debug; + +module_param(pc_debug, int, 0644); +static const char version[] = +"tcic.c 1.111 2000/02/15 04:13:12 (David Hinds)"; + +#define debug(lvl, fmt, arg...) do { \ + if (pc_debug > (lvl)) \ + printk(KERN_DEBUG "tcic: " fmt , ## arg); \ +} while (0) +#else +#define debug(lvl, fmt, arg...) do { } while (0) +#endif + +MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>"); +MODULE_DESCRIPTION("Databook TCIC-2 PCMCIA socket driver"); +MODULE_LICENSE("Dual MPL/GPL"); + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +/* The base port address of the TCIC-2 chip */ +static unsigned long tcic_base = TCIC_BASE; + +/* Specify a socket number to ignore */ +static int ignore = -1; + +/* Probe for safe interrupts? */ +static int do_scan = 1; + +/* Bit map of interrupts to choose from */ +static u_int irq_mask = 0xffff; +static int irq_list[16]; +static int irq_list_count; + +/* The card status change interrupt -- 0 means autoselect */ +static int cs_irq; + +/* Poll status interval -- 0 means default to interrupt */ +static int poll_interval; + +/* Delay for card status double-checking */ +static int poll_quick = HZ/20; + +/* CCLK external clock time, in nanoseconds. 70 ns = 14.31818 MHz */ +static int cycle_time = 70; + +module_param(tcic_base, ulong, 0444); +module_param(ignore, int, 0444); +module_param(do_scan, int, 0444); +module_param(irq_mask, int, 0444); +module_param_array(irq_list, int, &irq_list_count, 0444); +module_param(cs_irq, int, 0444); +module_param(poll_interval, int, 0444); +module_param(poll_quick, int, 0444); +module_param(cycle_time, int, 0444); + +/*====================================================================*/ + +static irqreturn_t tcic_interrupt(int irq, void *dev, struct pt_regs *regs); +static void tcic_timer(u_long data); +static struct pccard_operations tcic_operations; + +struct tcic_socket { + u_short psock; + u_char last_sstat; + u_char id; + struct pcmcia_socket socket; +}; + +static struct timer_list poll_timer; +static int tcic_timer_pending; + +static int sockets; +static struct tcic_socket socket_table[2]; + +/*====================================================================*/ + +/* Trick when selecting interrupts: the TCIC sktirq pin is supposed + to map to irq 11, but is coded as 0 or 1 in the irq registers. */ +#define TCIC_IRQ(x) ((x) ? (((x) == 11) ? 1 : (x)) : 15) + +#ifdef DEBUG_X +static u_char tcic_getb(u_char reg) +{ + u_char val = inb(tcic_base+reg); + printk(KERN_DEBUG "tcic_getb(%#lx) = %#x\n", tcic_base+reg, val); + return val; +} + +static u_short tcic_getw(u_char reg) +{ + u_short val = inw(tcic_base+reg); + printk(KERN_DEBUG "tcic_getw(%#lx) = %#x\n", tcic_base+reg, val); + return val; +} + +static void tcic_setb(u_char reg, u_char data) +{ + printk(KERN_DEBUG "tcic_setb(%#lx, %#x)\n", tcic_base+reg, data); + outb(data, tcic_base+reg); +} + +static void tcic_setw(u_char reg, u_short data) +{ + printk(KERN_DEBUG "tcic_setw(%#lx, %#x)\n", tcic_base+reg, data); + outw(data, tcic_base+reg); +} +#else +#define tcic_getb(reg) inb(tcic_base+reg) +#define tcic_getw(reg) inw(tcic_base+reg) +#define tcic_setb(reg, data) outb(data, tcic_base+reg) +#define tcic_setw(reg, data) outw(data, tcic_base+reg) +#endif + +static void tcic_setl(u_char reg, u_int data) +{ +#ifdef DEBUG_X + printk(KERN_DEBUG "tcic_setl(%#x, %#lx)\n", tcic_base+reg, data); +#endif + outw(data & 0xffff, tcic_base+reg); + outw(data >> 16, tcic_base+reg+2); +} + +static u_char tcic_aux_getb(u_short reg) +{ + u_char mode = (tcic_getb(TCIC_MODE) & TCIC_MODE_PGMMASK) | reg; + tcic_setb(TCIC_MODE, mode); + return tcic_getb(TCIC_AUX); +} + +static void tcic_aux_setb(u_short reg, u_char data) +{ + u_char mode = (tcic_getb(TCIC_MODE) & TCIC_MODE_PGMMASK) | reg; + tcic_setb(TCIC_MODE, mode); + tcic_setb(TCIC_AUX, data); +} + +static u_short tcic_aux_getw(u_short reg) +{ + u_char mode = (tcic_getb(TCIC_MODE) & TCIC_MODE_PGMMASK) | reg; + tcic_setb(TCIC_MODE, mode); + return tcic_getw(TCIC_AUX); +} + +static void tcic_aux_setw(u_short reg, u_short data) +{ + u_char mode = (tcic_getb(TCIC_MODE) & TCIC_MODE_PGMMASK) | reg; + tcic_setb(TCIC_MODE, mode); + tcic_setw(TCIC_AUX, data); +} + +/*====================================================================*/ + +/* Time conversion functions */ + +static int to_cycles(int ns) +{ + if (ns < 14) + return 0; + else + return 2*(ns-14)/cycle_time; +} + +/*====================================================================*/ + +static volatile u_int irq_hits; + +static irqreturn_t __init tcic_irq_count(int irq, void *dev, struct pt_regs *regs) +{ + irq_hits++; + return IRQ_HANDLED; +} + +static u_int __init try_irq(int irq) +{ + u_short cfg; + + irq_hits = 0; + if (request_irq(irq, tcic_irq_count, 0, "irq scan", tcic_irq_count) != 0) + return -1; + mdelay(10); + if (irq_hits) { + free_irq(irq, tcic_irq_count); + return -1; + } + + /* Generate one interrupt */ + cfg = TCIC_SYSCFG_AUTOBUSY | 0x0a00; + tcic_aux_setw(TCIC_AUX_SYSCFG, cfg | TCIC_IRQ(irq)); + tcic_setb(TCIC_IENA, TCIC_IENA_ERR | TCIC_IENA_CFG_HIGH); + tcic_setb(TCIC_ICSR, TCIC_ICSR_ERR | TCIC_ICSR_JAM); + + udelay(1000); + free_irq(irq, tcic_irq_count); + + /* Turn off interrupts */ + tcic_setb(TCIC_IENA, TCIC_IENA_CFG_OFF); + while (tcic_getb(TCIC_ICSR)) + tcic_setb(TCIC_ICSR, TCIC_ICSR_JAM); + tcic_aux_setw(TCIC_AUX_SYSCFG, cfg); + + return (irq_hits != 1); +} + +static u_int __init irq_scan(u_int mask0) +{ + u_int mask1; + int i; + +#ifdef __alpha__ +#define PIC 0x4d0 + /* Don't probe level-triggered interrupts -- reserved for PCI */ + int level_mask = inb_p(PIC) | (inb_p(PIC+1) << 8); + if (level_mask) + mask0 &= ~level_mask; +#endif + + mask1 = 0; + if (do_scan) { + for (i = 0; i < 16; i++) + if ((mask0 & (1 << i)) && (try_irq(i) == 0)) + mask1 |= (1 << i); + for (i = 0; i < 16; i++) + if ((mask1 & (1 << i)) && (try_irq(i) != 0)) { + mask1 ^= (1 << i); + } + } + + if (mask1) { + printk("scanned"); + } else { + /* Fallback: just find interrupts that aren't in use */ + for (i = 0; i < 16; i++) + if ((mask0 & (1 << i)) && + (request_irq(i, tcic_irq_count, 0, "x", tcic_irq_count) == 0)) { + mask1 |= (1 << i); + free_irq(i, tcic_irq_count); + } + printk("default"); + } + + printk(") = "); + for (i = 0; i < 16; i++) + if (mask1 & (1<<i)) + printk("%s%d", ((mask1 & ((1<<i)-1)) ? "," : ""), i); + printk(" "); + + return mask1; +} + +/*====================================================================== + + See if a card is present, powered up, in IO mode, and already + bound to a (non-PCMCIA) Linux driver. + + We make an exception for cards that look like serial devices. + +======================================================================*/ + +static int __init is_active(int s) +{ + u_short scf1, ioctl, base, num; + u_char pwr, sstat; + u_int addr; + + tcic_setl(TCIC_ADDR, (s << TCIC_ADDR_SS_SHFT) + | TCIC_ADDR_INDREG | TCIC_SCF1(s)); + scf1 = tcic_getw(TCIC_DATA); + pwr = tcic_getb(TCIC_PWR); + sstat = tcic_getb(TCIC_SSTAT); + addr = TCIC_IWIN(s, 0); + tcic_setw(TCIC_ADDR, addr + TCIC_IBASE_X); + base = tcic_getw(TCIC_DATA); + tcic_setw(TCIC_ADDR, addr + TCIC_ICTL_X); + ioctl = tcic_getw(TCIC_DATA); + + if (ioctl & TCIC_ICTL_TINY) + num = 1; + else { + num = (base ^ (base-1)); + base = base & (base-1); + } + + if ((sstat & TCIC_SSTAT_CD) && (pwr & TCIC_PWR_VCC(s)) && + (scf1 & TCIC_SCF1_IOSTS) && (ioctl & TCIC_ICTL_ENA) && + ((base & 0xfeef) != 0x02e8)) { + struct resource *res = request_region(base, num, "tcic-2"); + if (!res) /* region is busy */ + return 1; + release_region(base, num); + } + + return 0; +} + +/*====================================================================== + + This returns the revision code for the specified socket. + +======================================================================*/ + +static int __init get_tcic_id(void) +{ + u_short id; + + tcic_aux_setw(TCIC_AUX_TEST, TCIC_TEST_DIAG); + id = tcic_aux_getw(TCIC_AUX_ILOCK); + id = (id & TCIC_ILOCKTEST_ID_MASK) >> TCIC_ILOCKTEST_ID_SH; + tcic_aux_setw(TCIC_AUX_TEST, 0); + return id; +} + +/*====================================================================*/ + +static int tcic_drv_suspend(struct device *dev, pm_message_t state, u32 level) +{ + int ret = 0; + if (level == SUSPEND_SAVE_STATE) + ret = pcmcia_socket_dev_suspend(dev, state); + return ret; +} + +static int tcic_drv_resume(struct device *dev, u32 level) +{ + int ret = 0; + if (level == RESUME_RESTORE_STATE) + ret = pcmcia_socket_dev_resume(dev); + return ret; +} + +static struct device_driver tcic_driver = { + .name = "tcic-pcmcia", + .bus = &platform_bus_type, + .suspend = tcic_drv_suspend, + .resume = tcic_drv_resume, +}; + +static struct platform_device tcic_device = { + .name = "tcic-pcmcia", + .id = 0, +}; + + +static int __init init_tcic(void) +{ + int i, sock, ret = 0; + u_int mask, scan; + + if (driver_register(&tcic_driver)) + return -1; + + printk(KERN_INFO "Databook TCIC-2 PCMCIA probe: "); + sock = 0; + + if (!request_region(tcic_base, 16, "tcic-2")) { + printk("could not allocate ports,\n "); + driver_unregister(&tcic_driver); + return -ENODEV; + } + else { + tcic_setw(TCIC_ADDR, 0); + if (tcic_getw(TCIC_ADDR) == 0) { + tcic_setw(TCIC_ADDR, 0xc3a5); + if (tcic_getw(TCIC_ADDR) == 0xc3a5) sock = 2; + } + if (sock == 0) { + /* See if resetting the controller does any good */ + tcic_setb(TCIC_SCTRL, TCIC_SCTRL_RESET); + tcic_setb(TCIC_SCTRL, 0); + tcic_setw(TCIC_ADDR, 0); + if (tcic_getw(TCIC_ADDR) == 0) { + tcic_setw(TCIC_ADDR, 0xc3a5); + if (tcic_getw(TCIC_ADDR) == 0xc3a5) sock = 2; + } + } + } + if (sock == 0) { + printk("not found.\n"); + release_region(tcic_base, 16); + driver_unregister(&tcic_driver); + return -ENODEV; + } + + sockets = 0; + for (i = 0; i < sock; i++) { + if ((i == ignore) || is_active(i)) continue; + socket_table[sockets].psock = i; + socket_table[sockets].id = get_tcic_id(); + + socket_table[sockets].socket.owner = THIS_MODULE; + /* only 16-bit cards, memory windows must be size-aligned */ + /* No PCI or CardBus support */ + socket_table[sockets].socket.features = SS_CAP_PCCARD | SS_CAP_MEM_ALIGN; + /* irq 14, 11, 10, 7, 6, 5, 4, 3 */ + socket_table[sockets].socket.irq_mask = 0x4cf8; + /* 4K minimum window size */ + socket_table[sockets].socket.map_size = 0x1000; + sockets++; + } + + switch (socket_table[0].id) { + case TCIC_ID_DB86082: + printk("DB86082"); break; + case TCIC_ID_DB86082A: + printk("DB86082A"); break; + case TCIC_ID_DB86084: + printk("DB86084"); break; + case TCIC_ID_DB86084A: + printk("DB86084A"); break; + case TCIC_ID_DB86072: + printk("DB86072"); break; + case TCIC_ID_DB86184: + printk("DB86184"); break; + case TCIC_ID_DB86082B: + printk("DB86082B"); break; + default: + printk("Unknown ID 0x%02x", socket_table[0].id); + } + + /* Set up polling */ + poll_timer.function = &tcic_timer; + poll_timer.data = 0; + init_timer(&poll_timer); + + /* Build interrupt mask */ + printk(", %d sockets\n" KERN_INFO " irq list (", sockets); + if (irq_list_count == 0) + mask = irq_mask; + else + for (i = mask = 0; i < irq_list_count; i++) + mask |= (1<<irq_list[i]); + + /* irq 14, 11, 10, 7, 6, 5, 4, 3 */ + mask &= 0x4cf8; + /* Scan interrupts */ + mask = irq_scan(mask); + for (i=0;i<sockets;i++) + socket_table[i].socket.irq_mask = mask; + + /* Check for only two interrupts available */ + scan = (mask & (mask-1)); + if (((scan & (scan-1)) == 0) && (poll_interval == 0)) + poll_interval = HZ; + + if (poll_interval == 0) { + /* Avoid irq 12 unless it is explicitly requested */ + u_int cs_mask = mask & ((cs_irq) ? (1<<cs_irq) : ~(1<<12)); + for (i = 15; i > 0; i--) + if ((cs_mask & (1 << i)) && + (request_irq(i, tcic_interrupt, 0, "tcic", + tcic_interrupt) == 0)) + break; + cs_irq = i; + if (cs_irq == 0) poll_interval = HZ; + } + + if (socket_table[0].socket.irq_mask & (1 << 11)) + printk("sktirq is irq 11, "); + if (cs_irq != 0) + printk("status change on irq %d\n", cs_irq); + else + printk("polled status, interval = %d ms\n", + poll_interval * 1000 / HZ); + + for (i = 0; i < sockets; i++) { + tcic_setw(TCIC_ADDR+2, socket_table[i].psock << TCIC_SS_SHFT); + socket_table[i].last_sstat = tcic_getb(TCIC_SSTAT); + } + + /* jump start interrupt handler, if needed */ + tcic_interrupt(0, NULL, NULL); + + platform_device_register(&tcic_device); + + for (i = 0; i < sockets; i++) { + socket_table[i].socket.ops = &tcic_operations; + socket_table[i].socket.resource_ops = &pccard_nonstatic_ops; + socket_table[i].socket.dev.dev = &tcic_device.dev; + ret = pcmcia_register_socket(&socket_table[i].socket); + if (ret && i) + pcmcia_unregister_socket(&socket_table[0].socket); + } + + return ret; + + return 0; + +} /* init_tcic */ + +/*====================================================================*/ + +static void __exit exit_tcic(void) +{ + int i; + + del_timer_sync(&poll_timer); + if (cs_irq != 0) { + tcic_aux_setw(TCIC_AUX_SYSCFG, TCIC_SYSCFG_AUTOBUSY|0x0a00); + free_irq(cs_irq, tcic_interrupt); + } + release_region(tcic_base, 16); + + for (i = 0; i < sockets; i++) { + pcmcia_unregister_socket(&socket_table[i].socket); + } + + platform_device_unregister(&tcic_device); + driver_unregister(&tcic_driver); +} /* exit_tcic */ + +/*====================================================================*/ + +static irqreturn_t tcic_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + int i, quick = 0; + u_char latch, sstat; + u_short psock; + u_int events; + static volatile int active = 0; + + if (active) { + printk(KERN_NOTICE "tcic: reentered interrupt handler!\n"); + return IRQ_NONE; + } else + active = 1; + + debug(2, "tcic_interrupt()\n"); + + for (i = 0; i < sockets; i++) { + psock = socket_table[i].psock; + tcic_setl(TCIC_ADDR, (psock << TCIC_ADDR_SS_SHFT) + | TCIC_ADDR_INDREG | TCIC_SCF1(psock)); + sstat = tcic_getb(TCIC_SSTAT); + latch = sstat ^ socket_table[psock].last_sstat; + socket_table[i].last_sstat = sstat; + if (tcic_getb(TCIC_ICSR) & TCIC_ICSR_CDCHG) { + tcic_setb(TCIC_ICSR, TCIC_ICSR_CLEAR); + quick = 1; + } + if (latch == 0) + continue; + events = (latch & TCIC_SSTAT_CD) ? SS_DETECT : 0; + events |= (latch & TCIC_SSTAT_WP) ? SS_WRPROT : 0; + if (tcic_getw(TCIC_DATA) & TCIC_SCF1_IOSTS) { + events |= (latch & TCIC_SSTAT_LBAT1) ? SS_STSCHG : 0; + } else { + events |= (latch & TCIC_SSTAT_RDY) ? SS_READY : 0; + events |= (latch & TCIC_SSTAT_LBAT1) ? SS_BATDEAD : 0; + events |= (latch & TCIC_SSTAT_LBAT2) ? SS_BATWARN : 0; + } + if (events) { + pcmcia_parse_events(&socket_table[i].socket, events); + } + } + + /* Schedule next poll, if needed */ + if (((cs_irq == 0) || quick) && (!tcic_timer_pending)) { + poll_timer.expires = jiffies + (quick ? poll_quick : poll_interval); + add_timer(&poll_timer); + tcic_timer_pending = 1; + } + active = 0; + + debug(2, "interrupt done\n"); + return IRQ_HANDLED; +} /* tcic_interrupt */ + +static void tcic_timer(u_long data) +{ + debug(2, "tcic_timer()\n"); + tcic_timer_pending = 0; + tcic_interrupt(0, NULL, NULL); +} /* tcic_timer */ + +/*====================================================================*/ + +static int tcic_get_status(struct pcmcia_socket *sock, u_int *value) +{ + u_short psock = container_of(sock, struct tcic_socket, socket)->psock; + u_char reg; + + tcic_setl(TCIC_ADDR, (psock << TCIC_ADDR_SS_SHFT) + | TCIC_ADDR_INDREG | TCIC_SCF1(psock)); + reg = tcic_getb(TCIC_SSTAT); + *value = (reg & TCIC_SSTAT_CD) ? SS_DETECT : 0; + *value |= (reg & TCIC_SSTAT_WP) ? SS_WRPROT : 0; + if (tcic_getw(TCIC_DATA) & TCIC_SCF1_IOSTS) { + *value |= (reg & TCIC_SSTAT_LBAT1) ? SS_STSCHG : 0; + } else { + *value |= (reg & TCIC_SSTAT_RDY) ? SS_READY : 0; + *value |= (reg & TCIC_SSTAT_LBAT1) ? SS_BATDEAD : 0; + *value |= (reg & TCIC_SSTAT_LBAT2) ? SS_BATWARN : 0; + } + reg = tcic_getb(TCIC_PWR); + if (reg & (TCIC_PWR_VCC(psock)|TCIC_PWR_VPP(psock))) + *value |= SS_POWERON; + debug(1, "GetStatus(%d) = %#2.2x\n", psock, *value); + return 0; +} /* tcic_get_status */ + +/*====================================================================*/ + +static int tcic_get_socket(struct pcmcia_socket *sock, socket_state_t *state) +{ + u_short psock = container_of(sock, struct tcic_socket, socket)->psock; + u_char reg; + u_short scf1, scf2; + + tcic_setl(TCIC_ADDR, (psock << TCIC_ADDR_SS_SHFT) + | TCIC_ADDR_INDREG | TCIC_SCF1(psock)); + scf1 = tcic_getw(TCIC_DATA); + state->flags = (scf1 & TCIC_SCF1_IOSTS) ? SS_IOCARD : 0; + state->flags |= (scf1 & TCIC_SCF1_DMA_MASK) ? SS_DMA_MODE : 0; + state->flags |= (scf1 & TCIC_SCF1_SPKR) ? SS_SPKR_ENA : 0; + if (tcic_getb(TCIC_SCTRL) & TCIC_SCTRL_ENA) + state->flags |= SS_OUTPUT_ENA; + state->io_irq = scf1 & TCIC_SCF1_IRQ_MASK; + if (state->io_irq == 1) state->io_irq = 11; + + reg = tcic_getb(TCIC_PWR); + state->Vcc = state->Vpp = 0; + if (reg & TCIC_PWR_VCC(psock)) { + if (reg & TCIC_PWR_VPP(psock)) + state->Vcc = 50; + else + state->Vcc = state->Vpp = 50; + } else { + if (reg & TCIC_PWR_VPP(psock)) { + state->Vcc = 50; + state->Vpp = 120; + } + } + reg = tcic_aux_getb(TCIC_AUX_ILOCK); + state->flags |= (reg & TCIC_ILOCK_CRESET) ? SS_RESET : 0; + + /* Card status change interrupt mask */ + tcic_setw(TCIC_ADDR, TCIC_SCF2(psock)); + scf2 = tcic_getw(TCIC_DATA); + state->csc_mask = (scf2 & TCIC_SCF2_MCD) ? 0 : SS_DETECT; + if (state->flags & SS_IOCARD) { + state->csc_mask |= (scf2 & TCIC_SCF2_MLBAT1) ? 0 : SS_STSCHG; + } else { + state->csc_mask |= (scf2 & TCIC_SCF2_MLBAT1) ? 0 : SS_BATDEAD; + state->csc_mask |= (scf2 & TCIC_SCF2_MLBAT2) ? 0 : SS_BATWARN; + state->csc_mask |= (scf2 & TCIC_SCF2_MRDY) ? 0 : SS_READY; + } + + debug(1, "GetSocket(%d) = flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x\n", psock, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + return 0; +} /* tcic_get_socket */ + +/*====================================================================*/ + +static int tcic_set_socket(struct pcmcia_socket *sock, socket_state_t *state) +{ + u_short psock = container_of(sock, struct tcic_socket, socket)->psock; + u_char reg; + u_short scf1, scf2; + + debug(1, "SetSocket(%d, flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x)\n", psock, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + tcic_setw(TCIC_ADDR+2, (psock << TCIC_SS_SHFT) | TCIC_ADR2_INDREG); + + reg = tcic_getb(TCIC_PWR); + reg &= ~(TCIC_PWR_VCC(psock) | TCIC_PWR_VPP(psock)); + + if (state->Vcc == 50) { + switch (state->Vpp) { + case 0: reg |= TCIC_PWR_VCC(psock) | TCIC_PWR_VPP(psock); break; + case 50: reg |= TCIC_PWR_VCC(psock); break; + case 120: reg |= TCIC_PWR_VPP(psock); break; + default: return -EINVAL; + } + } else if (state->Vcc != 0) + return -EINVAL; + + if (reg != tcic_getb(TCIC_PWR)) + tcic_setb(TCIC_PWR, reg); + + reg = TCIC_ILOCK_HOLD_CCLK | TCIC_ILOCK_CWAIT; + if (state->flags & SS_OUTPUT_ENA) { + tcic_setb(TCIC_SCTRL, TCIC_SCTRL_ENA); + reg |= TCIC_ILOCK_CRESENA; + } else + tcic_setb(TCIC_SCTRL, 0); + if (state->flags & SS_RESET) + reg |= TCIC_ILOCK_CRESET; + tcic_aux_setb(TCIC_AUX_ILOCK, reg); + + tcic_setw(TCIC_ADDR, TCIC_SCF1(psock)); + scf1 = TCIC_SCF1_FINPACK; + scf1 |= TCIC_IRQ(state->io_irq); + if (state->flags & SS_IOCARD) { + scf1 |= TCIC_SCF1_IOSTS; + if (state->flags & SS_SPKR_ENA) + scf1 |= TCIC_SCF1_SPKR; + if (state->flags & SS_DMA_MODE) + scf1 |= TCIC_SCF1_DREQ2 << TCIC_SCF1_DMA_SHIFT; + } + tcic_setw(TCIC_DATA, scf1); + + /* Some general setup stuff, and configure status interrupt */ + reg = TCIC_WAIT_ASYNC | TCIC_WAIT_SENSE | to_cycles(250); + tcic_aux_setb(TCIC_AUX_WCTL, reg); + tcic_aux_setw(TCIC_AUX_SYSCFG, TCIC_SYSCFG_AUTOBUSY|0x0a00| + TCIC_IRQ(cs_irq)); + + /* Card status change interrupt mask */ + tcic_setw(TCIC_ADDR, TCIC_SCF2(psock)); + scf2 = TCIC_SCF2_MALL; + if (state->csc_mask & SS_DETECT) scf2 &= ~TCIC_SCF2_MCD; + if (state->flags & SS_IOCARD) { + if (state->csc_mask & SS_STSCHG) reg &= ~TCIC_SCF2_MLBAT1; + } else { + if (state->csc_mask & SS_BATDEAD) reg &= ~TCIC_SCF2_MLBAT1; + if (state->csc_mask & SS_BATWARN) reg &= ~TCIC_SCF2_MLBAT2; + if (state->csc_mask & SS_READY) reg &= ~TCIC_SCF2_MRDY; + } + tcic_setw(TCIC_DATA, scf2); + /* For the ISA bus, the irq should be active-high totem-pole */ + tcic_setb(TCIC_IENA, TCIC_IENA_CDCHG | TCIC_IENA_CFG_HIGH); + + return 0; +} /* tcic_set_socket */ + +/*====================================================================*/ + +static int tcic_set_io_map(struct pcmcia_socket *sock, struct pccard_io_map *io) +{ + u_short psock = container_of(sock, struct tcic_socket, socket)->psock; + u_int addr; + u_short base, len, ioctl; + + debug(1, "SetIOMap(%d, %d, %#2.2x, %d ns, " + "%#lx-%#lx)\n", psock, io->map, io->flags, + io->speed, io->start, io->stop); + if ((io->map > 1) || (io->start > 0xffff) || (io->stop > 0xffff) || + (io->stop < io->start)) return -EINVAL; + tcic_setw(TCIC_ADDR+2, TCIC_ADR2_INDREG | (psock << TCIC_SS_SHFT)); + addr = TCIC_IWIN(psock, io->map); + + base = io->start; len = io->stop - io->start; + /* Check to see that len+1 is power of two, etc */ + if ((len & (len+1)) || (base & len)) return -EINVAL; + base |= (len+1)>>1; + tcic_setw(TCIC_ADDR, addr + TCIC_IBASE_X); + tcic_setw(TCIC_DATA, base); + + ioctl = (psock << TCIC_ICTL_SS_SHFT); + ioctl |= (len == 0) ? TCIC_ICTL_TINY : 0; + ioctl |= (io->flags & MAP_ACTIVE) ? TCIC_ICTL_ENA : 0; + ioctl |= to_cycles(io->speed) & TCIC_ICTL_WSCNT_MASK; + if (!(io->flags & MAP_AUTOSZ)) { + ioctl |= TCIC_ICTL_QUIET; + ioctl |= (io->flags & MAP_16BIT) ? TCIC_ICTL_BW_16 : TCIC_ICTL_BW_8; + } + tcic_setw(TCIC_ADDR, addr + TCIC_ICTL_X); + tcic_setw(TCIC_DATA, ioctl); + + return 0; +} /* tcic_set_io_map */ + +/*====================================================================*/ + +static int tcic_set_mem_map(struct pcmcia_socket *sock, struct pccard_mem_map *mem) +{ + u_short psock = container_of(sock, struct tcic_socket, socket)->psock; + u_short addr, ctl; + u_long base, len, mmap; + + debug(1, "SetMemMap(%d, %d, %#2.2x, %d ns, " + "%#lx-%#lx, %#x)\n", psock, mem->map, mem->flags, + mem->speed, mem->res->start, mem->res->end, mem->card_start); + if ((mem->map > 3) || (mem->card_start > 0x3ffffff) || + (mem->res->start > 0xffffff) || (mem->res->end > 0xffffff) || + (mem->res->start > mem->res->end) || (mem->speed > 1000)) + return -EINVAL; + tcic_setw(TCIC_ADDR+2, TCIC_ADR2_INDREG | (psock << TCIC_SS_SHFT)); + addr = TCIC_MWIN(psock, mem->map); + + base = mem->res->start; len = mem->res->end - mem->res->start; + if ((len & (len+1)) || (base & len)) return -EINVAL; + if (len == 0x0fff) + base = (base >> TCIC_MBASE_HA_SHFT) | TCIC_MBASE_4K_BIT; + else + base = (base | (len+1)>>1) >> TCIC_MBASE_HA_SHFT; + tcic_setw(TCIC_ADDR, addr + TCIC_MBASE_X); + tcic_setw(TCIC_DATA, base); + + mmap = mem->card_start - mem->res->start; + mmap = (mmap >> TCIC_MMAP_CA_SHFT) & TCIC_MMAP_CA_MASK; + if (mem->flags & MAP_ATTRIB) mmap |= TCIC_MMAP_REG; + tcic_setw(TCIC_ADDR, addr + TCIC_MMAP_X); + tcic_setw(TCIC_DATA, mmap); + + ctl = TCIC_MCTL_QUIET | (psock << TCIC_MCTL_SS_SHFT); + ctl |= to_cycles(mem->speed) & TCIC_MCTL_WSCNT_MASK; + ctl |= (mem->flags & MAP_16BIT) ? 0 : TCIC_MCTL_B8; + ctl |= (mem->flags & MAP_WRPROT) ? TCIC_MCTL_WP : 0; + ctl |= (mem->flags & MAP_ACTIVE) ? TCIC_MCTL_ENA : 0; + tcic_setw(TCIC_ADDR, addr + TCIC_MCTL_X); + tcic_setw(TCIC_DATA, ctl); + + return 0; +} /* tcic_set_mem_map */ + +/*====================================================================*/ + +static int tcic_init(struct pcmcia_socket *s) +{ + int i; + struct resource res = { .start = 0, .end = 0x1000 }; + pccard_io_map io = { 0, 0, 0, 0, 1 }; + pccard_mem_map mem = { .res = &res, }; + + for (i = 0; i < 2; i++) { + io.map = i; + tcic_set_io_map(s, &io); + } + for (i = 0; i < 5; i++) { + mem.map = i; + tcic_set_mem_map(s, &mem); + } + return 0; +} + +static struct pccard_operations tcic_operations = { + .init = tcic_init, + .get_status = tcic_get_status, + .get_socket = tcic_get_socket, + .set_socket = tcic_set_socket, + .set_io_map = tcic_set_io_map, + .set_mem_map = tcic_set_mem_map, +}; + +/*====================================================================*/ + +module_init(init_tcic); +module_exit(exit_tcic); diff --git a/drivers/pcmcia/tcic.h b/drivers/pcmcia/tcic.h new file mode 100644 index 000000000000..2c0b8f65ad6c --- /dev/null +++ b/drivers/pcmcia/tcic.h @@ -0,0 +1,266 @@ +/* + * tcic.h 1.13 1999/10/25 20:03:34 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in which + * case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_TCIC_H +#define _LINUX_TCIC_H + +#define TCIC_BASE 0x240 + +/* offsets of registers from TCIC_BASE */ +#define TCIC_DATA 0x00 +#define TCIC_ADDR 0x02 +#define TCIC_SCTRL 0x06 +#define TCIC_SSTAT 0x07 +#define TCIC_MODE 0x08 +#define TCIC_PWR 0x09 +#define TCIC_EDC 0x0A +#define TCIC_ICSR 0x0C +#define TCIC_IENA 0x0D +#define TCIC_AUX 0x0E + +#define TCIC_SS_SHFT 12 +#define TCIC_SS_MASK 0x7000 + +/* Flags for TCIC_ADDR */ +#define TCIC_ADR2_REG 0x8000 +#define TCIC_ADR2_INDREG 0x0800 + +#define TCIC_ADDR_REG 0x80000000 +#define TCIC_ADDR_SS_SHFT (TCIC_SS_SHFT+16) +#define TCIC_ADDR_SS_MASK (TCIC_SS_MASK<<16) +#define TCIC_ADDR_INDREG 0x08000000 +#define TCIC_ADDR_IO 0x04000000 +#define TCIC_ADDR_MASK 0x03ffffff + +/* Flags for TCIC_SCTRL */ +#define TCIC_SCTRL_ENA 0x01 +#define TCIC_SCTRL_INCMODE 0x18 +#define TCIC_SCTRL_INCMODE_HOLD 0x00 +#define TCIC_SCTRL_INCMODE_WORD 0x08 +#define TCIC_SCTRL_INCMODE_REG 0x10 +#define TCIC_SCTRL_INCMODE_AUTO 0x18 +#define TCIC_SCTRL_EDCSUM 0x20 +#define TCIC_SCTRL_RESET 0x80 + +/* Flags for TCIC_SSTAT */ +#define TCIC_SSTAT_6US 0x01 +#define TCIC_SSTAT_10US 0x02 +#define TCIC_SSTAT_PROGTIME 0x04 +#define TCIC_SSTAT_LBAT1 0x08 +#define TCIC_SSTAT_LBAT2 0x10 +#define TCIC_SSTAT_RDY 0x20 /* Inverted */ +#define TCIC_SSTAT_WP 0x40 +#define TCIC_SSTAT_CD 0x80 /* Card detect */ + +/* Flags for TCIC_MODE */ +#define TCIC_MODE_PGMMASK 0x1f +#define TCIC_MODE_NORMAL 0x00 +#define TCIC_MODE_PGMWR 0x01 +#define TCIC_MODE_PGMRD 0x02 +#define TCIC_MODE_PGMCE 0x04 +#define TCIC_MODE_PGMDBW 0x08 +#define TCIC_MODE_PGMWORD 0x10 +#define TCIC_MODE_AUXSEL_MASK 0xe0 + +/* Registers accessed through TCIC_AUX, by setting TCIC_MODE */ +#define TCIC_AUX_TCTL (0<<5) +#define TCIC_AUX_PCTL (1<<5) +#define TCIC_AUX_WCTL (2<<5) +#define TCIC_AUX_EXTERN (3<<5) +#define TCIC_AUX_PDATA (4<<5) +#define TCIC_AUX_SYSCFG (5<<5) +#define TCIC_AUX_ILOCK (6<<5) +#define TCIC_AUX_TEST (7<<5) + +/* Flags for TCIC_PWR */ +#define TCIC_PWR_VCC(sock) (0x01<<(sock)) +#define TCIC_PWR_VCC_MASK 0x03 +#define TCIC_PWR_VPP(sock) (0x08<<(sock)) +#define TCIC_PWR_VPP_MASK 0x18 +#define TCIC_PWR_CLIMENA 0x40 +#define TCIC_PWR_CLIMSTAT 0x80 + +/* Flags for TCIC_ICSR */ +#define TCIC_ICSR_CLEAR 0x01 +#define TCIC_ICSR_SET 0x02 +#define TCIC_ICSR_JAM (TCIC_ICSR_CLEAR|TCIC_ICSR_SET) +#define TCIC_ICSR_STOPCPU 0x04 +#define TCIC_ICSR_ILOCK 0x08 +#define TCIC_ICSR_PROGTIME 0x10 +#define TCIC_ICSR_ERR 0x20 +#define TCIC_ICSR_CDCHG 0x40 +#define TCIC_ICSR_IOCHK 0x80 + +/* Flags for TCIC_IENA */ +#define TCIC_IENA_CFG_MASK 0x03 +#define TCIC_IENA_CFG_OFF 0x00 /* disabled */ +#define TCIC_IENA_CFG_OD 0x01 /* active low, open drain */ +#define TCIC_IENA_CFG_LOW 0x02 /* active low, totem pole */ +#define TCIC_IENA_CFG_HIGH 0x03 /* active high, totem pole */ +#define TCIC_IENA_ILOCK 0x08 +#define TCIC_IENA_PROGTIME 0x10 +#define TCIC_IENA_ERR 0x20 /* overcurrent or iochk */ +#define TCIC_IENA_CDCHG 0x40 + +/* Flags for TCIC_AUX_WCTL */ +#define TCIC_WAIT_COUNT_MASK 0x001f +#define TCIC_WAIT_ASYNC 0x0020 +#define TCIC_WAIT_SENSE 0x0040 +#define TCIC_WAIT_SRC 0x0080 +#define TCIC_WCTL_WR 0x0100 +#define TCIC_WCTL_RD 0x0200 +#define TCIC_WCTL_CE 0x0400 +#define TCIC_WCTL_LLBAT1 0x0800 +#define TCIC_WCTL_LLBAT2 0x1000 +#define TCIC_WCTL_LRDY 0x2000 +#define TCIC_WCTL_LWP 0x4000 +#define TCIC_WCTL_LCD 0x8000 + +/* Flags for TCIC_AUX_SYSCFG */ +#define TCIC_SYSCFG_IRQ_MASK 0x000f +#define TCIC_SYSCFG_MCSFULL 0x0010 +#define TCIC_SYSCFG_IO1723 0x0020 +#define TCIC_SYSCFG_MCSXB 0x0040 +#define TCIC_SYSCFG_ICSXB 0x0080 +#define TCIC_SYSCFG_NOPDN 0x0100 +#define TCIC_SYSCFG_MPSEL_SHFT 9 +#define TCIC_SYSCFG_MPSEL_MASK 0x0e00 +#define TCIC_SYSCFG_MPSENSE 0x2000 +#define TCIC_SYSCFG_AUTOBUSY 0x4000 +#define TCIC_SYSCFG_ACC 0x8000 + +#define TCIC_ILOCK_OUT 0x01 +#define TCIC_ILOCK_SENSE 0x02 +#define TCIC_ILOCK_CRESET 0x04 +#define TCIC_ILOCK_CRESENA 0x08 +#define TCIC_ILOCK_CWAIT 0x10 +#define TCIC_ILOCK_CWAITSNS 0x20 +#define TCIC_ILOCK_HOLD_MASK 0xc0 +#define TCIC_ILOCK_HOLD_CCLK 0xc0 + +#define TCIC_ILOCKTEST_ID_SH 8 +#define TCIC_ILOCKTEST_ID_MASK 0x7f00 +#define TCIC_ILOCKTEST_MCIC_1 0x8000 + +#define TCIC_ID_DB86082 0x02 +#define TCIC_ID_DB86082A 0x03 +#define TCIC_ID_DB86084 0x04 +#define TCIC_ID_DB86084A 0x08 +#define TCIC_ID_DB86072 0x15 +#define TCIC_ID_DB86184 0x14 +#define TCIC_ID_DB86082B 0x17 + +#define TCIC_TEST_DIAG 0x8000 + +/* + * Indirectly addressed registers + */ + +#define TCIC_SCF1(sock) ((sock)<<3) +#define TCIC_SCF2(sock) (((sock)<<3)+2) + +/* Flags for SCF1 */ +#define TCIC_SCF1_IRQ_MASK 0x000f +#define TCIC_SCF1_IRQ_OFF 0x0000 +#define TCIC_SCF1_IRQOC 0x0010 +#define TCIC_SCF1_PCVT 0x0020 +#define TCIC_SCF1_IRDY 0x0040 +#define TCIC_SCF1_ATA 0x0080 +#define TCIC_SCF1_DMA_SHIFT 8 +#define TCIC_SCF1_DMA_MASK 0x0700 +#define TCIC_SCF1_DMA_OFF 0 +#define TCIC_SCF1_DREQ2 2 +#define TCIC_SCF1_IOSTS 0x0800 +#define TCIC_SCF1_SPKR 0x1000 +#define TCIC_SCF1_FINPACK 0x2000 +#define TCIC_SCF1_DELWR 0x4000 +#define TCIC_SCF1_HD7IDE 0x8000 + +/* Flags for SCF2 */ +#define TCIC_SCF2_RI 0x0001 +#define TCIC_SCF2_IDBR 0x0002 +#define TCIC_SCF2_MDBR 0x0004 +#define TCIC_SCF2_MLBAT1 0x0008 +#define TCIC_SCF2_MLBAT2 0x0010 +#define TCIC_SCF2_MRDY 0x0020 +#define TCIC_SCF2_MWP 0x0040 +#define TCIC_SCF2_MCD 0x0080 +#define TCIC_SCF2_MALL 0x00f8 + +/* Indirect addresses for memory window registers */ +#define TCIC_MWIN(sock,map) (0x100+(((map)+((sock)<<2))<<3)) +#define TCIC_MBASE_X 2 +#define TCIC_MMAP_X 4 +#define TCIC_MCTL_X 6 + +#define TCIC_MBASE_4K_BIT 0x4000 +#define TCIC_MBASE_HA_SHFT 12 +#define TCIC_MBASE_HA_MASK 0x0fff + +#define TCIC_MMAP_REG 0x8000 +#define TCIC_MMAP_CA_SHFT 12 +#define TCIC_MMAP_CA_MASK 0x3fff + +#define TCIC_MCTL_WSCNT_MASK 0x001f +#define TCIC_MCTL_WCLK 0x0020 +#define TCIC_MCTL_WCLK_CCLK 0x0000 +#define TCIC_MCTL_WCLK_BCLK 0x0020 +#define TCIC_MCTL_QUIET 0x0040 +#define TCIC_MCTL_WP 0x0080 +#define TCIC_MCTL_ACC 0x0100 +#define TCIC_MCTL_KE 0x0200 +#define TCIC_MCTL_EDC 0x0400 +#define TCIC_MCTL_B8 0x0800 +#define TCIC_MCTL_SS_SHFT TCIC_SS_SHFT +#define TCIC_MCTL_SS_MASK TCIC_SS_MASK +#define TCIC_MCTL_ENA 0x8000 + +/* Indirect addresses for I/O window registers */ +#define TCIC_IWIN(sock,map) (0x200+(((map)+((sock)<<1))<<2)) +#define TCIC_IBASE_X 0 +#define TCIC_ICTL_X 2 + +#define TCIC_ICTL_WSCNT_MASK TCIC_MCTL_WSCNT_MASK +#define TCIC_ICTL_QUIET TCIC_MCTL_QUIET +#define TCIC_ICTL_1K 0x0080 +#define TCIC_ICTL_PASS16 0x0100 +#define TCIC_ICTL_ACC TCIC_MCTL_ACC +#define TCIC_ICTL_TINY 0x0200 +#define TCIC_ICTL_B16 0x0400 +#define TCIC_ICTL_B8 TCIC_MCTL_B8 +#define TCIC_ICTL_BW_MASK (TCIC_ICTL_B16|TCIC_ICTL_B8) +#define TCIC_ICTL_BW_DYN 0 +#define TCIC_ICTL_BW_8 TCIC_ICTL_B8 +#define TCIC_ICTL_BW_16 TCIC_ICTL_B16 +#define TCIC_ICTL_BW_ATA (TCIC_ICTL_B16|TCIC_ICTL_B8) +#define TCIC_ICTL_SS_SHFT TCIC_SS_SHFT +#define TCIC_ICTL_SS_MASK TCIC_SS_MASK +#define TCIC_ICTL_ENA TCIC_MCTL_ENA + +#endif /* _LINUX_TCIC_H */ diff --git a/drivers/pcmcia/ti113x.h b/drivers/pcmcia/ti113x.h new file mode 100644 index 000000000000..52c073a9d7e4 --- /dev/null +++ b/drivers/pcmcia/ti113x.h @@ -0,0 +1,662 @@ +/* + * ti113x.h 1.16 1999/10/25 20:03:34 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in which + * case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_TI113X_H +#define _LINUX_TI113X_H + +#include <linux/config.h> + +/* Register definitions for TI 113X PCI-to-CardBus bridges */ + +/* System Control Register */ +#define TI113X_SYSTEM_CONTROL 0x0080 /* 32 bit */ +#define TI113X_SCR_SMIROUTE 0x04000000 +#define TI113X_SCR_SMISTATUS 0x02000000 +#define TI113X_SCR_SMIENB 0x01000000 +#define TI113X_SCR_VCCPROT 0x00200000 +#define TI113X_SCR_REDUCEZV 0x00100000 +#define TI113X_SCR_CDREQEN 0x00080000 +#define TI113X_SCR_CDMACHAN 0x00070000 +#define TI113X_SCR_SOCACTIVE 0x00002000 +#define TI113X_SCR_PWRSTREAM 0x00000800 +#define TI113X_SCR_DELAYUP 0x00000400 +#define TI113X_SCR_DELAYDOWN 0x00000200 +#define TI113X_SCR_INTERROGATE 0x00000100 +#define TI113X_SCR_CLKRUN_SEL 0x00000080 +#define TI113X_SCR_PWRSAVINGS 0x00000040 +#define TI113X_SCR_SUBSYSRW 0x00000020 +#define TI113X_SCR_CB_DPAR 0x00000010 +#define TI113X_SCR_CDMA_EN 0x00000008 +#define TI113X_SCR_ASYNC_IRQ 0x00000004 +#define TI113X_SCR_KEEPCLK 0x00000002 +#define TI113X_SCR_CLKRUN_ENA 0x00000001 + +#define TI122X_SCR_SER_STEP 0xc0000000 +#define TI122X_SCR_INTRTIE 0x20000000 +#define TI122X_SCR_CBRSVD 0x00400000 +#define TI122X_SCR_MRBURSTDN 0x00008000 +#define TI122X_SCR_MRBURSTUP 0x00004000 +#define TI122X_SCR_RIMUX 0x00000001 + +/* Multimedia Control Register */ +#define TI1250_MULTIMEDIA_CTL 0x0084 /* 8 bit */ +#define TI1250_MMC_ZVOUTEN 0x80 +#define TI1250_MMC_PORTSEL 0x40 +#define TI1250_MMC_ZVEN1 0x02 +#define TI1250_MMC_ZVEN0 0x01 + +#define TI1250_GENERAL_STATUS 0x0085 /* 8 bit */ +#define TI1250_GPIO0_CONTROL 0x0088 /* 8 bit */ +#define TI1250_GPIO1_CONTROL 0x0089 /* 8 bit */ +#define TI1250_GPIO2_CONTROL 0x008a /* 8 bit */ +#define TI1250_GPIO3_CONTROL 0x008b /* 8 bit */ +#define TI1250_GPIO_MODE_MASK 0xc0 + +/* IRQMUX/MFUNC Register */ +#define TI122X_MFUNC 0x008c /* 32 bit */ +#define TI122X_MFUNC0_MASK 0x0000000f +#define TI122X_MFUNC1_MASK 0x000000f0 +#define TI122X_MFUNC2_MASK 0x00000f00 +#define TI122X_MFUNC3_MASK 0x0000f000 +#define TI122X_MFUNC4_MASK 0x000f0000 +#define TI122X_MFUNC5_MASK 0x00f00000 +#define TI122X_MFUNC6_MASK 0x0f000000 + +#define TI122X_MFUNC0_INTA 0x00000002 +#define TI125X_MFUNC0_INTB 0x00000001 +#define TI122X_MFUNC1_INTB 0x00000020 +#define TI122X_MFUNC3_IRQSER 0x00001000 + + +/* Retry Status Register */ +#define TI113X_RETRY_STATUS 0x0090 /* 8 bit */ +#define TI113X_RSR_PCIRETRY 0x80 +#define TI113X_RSR_CBRETRY 0x40 +#define TI113X_RSR_TEXP_CBB 0x20 +#define TI113X_RSR_MEXP_CBB 0x10 +#define TI113X_RSR_TEXP_CBA 0x08 +#define TI113X_RSR_MEXP_CBA 0x04 +#define TI113X_RSR_TEXP_PCI 0x02 +#define TI113X_RSR_MEXP_PCI 0x01 + +/* Card Control Register */ +#define TI113X_CARD_CONTROL 0x0091 /* 8 bit */ +#define TI113X_CCR_RIENB 0x80 +#define TI113X_CCR_ZVENABLE 0x40 +#define TI113X_CCR_PCI_IRQ_ENA 0x20 +#define TI113X_CCR_PCI_IREQ 0x10 +#define TI113X_CCR_PCI_CSC 0x08 +#define TI113X_CCR_SPKROUTEN 0x02 +#define TI113X_CCR_IFG 0x01 + +#define TI1220_CCR_PORT_SEL 0x20 +#define TI122X_CCR_AUD2MUX 0x04 + +/* Device Control Register */ +#define TI113X_DEVICE_CONTROL 0x0092 /* 8 bit */ +#define TI113X_DCR_5V_FORCE 0x40 +#define TI113X_DCR_3V_FORCE 0x20 +#define TI113X_DCR_IMODE_MASK 0x06 +#define TI113X_DCR_IMODE_ISA 0x02 +#define TI113X_DCR_IMODE_SERIAL 0x04 + +#define TI12XX_DCR_IMODE_PCI_ONLY 0x00 +#define TI12XX_DCR_IMODE_ALL_SERIAL 0x06 + +/* Buffer Control Register */ +#define TI113X_BUFFER_CONTROL 0x0093 /* 8 bit */ +#define TI113X_BCR_CB_READ_DEPTH 0x08 +#define TI113X_BCR_CB_WRITE_DEPTH 0x04 +#define TI113X_BCR_PCI_READ_DEPTH 0x02 +#define TI113X_BCR_PCI_WRITE_DEPTH 0x01 + +/* Diagnostic Register */ +#define TI1250_DIAGNOSTIC 0x0093 /* 8 bit */ +#define TI1250_DIAG_TRUE_VALUE 0x80 +#define TI1250_DIAG_PCI_IREQ 0x40 +#define TI1250_DIAG_PCI_CSC 0x20 +#define TI1250_DIAG_ASYNC_CSC 0x01 + +/* DMA Registers */ +#define TI113X_DMA_0 0x0094 /* 32 bit */ +#define TI113X_DMA_1 0x0098 /* 32 bit */ + +/* ExCA IO offset registers */ +#define TI113X_IO_OFFSET(map) (0x36+((map)<<1)) + +/* EnE test register */ +#define ENE_TEST_C9 0xc9 /* 8bit */ +#define ENE_TEST_C9_TLTENABLE 0x02 + +#ifdef CONFIG_CARDBUS + +/* + * Texas Instruments CardBus controller overrides. + */ +#define ti_sysctl(socket) ((socket)->private[0]) +#define ti_cardctl(socket) ((socket)->private[1]) +#define ti_devctl(socket) ((socket)->private[2]) +#define ti_diag(socket) ((socket)->private[3]) +#define ti_mfunc(socket) ((socket)->private[4]) +#define ene_test_c9(socket) ((socket)->private[5]) + +/* + * These are the TI specific power management handlers. + */ +static void ti_save_state(struct yenta_socket *socket) +{ + ti_sysctl(socket) = config_readl(socket, TI113X_SYSTEM_CONTROL); + ti_mfunc(socket) = config_readl(socket, TI122X_MFUNC); + ti_cardctl(socket) = config_readb(socket, TI113X_CARD_CONTROL); + ti_devctl(socket) = config_readb(socket, TI113X_DEVICE_CONTROL); + ti_diag(socket) = config_readb(socket, TI1250_DIAGNOSTIC); + + if (socket->dev->vendor == PCI_VENDOR_ID_ENE) + ene_test_c9(socket) = config_readb(socket, ENE_TEST_C9); +} + +static void ti_restore_state(struct yenta_socket *socket) +{ + config_writel(socket, TI113X_SYSTEM_CONTROL, ti_sysctl(socket)); + config_writel(socket, TI122X_MFUNC, ti_mfunc(socket)); + config_writeb(socket, TI113X_CARD_CONTROL, ti_cardctl(socket)); + config_writeb(socket, TI113X_DEVICE_CONTROL, ti_devctl(socket)); + config_writeb(socket, TI1250_DIAGNOSTIC, ti_diag(socket)); + + if (socket->dev->vendor == PCI_VENDOR_ID_ENE) + config_writeb(socket, ENE_TEST_C9, ene_test_c9(socket)); +} + +/* + * Zoom video control for TI122x/113x chips + */ + +static void ti_zoom_video(struct pcmcia_socket *sock, int onoff) +{ + u8 reg; + struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket); + + /* If we don't have a Zoom Video switch this is harmless, + we just tristate the unused (ZV) lines */ + reg = config_readb(socket, TI113X_CARD_CONTROL); + if (onoff) + /* Zoom zoom, we will all go together, zoom zoom, zoom zoom */ + reg |= TI113X_CCR_ZVENABLE; + else + reg &= ~TI113X_CCR_ZVENABLE; + config_writeb(socket, TI113X_CARD_CONTROL, reg); +} + +/* + * The 145x series can also use this. They have an additional + * ZV autodetect mode we don't use but don't actually need. + * FIXME: manual says its in func0 and func1 but disagrees with + * itself about this - do we need to force func0, if so we need + * to know a lot more about socket pairings in pcmcia_socket than + * we do now.. uggh. + */ + +static void ti1250_zoom_video(struct pcmcia_socket *sock, int onoff) +{ + struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket); + int shift = 0; + u8 reg; + + ti_zoom_video(sock, onoff); + + reg = config_readb(socket, TI1250_MULTIMEDIA_CTL); + reg |= TI1250_MMC_ZVOUTEN; /* ZV bus enable */ + + if(PCI_FUNC(socket->dev->devfn)==1) + shift = 1; + + if(onoff) + { + reg &= ~(1<<6); /* Clear select bit */ + reg |= shift<<6; /* Favour our socket */ + reg |= 1<<shift; /* Socket zoom video on */ + } + else + { + reg &= ~(1<<6); /* Clear select bit */ + reg |= (1^shift)<<6; /* Favour other socket */ + reg &= ~(1<<shift); /* Socket zoon video off */ + } + + config_writeb(socket, TI1250_MULTIMEDIA_CTL, reg); +} + +static void ti_set_zv(struct yenta_socket *socket) +{ + if(socket->dev->vendor == PCI_VENDOR_ID_TI) + { + switch(socket->dev->device) + { + /* There may be more .. */ + case PCI_DEVICE_ID_TI_1220: + case PCI_DEVICE_ID_TI_1221: + case PCI_DEVICE_ID_TI_1225: + case PCI_DEVICE_ID_TI_4510: + socket->socket.zoom_video = ti_zoom_video; + break; + case PCI_DEVICE_ID_TI_1250: + case PCI_DEVICE_ID_TI_1251A: + case PCI_DEVICE_ID_TI_1251B: + case PCI_DEVICE_ID_TI_1450: + socket->socket.zoom_video = ti1250_zoom_video; + } + } +} + + +/* + * Generic TI init - TI has an extension for the + * INTCTL register that sets the PCI CSC interrupt. + * Make sure we set it correctly at open and init + * time + * - override: disable the PCI CSC interrupt. This makes + * it possible to use the CSC interrupt to probe the + * ISA interrupts. + * - init: set the interrupt to match our PCI state. + * This makes us correctly get PCI CSC interrupt + * events. + */ +static int ti_init(struct yenta_socket *socket) +{ + u8 new, reg = exca_readb(socket, I365_INTCTL); + + new = reg & ~I365_INTR_ENA; + if (socket->cb_irq) + new |= I365_INTR_ENA; + if (new != reg) + exca_writeb(socket, I365_INTCTL, new); + return 0; +} + +static int ti_override(struct yenta_socket *socket) +{ + u8 new, reg = exca_readb(socket, I365_INTCTL); + + new = reg & ~I365_INTR_ENA; + if (new != reg) + exca_writeb(socket, I365_INTCTL, new); + + ti_set_zv(socket); + + return 0; +} + +static int ti113x_override(struct yenta_socket *socket) +{ + u8 cardctl; + + cardctl = config_readb(socket, TI113X_CARD_CONTROL); + cardctl &= ~(TI113X_CCR_PCI_IRQ_ENA | TI113X_CCR_PCI_IREQ | TI113X_CCR_PCI_CSC); + if (socket->cb_irq) + cardctl |= TI113X_CCR_PCI_IRQ_ENA | TI113X_CCR_PCI_CSC | TI113X_CCR_PCI_IREQ; + config_writeb(socket, TI113X_CARD_CONTROL, cardctl); + + return ti_override(socket); +} + + +/* irqrouting for func0, probes PCI interrupt and ISA interrupts */ +static void ti12xx_irqroute_func0(struct yenta_socket *socket) +{ + u32 mfunc, mfunc_old, devctl; + u8 gpio3, gpio3_old; + int pci_irq_status; + + mfunc = mfunc_old = config_readl(socket, TI122X_MFUNC); + devctl = config_readb(socket, TI113X_DEVICE_CONTROL); + printk(KERN_INFO "Yenta TI: socket %s, mfunc 0x%08x, devctl 0x%02x\n", + pci_name(socket->dev), mfunc, devctl); + + /* make sure PCI interrupts are enabled before probing */ + ti_init(socket); + + /* test PCI interrupts first. only try fixing if return value is 0! */ + pci_irq_status = yenta_probe_cb_irq(socket); + if (pci_irq_status) + goto out; + + /* + * We're here which means PCI interrupts are _not_ delivered. try to + * find the right setting (all serial or parallel) + */ + printk(KERN_INFO "Yenta TI: socket %s probing PCI interrupt failed, trying to fix\n", + pci_name(socket->dev)); + + /* for serial PCI make sure MFUNC3 is set to IRQSER */ + if ((devctl & TI113X_DCR_IMODE_MASK) == TI12XX_DCR_IMODE_ALL_SERIAL) { + switch (socket->dev->device) { + case PCI_DEVICE_ID_TI_1250: + case PCI_DEVICE_ID_TI_1251A: + case PCI_DEVICE_ID_TI_1251B: + case PCI_DEVICE_ID_TI_1450: + case PCI_DEVICE_ID_TI_1451A: + case PCI_DEVICE_ID_TI_4450: + case PCI_DEVICE_ID_TI_4451: + /* these chips have no IRQSER setting in MFUNC3 */ + break; + + default: + mfunc = (mfunc & ~TI122X_MFUNC3_MASK) | TI122X_MFUNC3_IRQSER; + + /* write down if changed, probe */ + if (mfunc != mfunc_old) { + config_writel(socket, TI122X_MFUNC, mfunc); + + pci_irq_status = yenta_probe_cb_irq(socket); + if (pci_irq_status == 1) { + printk(KERN_INFO "Yenta TI: socket %s all-serial interrupts ok\n", + pci_name(socket->dev)); + mfunc_old = mfunc; + goto out; + } + + /* not working, back to old value */ + mfunc = mfunc_old; + config_writel(socket, TI122X_MFUNC, mfunc); + + if (pci_irq_status == -1) + goto out; + } + } + + /* serial PCI interrupts not working fall back to parallel */ + printk(KERN_INFO "Yenta TI: socket %s falling back to parallel PCI interrupts\n", + pci_name(socket->dev)); + devctl &= ~TI113X_DCR_IMODE_MASK; + devctl |= TI113X_DCR_IMODE_SERIAL; /* serial ISA could be right */ + config_writeb(socket, TI113X_DEVICE_CONTROL, devctl); + } + + /* parallel PCI interrupts: route INTA */ + switch (socket->dev->device) { + case PCI_DEVICE_ID_TI_1250: + case PCI_DEVICE_ID_TI_1251A: + case PCI_DEVICE_ID_TI_1251B: + case PCI_DEVICE_ID_TI_1450: + /* make sure GPIO3 is set to INTA */ + gpio3 = gpio3_old = config_readb(socket, TI1250_GPIO3_CONTROL); + gpio3 &= ~TI1250_GPIO_MODE_MASK; + if (gpio3 != gpio3_old) + config_writeb(socket, TI1250_GPIO3_CONTROL, gpio3); + break; + + default: + gpio3 = gpio3_old = 0; + + mfunc = (mfunc & ~TI122X_MFUNC0_MASK) | TI122X_MFUNC0_INTA; + if (mfunc != mfunc_old) + config_writel(socket, TI122X_MFUNC, mfunc); + } + + /* time to probe again */ + pci_irq_status = yenta_probe_cb_irq(socket); + if (pci_irq_status == 1) { + mfunc_old = mfunc; + printk(KERN_INFO "Yenta TI: socket %s parallel PCI interrupts ok\n", + pci_name(socket->dev)); + } else { + /* not working, back to old value */ + mfunc = mfunc_old; + config_writel(socket, TI122X_MFUNC, mfunc); + if (gpio3 != gpio3_old) + config_writeb(socket, TI1250_GPIO3_CONTROL, gpio3_old); + } + +out: + if (pci_irq_status < 1) { + socket->cb_irq = 0; + printk(KERN_INFO "Yenta TI: socket %s no PCI interrupts. Fish. Please report.\n", + pci_name(socket->dev)); + } +} + + +/* + * ties INTA and INTB together. also changes the devices irq to that of + * the function 0 device. call from func1 only. + * returns 1 if INTRTIE changed, 0 otherwise. + */ +static int ti12xx_tie_interrupts(struct yenta_socket *socket, int *old_irq) +{ + struct pci_dev *func0; + u32 sysctl; + + sysctl = config_readl(socket, TI113X_SYSTEM_CONTROL); + if (sysctl & TI122X_SCR_INTRTIE) + return 0; + + /* find func0 device */ + func0 = pci_get_slot(socket->dev->bus, socket->dev->devfn & ~0x07); + if (!func0) + return 0; + + /* change the interrupt to match func0, tie 'em up */ + *old_irq = socket->cb_irq; + socket->cb_irq = socket->dev->irq = func0->irq; + sysctl |= TI122X_SCR_INTRTIE; + config_writel(socket, TI113X_SYSTEM_CONTROL, sysctl); + + pci_dev_put(func0); + + return 1; +} + +/* undo what ti12xx_tie_interrupts() did */ +static void ti12xx_untie_interrupts(struct yenta_socket *socket, int old_irq) +{ + u32 sysctl = config_readl(socket, TI113X_SYSTEM_CONTROL); + sysctl &= ~TI122X_SCR_INTRTIE; + config_writel(socket, TI113X_SYSTEM_CONTROL, sysctl); + + socket->cb_irq = socket->dev->irq = old_irq; +} + +/* + * irqrouting for func1, plays with INTB routing + * only touches MFUNC for INTB routing. all other bits are taken + * care of in func0 already. + */ +static void ti12xx_irqroute_func1(struct yenta_socket *socket) +{ + u32 mfunc, mfunc_old, devctl; + int pci_irq_status; + + mfunc = mfunc_old = config_readl(socket, TI122X_MFUNC); + devctl = config_readb(socket, TI113X_DEVICE_CONTROL); + printk(KERN_INFO "Yenta TI: socket %s, mfunc 0x%08x, devctl 0x%02x\n", + pci_name(socket->dev), mfunc, devctl); + + /* make sure PCI interrupts are enabled before probing */ + ti_init(socket); + + /* test PCI interrupts first. only try fixing if return value is 0! */ + pci_irq_status = yenta_probe_cb_irq(socket); + if (pci_irq_status) + goto out; + + /* + * We're here which means PCI interrupts are _not_ delivered. try to + * find the right setting + */ + printk(KERN_INFO "Yenta TI: socket %s probing PCI interrupt failed, trying to fix\n", + pci_name(socket->dev)); + + + /* if all serial: set INTRTIE, probe again */ + if ((devctl & TI113X_DCR_IMODE_MASK) == TI12XX_DCR_IMODE_ALL_SERIAL) { + int old_irq; + + if (ti12xx_tie_interrupts(socket, &old_irq)) { + pci_irq_status = yenta_probe_cb_irq(socket); + if (pci_irq_status == 1) { + printk(KERN_INFO "Yenta TI: socket %s all-serial interrupts, tied ok\n", + pci_name(socket->dev)); + goto out; + } + + ti12xx_untie_interrupts(socket, old_irq); + } + } + /* parallel PCI: route INTB, probe again */ + else { + int old_irq; + + switch (socket->dev->device) { + case PCI_DEVICE_ID_TI_1250: + /* the 1250 has one pin for IRQSER/INTB depending on devctl */ + break; + + case PCI_DEVICE_ID_TI_1251A: + case PCI_DEVICE_ID_TI_1251B: + case PCI_DEVICE_ID_TI_1450: + /* + * those have a pin for IRQSER/INTB plus INTB in MFUNC0 + * we alread probed the shared pin, now go for MFUNC0 + */ + mfunc = (mfunc & ~TI122X_MFUNC0_MASK) | TI125X_MFUNC0_INTB; + break; + + default: + mfunc = (mfunc & ~TI122X_MFUNC1_MASK) | TI122X_MFUNC1_INTB; + break; + } + + /* write, probe */ + if (mfunc != mfunc_old) { + config_writel(socket, TI122X_MFUNC, mfunc); + + pci_irq_status = yenta_probe_cb_irq(socket); + if (pci_irq_status == 1) { + printk(KERN_INFO "Yenta TI: socket %s parallel PCI interrupts ok\n", + pci_name(socket->dev)); + goto out; + } + + mfunc = mfunc_old; + config_writel(socket, TI122X_MFUNC, mfunc); + + if (pci_irq_status == -1) + goto out; + } + + /* still nothing: set INTRTIE */ + if (ti12xx_tie_interrupts(socket, &old_irq)) { + pci_irq_status = yenta_probe_cb_irq(socket); + if (pci_irq_status == 1) { + printk(KERN_INFO "Yenta TI: socket %s parallel PCI interrupts, tied ok\n", + pci_name(socket->dev)); + goto out; + } + + ti12xx_untie_interrupts(socket, old_irq); + } + } + +out: + if (pci_irq_status < 1) { + socket->cb_irq = 0; + printk(KERN_INFO "Yenta TI: socket %s no PCI interrupts. Fish. Please report.\n", + pci_name(socket->dev)); + } +} + +static int ti12xx_override(struct yenta_socket *socket) +{ + u32 val, val_orig; + + /* make sure that memory burst is active */ + val_orig = val = config_readl(socket, TI113X_SYSTEM_CONTROL); + if (disable_clkrun && PCI_FUNC(socket->dev->devfn) == 0) { + printk(KERN_INFO "Yenta: Disabling CLKRUN feature\n"); + val |= TI113X_SCR_KEEPCLK; + } + if (!(val & TI122X_SCR_MRBURSTUP)) { + printk(KERN_INFO "Yenta: Enabling burst memory read transactions\n"); + val |= TI122X_SCR_MRBURSTUP; + } + if (val_orig != val) + config_writel(socket, TI113X_SYSTEM_CONTROL, val); + + /* + * for EnE bridges only: clear testbit TLTEnable. this makes the + * RME Hammerfall DSP sound card working. + */ + if (socket->dev->vendor == PCI_VENDOR_ID_ENE) { + u8 test_c9 = config_readb(socket, ENE_TEST_C9); + test_c9 &= ~ENE_TEST_C9_TLTENABLE; + config_writeb(socket, ENE_TEST_C9, test_c9); + } + + /* + * Yenta expects controllers to use CSCINT to route + * CSC interrupts to PCI rather than INTVAL. + */ + val = config_readb(socket, TI1250_DIAGNOSTIC); + printk(KERN_INFO "Yenta: Using %s to route CSC interrupts to PCI\n", + (val & TI1250_DIAG_PCI_CSC) ? "CSCINT" : "INTVAL"); + printk(KERN_INFO "Yenta: Routing CardBus interrupts to %s\n", + (val & TI1250_DIAG_PCI_IREQ) ? "PCI" : "ISA"); + + /* do irqrouting, depending on function */ + if (PCI_FUNC(socket->dev->devfn) == 0) + ti12xx_irqroute_func0(socket); + else + ti12xx_irqroute_func1(socket); + + return ti_override(socket); +} + + +static int ti1250_override(struct yenta_socket *socket) +{ + u8 old, diag; + + old = config_readb(socket, TI1250_DIAGNOSTIC); + diag = old & ~(TI1250_DIAG_PCI_CSC | TI1250_DIAG_PCI_IREQ); + if (socket->cb_irq) + diag |= TI1250_DIAG_PCI_CSC | TI1250_DIAG_PCI_IREQ; + + if (diag != old) { + printk(KERN_INFO "Yenta: adjusting diagnostic: %02x -> %02x\n", + old, diag); + config_writeb(socket, TI1250_DIAGNOSTIC, diag); + } + + return ti12xx_override(socket); +} + +#endif /* CONFIG_CARDBUS */ + +#endif /* _LINUX_TI113X_H */ + diff --git a/drivers/pcmcia/topic.h b/drivers/pcmcia/topic.h new file mode 100644 index 000000000000..be420bb29113 --- /dev/null +++ b/drivers/pcmcia/topic.h @@ -0,0 +1,140 @@ +/* + * topic.h 1.8 1999/08/28 04:01:47 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in which + * case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + * topic.h $Release$ 1999/08/28 04:01:47 + */ + +#ifndef _LINUX_TOPIC_H +#define _LINUX_TOPIC_H + +/* Register definitions for Toshiba ToPIC95/97/100 controllers */ + +#define TOPIC_SOCKET_CONTROL 0x0090 /* 32 bit */ +#define TOPIC_SCR_IRQSEL 0x00000001 + +#define TOPIC_SLOT_CONTROL 0x00a0 /* 8 bit */ +#define TOPIC_SLOT_SLOTON 0x80 +#define TOPIC_SLOT_SLOTEN 0x40 +#define TOPIC_SLOT_ID_LOCK 0x20 +#define TOPIC_SLOT_ID_WP 0x10 +#define TOPIC_SLOT_PORT_MASK 0x0c +#define TOPIC_SLOT_PORT_SHIFT 2 +#define TOPIC_SLOT_OFS_MASK 0x03 + +#define TOPIC_CARD_CONTROL 0x00a1 /* 8 bit */ +#define TOPIC_CCR_INTB 0x20 +#define TOPIC_CCR_INTA 0x10 +#define TOPIC_CCR_CLOCK 0x0c +#define TOPIC_CCR_PCICLK 0x0c +#define TOPIC_CCR_PCICLK_2 0x08 +#define TOPIC_CCR_CCLK 0x04 + +#define TOPIC97_INT_CONTROL 0x00a1 /* 8 bit */ +#define TOPIC97_ICR_INTB 0x20 +#define TOPIC97_ICR_INTA 0x10 +#define TOPIC97_ICR_STSIRQNP 0x04 +#define TOPIC97_ICR_IRQNP 0x02 +#define TOPIC97_ICR_IRQSEL 0x01 + +#define TOPIC_CARD_DETECT 0x00a3 /* 8 bit */ +#define TOPIC_CDR_MODE_PC32 0x80 +#define TOPIC_CDR_VS1 0x04 +#define TOPIC_CDR_VS2 0x02 +#define TOPIC_CDR_SW_DETECT 0x01 + +#define TOPIC_REGISTER_CONTROL 0x00a4 /* 32 bit */ +#define TOPIC_RCR_RESUME_RESET 0x80000000 +#define TOPIC_RCR_REMOVE_RESET 0x40000000 +#define TOPIC97_RCR_CLKRUN_ENA 0x20000000 +#define TOPIC97_RCR_TESTMODE 0x10000000 +#define TOPIC97_RCR_IOPLUP 0x08000000 +#define TOPIC_RCR_BUFOFF_PWROFF 0x02000000 +#define TOPIC_RCR_BUFOFF_SIGOFF 0x01000000 +#define TOPIC97_RCR_CB_DEV_MASK 0x0000f800 +#define TOPIC97_RCR_CB_DEV_SHIFT 11 +#define TOPIC97_RCR_RI_DISABLE 0x00000004 +#define TOPIC97_RCR_CAUDIO_OFF 0x00000002 +#define TOPIC_RCR_CAUDIO_INVERT 0x00000001 + +#define TOPIC97_MISC1 0x00ad /* 8bit */ +#define TOPIC97_MISC1_CLOCKRUN_ENABLE 0x80 +#define TOPIC97_MISC1_CLOCKRUN_MODE 0x40 +#define TOPIC97_MISC1_DETECT_REQ_ENA 0x10 +#define TOPIC97_MISC1_SCK_CLEAR_DIS 0x04 +#define TOPIC97_MISC1_R2_LOW_ENABLE 0x10 + +#define TOPIC97_MISC2 0x00ae /* 8 bit */ +#define TOPIC97_MISC2_SPWRCLK_MASK 0x70 +#define TOPIC97_MISC2_SPWRMOD 0x08 +#define TOPIC97_MISC2_SPWR_ENABLE 0x04 +#define TOPIC97_MISC2_ZV_MODE 0x02 +#define TOPIC97_MISC2_ZV_ENABLE 0x01 + +#define TOPIC97_ZOOM_VIDEO_CONTROL 0x009c /* 8 bit */ +#define TOPIC97_ZV_CONTROL_ENABLE 0x01 + +#define TOPIC97_AUDIO_VIDEO_SWITCH 0x003c /* 8 bit */ +#define TOPIC97_AVS_AUDIO_CONTROL 0x02 +#define TOPIC97_AVS_VIDEO_CONTROL 0x01 + + +static void topic97_zoom_video(struct pcmcia_socket *sock, int onoff) +{ + struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket); + u8 reg_zv, reg; + + reg_zv = config_readb(socket, TOPIC97_ZOOM_VIDEO_CONTROL); + if (onoff) { + reg_zv |= TOPIC97_ZV_CONTROL_ENABLE; + config_writeb(socket, TOPIC97_ZOOM_VIDEO_CONTROL, reg_zv); + + reg = config_readb(socket, TOPIC97_MISC2); + reg |= TOPIC97_MISC2_ZV_ENABLE; + config_writeb(socket, TOPIC97_MISC2, reg); + + /* not sure this is needed, doc is unclear */ +#if 0 + reg = config_readb(socket, TOPIC97_AUDIO_VIDEO_SWITCH); + reg |= TOPIC97_AVS_AUDIO_CONTROL | TOPIC97_AVS_VIDEO_CONTROL; + config_writeb(socket, TOPIC97_AUDIO_VIDEO_SWITCH, reg); +#endif + } + else { + reg_zv &= ~TOPIC97_ZV_CONTROL_ENABLE; + config_writeb(socket, TOPIC97_ZOOM_VIDEO_CONTROL, reg_zv); + } + +} + +static int topic97_override(struct yenta_socket *socket) +{ + /* ToPIC97/100 support ZV */ + socket->socket.zoom_video = topic97_zoom_video; + return 0; +} + +#endif /* _LINUX_TOPIC_H */ diff --git a/drivers/pcmcia/vg468.h b/drivers/pcmcia/vg468.h new file mode 100644 index 000000000000..88c2b487f675 --- /dev/null +++ b/drivers/pcmcia/vg468.h @@ -0,0 +1,106 @@ +/* + * vg468.h 1.11 1999/10/25 20:03:34 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License + * at http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and + * limitations under the License. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License version 2 (the "GPL"), in which + * case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_VG468_H +#define _LINUX_VG468_H + +/* Special bit in I365_IDENT used for Vadem chip detection */ +#define I365_IDENT_VADEM 0x08 + +/* Special definitions in I365_POWER */ +#define VG468_VPP2_MASK 0x0c +#define VG468_VPP2_5V 0x04 +#define VG468_VPP2_12V 0x08 + +/* Unique Vadem registers */ +#define VG469_VSENSE 0x1f /* Card voltage sense */ +#define VG469_VSELECT 0x2f /* Card voltage select */ +#define VG468_CTL 0x38 /* Control register */ +#define VG468_TIMER 0x39 /* Timer control */ +#define VG468_MISC 0x3a /* Miscellaneous */ +#define VG468_GPIO_CFG 0x3b /* GPIO configuration */ +#define VG469_EXT_MODE 0x3c /* Extended mode register */ +#define VG468_SELECT 0x3d /* Programmable chip select */ +#define VG468_SELECT_CFG 0x3e /* Chip select configuration */ +#define VG468_ATA 0x3f /* ATA control */ + +/* Flags for VG469_VSENSE */ +#define VG469_VSENSE_A_VS1 0x01 +#define VG469_VSENSE_A_VS2 0x02 +#define VG469_VSENSE_B_VS1 0x04 +#define VG469_VSENSE_B_VS2 0x08 + +/* Flags for VG469_VSELECT */ +#define VG469_VSEL_VCC 0x03 +#define VG469_VSEL_5V 0x00 +#define VG469_VSEL_3V 0x03 +#define VG469_VSEL_MAX 0x0c +#define VG469_VSEL_EXT_STAT 0x10 +#define VG469_VSEL_EXT_BUS 0x20 +#define VG469_VSEL_MIXED 0x40 +#define VG469_VSEL_ISA 0x80 + +/* Flags for VG468_CTL */ +#define VG468_CTL_SLOW 0x01 /* 600ns memory timing */ +#define VG468_CTL_ASYNC 0x02 /* Asynchronous bus clocking */ +#define VG468_CTL_TSSI 0x08 /* Tri-state some outputs */ +#define VG468_CTL_DELAY 0x10 /* Card detect debounce */ +#define VG468_CTL_INPACK 0x20 /* Obey INPACK signal? */ +#define VG468_CTL_POLARITY 0x40 /* VCCEN polarity */ +#define VG468_CTL_COMPAT 0x80 /* Compatibility stuff */ + +#define VG469_CTL_WS_COMPAT 0x04 /* Wait state compatibility */ +#define VG469_CTL_STRETCH 0x10 /* LED stretch */ + +/* Flags for VG468_TIMER */ +#define VG468_TIMER_ZEROPWR 0x10 /* Zero power control */ +#define VG468_TIMER_SIGEN 0x20 /* Power up */ +#define VG468_TIMER_STATUS 0x40 /* Activity timer status */ +#define VG468_TIMER_RES 0x80 /* Timer resolution */ +#define VG468_TIMER_MASK 0x0f /* Activity timer timeout */ + +/* Flags for VG468_MISC */ +#define VG468_MISC_GPIO 0x04 /* General-purpose IO */ +#define VG468_MISC_DMAWSB 0x08 /* DMA wait state control */ +#define VG469_MISC_LEDENA 0x10 /* LED enable */ +#define VG468_MISC_VADEMREV 0x40 /* Vadem revision control */ +#define VG468_MISC_UNLOCK 0x80 /* Unique register lock */ + +/* Flags for VG469_EXT_MODE_A */ +#define VG469_MODE_VPPST 0x03 /* Vpp steering control */ +#define VG469_MODE_INT_SENSE 0x04 /* Internal voltage sense */ +#define VG469_MODE_CABLE 0x08 +#define VG469_MODE_COMPAT 0x10 /* i82365sl B or DF step */ +#define VG469_MODE_TEST 0x20 +#define VG469_MODE_RIO 0x40 /* Steer RIO to INTR? */ + +/* Flags for VG469_EXT_MODE_B */ +#define VG469_MODE_B_3V 0x01 /* 3.3v for socket B */ + +#endif /* _LINUX_VG468_H */ diff --git a/drivers/pcmcia/vrc4171_card.c b/drivers/pcmcia/vrc4171_card.c new file mode 100644 index 000000000000..987bc21ced42 --- /dev/null +++ b/drivers/pcmcia/vrc4171_card.c @@ -0,0 +1,846 @@ +/* + * vrc4171_card.c, NEC VRC4171 Card Controller driver for Socket Services. + * + * Copyright (C) 2003-2005 Yoichi Yuasa <yuasa@hh.iij4u.or.jp> + * + * 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 + */ +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/sched.h> +#include <linux/types.h> + +#include <asm/io.h> + +#include <pcmcia/ss.h> + +#include "i82365.h" + +MODULE_DESCRIPTION("NEC VRC4171 Card Controllers driver for Socket Services"); +MODULE_AUTHOR("Yoichi Yuasa <yuasa@hh.iij4u.or.jp>"); +MODULE_LICENSE("GPL"); + +#define CARD_MAX_SLOTS 2 +#define CARD_SLOTA 0 +#define CARD_SLOTB 1 +#define CARD_SLOTB_OFFSET 0x40 + +#define CARD_MEM_START 0x10000000 +#define CARD_MEM_END 0x13ffffff +#define CARD_MAX_MEM_OFFSET 0x3ffffff +#define CARD_MAX_MEM_SPEED 1000 + +#define CARD_CONTROLLER_INDEX 0x03e0 +#define CARD_CONTROLLER_DATA 0x03e1 + /* Power register */ + #define VPP_GET_VCC 0x01 + #define POWER_ENABLE 0x10 + #define CARD_VOLTAGE_SENSE 0x1f + #define VCC_3VORXV_CAPABLE 0x00 + #define VCC_XV_ONLY 0x01 + #define VCC_3V_CAPABLE 0x02 + #define VCC_5V_ONLY 0x03 + #define CARD_VOLTAGE_SELECT 0x2f + #define VCC_3V 0x01 + #define VCC_5V 0x00 + #define VCC_XV 0x02 + #define VCC_STATUS_3V 0x02 + #define VCC_STATUS_5V 0x01 + #define VCC_STATUS_XV 0x03 + #define GLOBAL_CONTROL 0x1e + #define EXWRBK 0x04 + #define IRQPM_EN 0x08 + #define CLRPMIRQ 0x10 + +#define INTERRUPT_STATUS 0x05fa + #define IRQ_A 0x02 + #define IRQ_B 0x04 + +#define CONFIGURATION1 0x05fe + #define SLOTB_CONFIG 0xc000 + #define SLOTB_NONE 0x0000 + #define SLOTB_PCCARD 0x4000 + #define SLOTB_CF 0x8000 + #define SLOTB_FLASHROM 0xc000 + +#define CARD_CONTROLLER_START CARD_CONTROLLER_INDEX +#define CARD_CONTROLLER_END CARD_CONTROLLER_DATA + +#define IO_MAX_MAPS 2 +#define MEM_MAX_MAPS 5 + +typedef enum { + SLOT_PROBE = 0, + SLOT_NOPROBE_IO, + SLOT_NOPROBE_MEM, + SLOT_NOPROBE_ALL, + SLOT_INITIALIZED, +} vrc4171_slot_t; + +typedef enum { + SLOTB_IS_NONE, + SLOTB_IS_PCCARD, + SLOTB_IS_CF, + SLOTB_IS_FLASHROM, +} vrc4171_slotb_t; + +typedef struct vrc4171_socket { + vrc4171_slot_t slot; + struct pcmcia_socket pcmcia_socket; + char name[24]; + int csc_irq; + int io_irq; +} vrc4171_socket_t; + +static vrc4171_socket_t vrc4171_sockets[CARD_MAX_SLOTS]; +static vrc4171_slotb_t vrc4171_slotb = SLOTB_IS_NONE; +static char vrc4171_card_name[] = "NEC VRC4171 Card Controller"; +static unsigned int vrc4171_irq; +static uint16_t vrc4171_irq_mask = 0xdeb8; + +static struct resource vrc4171_card_resource[3] = { + { .name = vrc4171_card_name, + .start = CARD_CONTROLLER_START, + .end = CARD_CONTROLLER_END, + .flags = IORESOURCE_IO, }, + { .name = vrc4171_card_name, + .start = INTERRUPT_STATUS, + .end = INTERRUPT_STATUS, + .flags = IORESOURCE_IO, }, + { .name = vrc4171_card_name, + .start = CONFIGURATION1, + .end = CONFIGURATION1, + .flags = IORESOURCE_IO, }, +}; + +static struct platform_device vrc4171_card_device = { + .name = vrc4171_card_name, + .id = 0, + .num_resources = 3, + .resource = vrc4171_card_resource, +}; + +static inline uint16_t vrc4171_get_irq_status(void) +{ + return inw(INTERRUPT_STATUS); +} + +static inline void vrc4171_set_multifunction_pin(vrc4171_slotb_t config) +{ + uint16_t config1; + + config1 = inw(CONFIGURATION1); + config1 &= ~SLOTB_CONFIG; + + switch (config) { + case SLOTB_IS_NONE: + config1 |= SLOTB_NONE; + break; + case SLOTB_IS_PCCARD: + config1 |= SLOTB_PCCARD; + break; + case SLOTB_IS_CF: + config1 |= SLOTB_CF; + break; + case SLOTB_IS_FLASHROM: + config1 |= SLOTB_FLASHROM; + break; + default: + break; + } + + outw(config1, CONFIGURATION1); +} + +static inline uint8_t exca_read_byte(int slot, uint8_t index) +{ + if (slot == CARD_SLOTB) + index += CARD_SLOTB_OFFSET; + + outb(index, CARD_CONTROLLER_INDEX); + return inb(CARD_CONTROLLER_DATA); +} + +static inline uint16_t exca_read_word(int slot, uint8_t index) +{ + uint16_t data; + + if (slot == CARD_SLOTB) + index += CARD_SLOTB_OFFSET; + + outb(index++, CARD_CONTROLLER_INDEX); + data = inb(CARD_CONTROLLER_DATA); + + outb(index, CARD_CONTROLLER_INDEX); + data |= ((uint16_t)inb(CARD_CONTROLLER_DATA)) << 8; + + return data; +} + +static inline uint8_t exca_write_byte(int slot, uint8_t index, uint8_t data) +{ + if (slot == CARD_SLOTB) + index += CARD_SLOTB_OFFSET; + + outb(index, CARD_CONTROLLER_INDEX); + outb(data, CARD_CONTROLLER_DATA); + + return data; +} + +static inline uint16_t exca_write_word(int slot, uint8_t index, uint16_t data) +{ + if (slot == CARD_SLOTB) + index += CARD_SLOTB_OFFSET; + + outb(index++, CARD_CONTROLLER_INDEX); + outb(data, CARD_CONTROLLER_DATA); + + outb(index, CARD_CONTROLLER_INDEX); + outb((uint8_t)(data >> 8), CARD_CONTROLLER_DATA); + + return data; +} + +static inline int search_nonuse_irq(void) +{ + int i; + + for (i = 0; i < 16; i++) { + if (vrc4171_irq_mask & (1 << i)) { + vrc4171_irq_mask &= ~(1 << i); + return i; + } + } + + return -1; +} + +static int pccard_init(struct pcmcia_socket *sock) +{ + vrc4171_socket_t *socket; + unsigned int slot; + + sock->features |= SS_CAP_PCCARD | SS_CAP_PAGE_REGS; + sock->irq_mask = 0; + sock->map_size = 0x1000; + sock->pci_irq = vrc4171_irq; + + slot = sock->sock; + socket = &vrc4171_sockets[slot]; + socket->csc_irq = search_nonuse_irq(); + socket->io_irq = search_nonuse_irq(); + + return 0; +} + +static int pccard_get_status(struct pcmcia_socket *sock, u_int *value) +{ + unsigned int slot; + uint8_t status, sense; + u_int val = 0; + + if (sock == NULL || sock->sock >= CARD_MAX_SLOTS || value == NULL) + return -EINVAL; + + slot = sock->sock; + + status = exca_read_byte(slot, I365_STATUS); + if (exca_read_byte(slot, I365_INTCTL) & I365_PC_IOCARD) { + if (status & I365_CS_STSCHG) + val |= SS_STSCHG; + } else { + if (!(status & I365_CS_BVD1)) + val |= SS_BATDEAD; + else if ((status & (I365_CS_BVD1 | I365_CS_BVD2)) == I365_CS_BVD1) + val |= SS_BATWARN; + } + if ((status & I365_CS_DETECT) == I365_CS_DETECT) + val |= SS_DETECT; + if (status & I365_CS_WRPROT) + val |= SS_WRPROT; + if (status & I365_CS_READY) + val |= SS_READY; + if (status & I365_CS_POWERON) + val |= SS_POWERON; + + sense = exca_read_byte(slot, CARD_VOLTAGE_SENSE); + switch (sense) { + case VCC_3VORXV_CAPABLE: + val |= SS_3VCARD | SS_XVCARD; + break; + case VCC_XV_ONLY: + val |= SS_XVCARD; + break; + case VCC_3V_CAPABLE: + val |= SS_3VCARD; + break; + default: + /* 5V only */ + break; + } + + *value = val; + + return 0; +} + +static inline u_char get_Vcc_value(uint8_t voltage) +{ + switch (voltage) { + case VCC_STATUS_3V: + return 33; + case VCC_STATUS_5V: + return 50; + default: + break; + } + + return 0; +} + +static inline u_char get_Vpp_value(uint8_t power, u_char Vcc) +{ + if ((power & 0x03) == 0x01 || (power & 0x03) == 0x02) + return Vcc; + + return 0; +} + +static int pccard_get_socket(struct pcmcia_socket *sock, socket_state_t *state) +{ + unsigned int slot; + uint8_t power, voltage, control, cscint; + + if (sock == NULL || sock->sock >= CARD_MAX_SLOTS || state == NULL) + return -EINVAL; + + slot = sock->sock; + + power = exca_read_byte(slot, I365_POWER); + voltage = exca_read_byte(slot, CARD_VOLTAGE_SELECT); + + state->Vcc = get_Vcc_value(voltage); + state->Vpp = get_Vpp_value(power, state->Vcc); + + state->flags = 0; + if (power & POWER_ENABLE) + state->flags |= SS_PWR_AUTO; + if (power & I365_PWR_OUT) + state->flags |= SS_OUTPUT_ENA; + + control = exca_read_byte(slot, I365_INTCTL); + if (control & I365_PC_IOCARD) + state->flags |= SS_IOCARD; + if (!(control & I365_PC_RESET)) + state->flags |= SS_RESET; + + cscint = exca_read_byte(slot, I365_CSCINT); + state->csc_mask = 0; + if (state->flags & SS_IOCARD) { + if (cscint & I365_CSC_STSCHG) + state->flags |= SS_STSCHG; + } else { + if (cscint & I365_CSC_BVD1) + state->csc_mask |= SS_BATDEAD; + if (cscint & I365_CSC_BVD2) + state->csc_mask |= SS_BATWARN; + } + if (cscint & I365_CSC_READY) + state->csc_mask |= SS_READY; + if (cscint & I365_CSC_DETECT) + state->csc_mask |= SS_DETECT; + + return 0; +} + +static inline uint8_t set_Vcc_value(u_char Vcc) +{ + switch (Vcc) { + case 33: + return VCC_3V; + case 50: + return VCC_5V; + } + + /* Small voltage is chosen for safety. */ + return VCC_3V; +} + +static int pccard_set_socket(struct pcmcia_socket *sock, socket_state_t *state) +{ + vrc4171_socket_t *socket; + unsigned int slot; + uint8_t voltage, power, control, cscint; + + if (sock == NULL || sock->sock >= CARD_MAX_SLOTS || + (state->Vpp != state->Vcc && state->Vpp != 0) || + (state->Vcc != 50 && state->Vcc != 33 && state->Vcc != 0)) + return -EINVAL; + + slot = sock->sock; + socket = &vrc4171_sockets[slot]; + + spin_lock_irq(&sock->lock); + + voltage = set_Vcc_value(state->Vcc); + exca_write_byte(slot, CARD_VOLTAGE_SELECT, voltage); + + power = POWER_ENABLE; + if (state->Vpp == state->Vcc) + power |= VPP_GET_VCC; + if (state->flags & SS_OUTPUT_ENA) + power |= I365_PWR_OUT; + exca_write_byte(slot, I365_POWER, power); + + control = 0; + if (state->io_irq != 0) + control |= socket->io_irq; + if (state->flags & SS_IOCARD) + control |= I365_PC_IOCARD; + if (state->flags & SS_RESET) + control &= ~I365_PC_RESET; + else + control |= I365_PC_RESET; + exca_write_byte(slot, I365_INTCTL, control); + + cscint = 0; + exca_write_byte(slot, I365_CSCINT, cscint); + exca_read_byte(slot, I365_CSC); /* clear CardStatus change */ + if (state->csc_mask != 0) + cscint |= socket->csc_irq << 8; + if (state->flags & SS_IOCARD) { + if (state->csc_mask & SS_STSCHG) + cscint |= I365_CSC_STSCHG; + } else { + if (state->csc_mask & SS_BATDEAD) + cscint |= I365_CSC_BVD1; + if (state->csc_mask & SS_BATWARN) + cscint |= I365_CSC_BVD2; + } + if (state->csc_mask & SS_READY) + cscint |= I365_CSC_READY; + if (state->csc_mask & SS_DETECT) + cscint |= I365_CSC_DETECT; + exca_write_byte(slot, I365_CSCINT, cscint); + + spin_unlock_irq(&sock->lock); + + return 0; +} + +static int pccard_set_io_map(struct pcmcia_socket *sock, struct pccard_io_map *io) +{ + unsigned int slot; + uint8_t ioctl, addrwin; + u_char map; + + if (sock == NULL || sock->sock >= CARD_MAX_SLOTS || + io == NULL || io->map >= IO_MAX_MAPS || + io->start > 0xffff || io->stop > 0xffff || io->start > io->stop) + return -EINVAL; + + slot = sock->sock; + map = io->map; + + addrwin = exca_read_byte(slot, I365_ADDRWIN); + if (addrwin & I365_ENA_IO(map)) { + addrwin &= ~I365_ENA_IO(map); + exca_write_byte(slot, I365_ADDRWIN, addrwin); + } + + exca_write_word(slot, I365_IO(map)+I365_W_START, io->start); + exca_write_word(slot, I365_IO(map)+I365_W_STOP, io->stop); + + ioctl = 0; + if (io->speed > 0) + ioctl |= I365_IOCTL_WAIT(map); + if (io->flags & MAP_16BIT) + ioctl |= I365_IOCTL_16BIT(map); + if (io->flags & MAP_AUTOSZ) + ioctl |= I365_IOCTL_IOCS16(map); + if (io->flags & MAP_0WS) + ioctl |= I365_IOCTL_0WS(map); + exca_write_byte(slot, I365_IOCTL, ioctl); + + if (io->flags & MAP_ACTIVE) { + addrwin |= I365_ENA_IO(map); + exca_write_byte(slot, I365_ADDRWIN, addrwin); + } + + return 0; +} + +static int pccard_set_mem_map(struct pcmcia_socket *sock, struct pccard_mem_map *mem) +{ + unsigned int slot; + uint16_t start, stop, offset; + uint8_t addrwin; + u_char map; + + if (sock == NULL || sock->sock >= CARD_MAX_SLOTS || + mem == NULL || mem->map >= MEM_MAX_MAPS || + mem->res->start < CARD_MEM_START || mem->res->start > CARD_MEM_END || + mem->res->end < CARD_MEM_START || mem->res->end > CARD_MEM_END || + mem->res->start > mem->res->end || + mem->card_start > CARD_MAX_MEM_OFFSET || + mem->speed > CARD_MAX_MEM_SPEED) + return -EINVAL; + + slot = sock->sock; + map = mem->map; + + addrwin = exca_read_byte(slot, I365_ADDRWIN); + if (addrwin & I365_ENA_MEM(map)) { + addrwin &= ~I365_ENA_MEM(map); + exca_write_byte(slot, I365_ADDRWIN, addrwin); + } + + start = (mem->res->start >> 12) & 0x3fff; + if (mem->flags & MAP_16BIT) + start |= I365_MEM_16BIT; + exca_write_word(slot, I365_MEM(map)+I365_W_START, start); + + stop = (mem->res->end >> 12) & 0x3fff; + switch (mem->speed) { + case 0: + break; + case 1: + stop |= I365_MEM_WS0; + break; + case 2: + stop |= I365_MEM_WS1; + break; + default: + stop |= I365_MEM_WS0 | I365_MEM_WS1; + break; + } + exca_write_word(slot, I365_MEM(map)+I365_W_STOP, stop); + + offset = (mem->card_start >> 12) & 0x3fff; + if (mem->flags & MAP_ATTRIB) + offset |= I365_MEM_REG; + if (mem->flags & MAP_WRPROT) + offset |= I365_MEM_WRPROT; + exca_write_word(slot, I365_MEM(map)+I365_W_OFF, offset); + + if (mem->flags & MAP_ACTIVE) { + addrwin |= I365_ENA_MEM(map); + exca_write_byte(slot, I365_ADDRWIN, addrwin); + } + + return 0; +} + +static struct pccard_operations vrc4171_pccard_operations = { + .init = pccard_init, + .get_status = pccard_get_status, + .get_socket = pccard_get_socket, + .set_socket = pccard_set_socket, + .set_io_map = pccard_set_io_map, + .set_mem_map = pccard_set_mem_map, +}; + +static inline unsigned int get_events(int slot) +{ + unsigned int events = 0; + uint8_t status, csc; + + status = exca_read_byte(slot, I365_STATUS); + csc = exca_read_byte(slot, I365_CSC); + + if (exca_read_byte(slot, I365_INTCTL) & I365_PC_IOCARD) { + if ((csc & I365_CSC_STSCHG) && (status & I365_CS_STSCHG)) + events |= SS_STSCHG; + } else { + if (csc & (I365_CSC_BVD1 | I365_CSC_BVD2)) { + if (!(status & I365_CS_BVD1)) + events |= SS_BATDEAD; + else if ((status & (I365_CS_BVD1 | I365_CS_BVD2)) == I365_CS_BVD1) + events |= SS_BATWARN; + } + } + if ((csc & I365_CSC_READY) && (status & I365_CS_READY)) + events |= SS_READY; + if ((csc & I365_CSC_DETECT) && ((status & I365_CS_DETECT) == I365_CS_DETECT)) + events |= SS_DETECT; + + return events; +} + +static irqreturn_t pccard_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + vrc4171_socket_t *socket; + unsigned int events; + irqreturn_t retval = IRQ_NONE; + uint16_t status; + + status = vrc4171_get_irq_status(); + if (status & IRQ_A) { + socket = &vrc4171_sockets[CARD_SLOTA]; + if (socket->slot == SLOT_INITIALIZED) { + if (status & (1 << socket->csc_irq)) { + events = get_events(CARD_SLOTA); + if (events != 0) { + pcmcia_parse_events(&socket->pcmcia_socket, events); + retval = IRQ_HANDLED; + } + } + } + } + + if (status & IRQ_B) { + socket = &vrc4171_sockets[CARD_SLOTB]; + if (socket->slot == SLOT_INITIALIZED) { + if (status & (1 << socket->csc_irq)) { + events = get_events(CARD_SLOTB); + if (events != 0) { + pcmcia_parse_events(&socket->pcmcia_socket, events); + retval = IRQ_HANDLED; + } + } + } + } + + return retval; +} + +static inline void reserve_using_irq(int slot) +{ + unsigned int irq; + + irq = exca_read_byte(slot, I365_INTCTL); + irq &= 0x0f; + vrc4171_irq_mask &= ~(1 << irq); + + irq = exca_read_byte(slot, I365_CSCINT); + irq = (irq & 0xf0) >> 4; + vrc4171_irq_mask &= ~(1 << irq); +} + +static int __devinit vrc4171_add_sockets(void) +{ + vrc4171_socket_t *socket; + int slot, retval; + + for (slot = 0; slot < CARD_MAX_SLOTS; slot++) { + if (slot == CARD_SLOTB && vrc4171_slotb == SLOTB_IS_NONE) + continue; + + socket = &vrc4171_sockets[slot]; + if (socket->slot != SLOT_PROBE) { + uint8_t addrwin; + + switch (socket->slot) { + case SLOT_NOPROBE_MEM: + addrwin = exca_read_byte(slot, I365_ADDRWIN); + addrwin &= 0x1f; + exca_write_byte(slot, I365_ADDRWIN, addrwin); + break; + case SLOT_NOPROBE_IO: + addrwin = exca_read_byte(slot, I365_ADDRWIN); + addrwin &= 0xc0; + exca_write_byte(slot, I365_ADDRWIN, addrwin); + break; + default: + break; + } + + reserve_using_irq(slot); + continue; + } + + sprintf(socket->name, "NEC VRC4171 Card Slot %1c", 'A' + slot); + socket->pcmcia_socket.dev.dev = &vrc4171_card_device.dev; + socket->pcmcia_socket.ops = &vrc4171_pccard_operations; + socket->pcmcia_socket.owner = THIS_MODULE; + + retval = pcmcia_register_socket(&socket->pcmcia_socket); + if (retval < 0) + return retval; + + exca_write_byte(slot, I365_ADDRWIN, 0); + exca_write_byte(slot, GLOBAL_CONTROL, 0); + + socket->slot = SLOT_INITIALIZED; + } + + return 0; +} + +static void vrc4171_remove_sockets(void) +{ + vrc4171_socket_t *socket; + int slot; + + for (slot = 0; slot < CARD_MAX_SLOTS; slot++) { + if (slot == CARD_SLOTB && vrc4171_slotb == SLOTB_IS_NONE) + continue; + + socket = &vrc4171_sockets[slot]; + if (socket->slot == SLOT_INITIALIZED) + pcmcia_unregister_socket(&socket->pcmcia_socket); + + socket->slot = SLOT_PROBE; + } +} + +static int __devinit vrc4171_card_setup(char *options) +{ + if (options == NULL || *options == '\0') + return 0; + + if (strncmp(options, "irq:", 4) == 0) { + int irq; + options += 4; + irq = simple_strtoul(options, &options, 0); + if (irq >= 0 && irq < NR_IRQS) + vrc4171_irq = irq; + + if (*options != ',') + return 0; + options++; + } + + if (strncmp(options, "slota:", 6) == 0) { + options += 6; + if (*options != '\0') { + if (strncmp(options, "memnoprobe", 10) == 0) { + vrc4171_sockets[CARD_SLOTA].slot = SLOT_NOPROBE_MEM; + options += 10; + } else if (strncmp(options, "ionoprobe", 9) == 0) { + vrc4171_sockets[CARD_SLOTA].slot = SLOT_NOPROBE_IO; + options += 9; + } else if ( strncmp(options, "noprobe", 7) == 0) { + vrc4171_sockets[CARD_SLOTA].slot = SLOT_NOPROBE_ALL; + options += 7; + } + + if (*options != ',') + return 0; + options++; + } else + return 0; + + } + + if (strncmp(options, "slotb:", 6) == 0) { + options += 6; + if (*options != '\0') { + if (strncmp(options, "pccard", 6) == 0) { + vrc4171_slotb = SLOTB_IS_PCCARD; + options += 6; + } else if (strncmp(options, "cf", 2) == 0) { + vrc4171_slotb = SLOTB_IS_CF; + options += 2; + } else if (strncmp(options, "flashrom", 8) == 0) { + vrc4171_slotb = SLOTB_IS_FLASHROM; + options += 8; + } else if (strncmp(options, "none", 4) == 0) { + vrc4171_slotb = SLOTB_IS_NONE; + options += 4; + } + + if (*options != ',') + return 0; + options++; + + if (strncmp(options, "memnoprobe", 10) == 0) + vrc4171_sockets[CARD_SLOTB].slot = SLOT_NOPROBE_MEM; + if (strncmp(options, "ionoprobe", 9) == 0) + vrc4171_sockets[CARD_SLOTB].slot = SLOT_NOPROBE_IO; + if (strncmp(options, "noprobe", 7) == 0) + vrc4171_sockets[CARD_SLOTB].slot = SLOT_NOPROBE_ALL; + } + } + + return 0; +} + +__setup("vrc4171_card=", vrc4171_card_setup); + +static int vrc4171_card_suspend(struct device *dev, u32 state, u32 level) +{ + int retval = 0; + + if (level == SUSPEND_SAVE_STATE) + retval = pcmcia_socket_dev_suspend(dev, state); + + return retval; +} + +static int vrc4171_card_resume(struct device *dev, u32 level) +{ + int retval = 0; + + if (level == RESUME_RESTORE_STATE) + retval = pcmcia_socket_dev_resume(dev); + + return retval; +} + +static struct device_driver vrc4171_card_driver = { + .name = vrc4171_card_name, + .bus = &platform_bus_type, + .suspend = vrc4171_card_suspend, + .resume = vrc4171_card_resume, +}; + +static int __devinit vrc4171_card_init(void) +{ + int retval; + + retval = driver_register(&vrc4171_card_driver); + if (retval < 0) + return retval; + + retval = platform_device_register(&vrc4171_card_device); + if (retval < 0) { + driver_unregister(&vrc4171_card_driver); + return retval; + } + + vrc4171_set_multifunction_pin(vrc4171_slotb); + + retval = vrc4171_add_sockets(); + if (retval == 0) + retval = request_irq(vrc4171_irq, pccard_interrupt, SA_SHIRQ, + vrc4171_card_name, vrc4171_sockets); + + if (retval < 0) { + vrc4171_remove_sockets(); + platform_device_unregister(&vrc4171_card_device); + driver_unregister(&vrc4171_card_driver); + return retval; + } + + printk(KERN_INFO "%s, connected to IRQ %d\n", vrc4171_card_driver.name, vrc4171_irq); + + return 0; +} + +static void __devexit vrc4171_card_exit(void) +{ + free_irq(vrc4171_irq, vrc4171_sockets); + vrc4171_remove_sockets(); + platform_device_unregister(&vrc4171_card_device); + driver_unregister(&vrc4171_card_driver); +} + +module_init(vrc4171_card_init); +module_exit(vrc4171_card_exit); diff --git a/drivers/pcmcia/vrc4173_cardu.c b/drivers/pcmcia/vrc4173_cardu.c new file mode 100644 index 000000000000..db91259dc50e --- /dev/null +++ b/drivers/pcmcia/vrc4173_cardu.c @@ -0,0 +1,617 @@ +/* + * FILE NAME + * drivers/pcmcia/vrc4173_cardu.c + * + * BRIEF MODULE DESCRIPTION + * NEC VRC4173 CARDU driver for Socket Services + * (This device doesn't support CardBus. it is supporting only 16bit PC Card.) + * + * Copyright 2002,2003 Yoichi Yuasa <yuasa@hh.iij4u.or.jp> + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#include <asm/io.h> + +#include <pcmcia/ss.h> + +#include "vrc4173_cardu.h" + +MODULE_DESCRIPTION("NEC VRC4173 CARDU driver for Socket Services"); +MODULE_AUTHOR("Yoichi Yuasa <yuasa@hh.iij4u.or.jp>"); +MODULE_LICENSE("GPL"); + +static int vrc4173_cardu_slots; + +static vrc4173_socket_t cardu_sockets[CARDU_MAX_SOCKETS]; + +extern struct socket_info_t *pcmcia_register_socket (int slot, + struct pccard_operations *vtable, + int use_bus_pm); +extern void pcmcia_unregister_socket(struct socket_info_t *s); + +static inline uint8_t exca_readb(vrc4173_socket_t *socket, uint16_t offset) +{ + return readb(socket->base + EXCA_REGS_BASE + offset); +} + +static inline uint16_t exca_readw(vrc4173_socket_t *socket, uint16_t offset) +{ + uint16_t val; + + val = readb(socket->base + EXCA_REGS_BASE + offset); + val |= (u16)readb(socket->base + EXCA_REGS_BASE + offset + 1) << 8; + + return val; +} + +static inline void exca_writeb(vrc4173_socket_t *socket, uint16_t offset, uint8_t val) +{ + writeb(val, socket->base + EXCA_REGS_BASE + offset); +} + +static inline void exca_writew(vrc4173_socket_t *socket, uint8_t offset, uint16_t val) +{ + writeb((u8)val, socket->base + EXCA_REGS_BASE + offset); + writeb((u8)(val >> 8), socket->base + EXCA_REGS_BASE + offset + 1); +} + +static inline uint32_t cardbus_socket_readl(vrc4173_socket_t *socket, u16 offset) +{ + return readl(socket->base + CARDBUS_SOCKET_REGS_BASE + offset); +} + +static inline void cardbus_socket_writel(vrc4173_socket_t *socket, u16 offset, uint32_t val) +{ + writel(val, socket->base + CARDBUS_SOCKET_REGS_BASE + offset); +} + +static void cardu_pciregs_init(struct pci_dev *dev) +{ + u32 syscnt; + u16 brgcnt; + u8 devcnt; + + pci_write_config_dword(dev, 0x1c, 0x10000000); + pci_write_config_dword(dev, 0x20, 0x17fff000); + pci_write_config_dword(dev, 0x2c, 0); + pci_write_config_dword(dev, 0x30, 0xfffc); + + pci_read_config_word(dev, BRGCNT, &brgcnt); + brgcnt &= ~IREQ_INT; + pci_write_config_word(dev, BRGCNT, brgcnt); + + pci_read_config_dword(dev, SYSCNT, &syscnt); + syscnt &= ~(BAD_VCC_REQ_DISB|PCPCI_EN|CH_ASSIGN_MASK|SUB_ID_WR_EN|PCI_CLK_RIN); + syscnt |= (CH_ASSIGN_NODMA|ASYN_INT_MODE); + pci_write_config_dword(dev, SYSCNT, syscnt); + + pci_read_config_byte(dev, DEVCNT, &devcnt); + devcnt &= ~(ZOOM_VIDEO_EN|SR_PCI_INT_SEL_MASK|PCI_INT_MODE|IRQ_MODE); + devcnt |= (SR_PCI_INT_SEL_NONE|IFG); + pci_write_config_byte(dev, DEVCNT, devcnt); + + pci_write_config_byte(dev, CHIPCNT, S_PREF_DISB); + + pci_write_config_byte(dev, SERRDIS, 0); +} + +static int cardu_init(unsigned int slot) +{ + vrc4173_socket_t *socket = &cardu_sockets[slot]; + + cardu_pciregs_init(socket->dev); + + /* CARD_SC bits are cleared by reading CARD_SC. */ + exca_writeb(socket, GLO_CNT, 0); + + socket->cap.features |= SS_CAP_PCCARD | SS_CAP_PAGE_REGS; + socket->cap.irq_mask = 0; + socket->cap.map_size = 0x1000; + socket->cap.pci_irq = socket->dev->irq; + socket->events = 0; + spin_lock_init(socket->event_lock); + + /* Enable PC Card status interrupts */ + exca_writeb(socket, CARD_SCI, CARD_DT_EN|RDY_EN|BAT_WAR_EN|BAT_DEAD_EN); + + return 0; +} + +static int cardu_register_callback(unsigned int sock, + void (*handler)(void *, unsigned int), + void * info) +{ + vrc4173_socket_t *socket = &cardu_sockets[sock]; + + socket->handler = handler; + socket->info = info; + + return 0; +} + +static int cardu_inquire_socket(unsigned int sock, socket_cap_t *cap) +{ + vrc4173_socket_t *socket = &cardu_sockets[sock]; + + *cap = socket->cap; + + return 0; +} + +static int cardu_get_status(unsigned int sock, u_int *value) +{ + vrc4173_socket_t *socket = &cardu_sockets[sock]; + uint32_t state; + uint8_t status; + u_int val = 0; + + status = exca_readb(socket, IF_STATUS); + if (status & CARD_PWR) val |= SS_POWERON; + if (status & READY) val |= SS_READY; + if (status & CARD_WP) val |= SS_WRPROT; + if ((status & (CARD_DETECT1|CARD_DETECT2)) == (CARD_DETECT1|CARD_DETECT2)) + val |= SS_DETECT; + if (exca_readb(socket, INT_GEN_CNT) & CARD_TYPE_IO) { + if (status & STSCHG) val |= SS_STSCHG; + } else { + status &= BV_DETECT_MASK; + if (status != BV_DETECT_GOOD) { + if (status == BV_DETECT_WARN) val |= SS_BATWARN; + else val |= SS_BATDEAD; + } + } + + state = cardbus_socket_readl(socket, SKT_PRE_STATE); + if (state & VOL_3V_CARD_DT) val |= SS_3VCARD; + if (state & VOL_XV_CARD_DT) val |= SS_XVCARD; + if (state & CB_CARD_DT) val |= SS_CARDBUS; + if (!(state & + (VOL_YV_CARD_DT|VOL_XV_CARD_DT|VOL_3V_CARD_DT|VOL_5V_CARD_DT|CCD20|CCD10))) + val |= SS_PENDING; + + *value = val; + + return 0; +} + +static inline u_char get_Vcc_value(uint8_t val) +{ + switch (val & VCC_MASK) { + case VCC_3V: + return 33; + case VCC_5V: + return 50; + } + + return 0; +} + +static inline u_char get_Vpp_value(uint8_t val) +{ + switch (val & VPP_MASK) { + case VPP_12V: + return 120; + case VPP_VCC: + return get_Vcc_value(val); + } + + return 0; +} + +static int cardu_get_socket(unsigned int sock, socket_state_t *state) +{ + vrc4173_socket_t *socket = &cardu_sockets[sock]; + uint8_t val; + + val = exca_readb(socket, PWR_CNT); + state->Vcc = get_Vcc_value(val); + state->Vpp = get_Vpp_value(val); + state->flags = 0; + if (val & CARD_OUT_EN) state->flags |= SS_OUTPUT_ENA; + + val = exca_readb(socket, INT_GEN_CNT); + if (!(val & CARD_REST0)) state->flags |= SS_RESET; + if (val & CARD_TYPE_IO) state->flags |= SS_IOCARD; + + return 0; +} + +static inline uint8_t set_Vcc_value(u_char Vcc) +{ + switch (Vcc) { + case 33: + return VCC_3V; + case 50: + return VCC_5V; + } + + return VCC_0V; +} + +static inline uint8_t set_Vpp_value(u_char Vpp) +{ + switch (Vpp) { + case 33: + case 50: + return VPP_VCC; + case 120: + return VPP_12V; + } + + return VPP_0V; +} + +static int cardu_set_socket(unsigned int sock, socket_state_t *state) +{ + vrc4173_socket_t *socket = &cardu_sockets[sock]; + uint8_t val; + + if (((state->Vpp == 33) || (state->Vpp == 50)) && (state->Vpp != state->Vcc)) + return -EINVAL; + + val = set_Vcc_value(state->Vcc); + val |= set_Vpp_value(state->Vpp); + if (state->flags & SS_OUTPUT_ENA) val |= CARD_OUT_EN; + exca_writeb(socket, PWR_CNT, val); + + val = exca_readb(socket, INT_GEN_CNT) & CARD_REST0; + if (state->flags & SS_RESET) val &= ~CARD_REST0; + else val |= CARD_REST0; + if (state->flags & SS_IOCARD) val |= CARD_TYPE_IO; + exca_writeb(socket, INT_GEN_CNT, val); + + return 0; +} + +static int cardu_get_io_map(unsigned int sock, struct pccard_io_map *io) +{ + vrc4173_socket_t *socket = &cardu_sockets[sock]; + uint8_t ioctl, window; + u_char map; + + map = io->map; + if (map > 1) + return -EINVAL; + + io->start = exca_readw(socket, IO_WIN_SA(map)); + io->stop = exca_readw(socket, IO_WIN_EA(map)); + + ioctl = exca_readb(socket, IO_WIN_CNT); + window = exca_readb(socket, ADR_WIN_EN); + io->flags = (window & IO_WIN_EN(map)) ? MAP_ACTIVE : 0; + if (ioctl & IO_WIN_DATA_AUTOSZ(map)) + io->flags |= MAP_AUTOSZ; + else if (ioctl & IO_WIN_DATA_16BIT(map)) + io->flags |= MAP_16BIT; + + return 0; +} + +static int cardu_set_io_map(unsigned int sock, struct pccard_io_map *io) +{ + vrc4173_socket_t *socket = &cardu_sockets[sock]; + uint16_t ioctl; + uint8_t window, enable; + u_char map; + + map = io->map; + if (map > 1) + return -EINVAL; + + window = exca_readb(socket, ADR_WIN_EN); + enable = IO_WIN_EN(map); + + if (window & enable) { + window &= ~enable; + exca_writeb(socket, ADR_WIN_EN, window); + } + + exca_writew(socket, IO_WIN_SA(map), io->start); + exca_writew(socket, IO_WIN_EA(map), io->stop); + + ioctl = exca_readb(socket, IO_WIN_CNT) & ~IO_WIN_CNT_MASK(map); + if (io->flags & MAP_AUTOSZ) ioctl |= IO_WIN_DATA_AUTOSZ(map); + else if (io->flags & MAP_16BIT) ioctl |= IO_WIN_DATA_16BIT(map); + exca_writeb(socket, IO_WIN_CNT, ioctl); + + if (io->flags & MAP_ACTIVE) + exca_writeb(socket, ADR_WIN_EN, window | enable); + + return 0; +} + +static int cardu_get_mem_map(unsigned int sock, struct pccard_mem_map *mem) +{ + vrc4173_socket_t *socket = &cardu_sockets[sock]; + uint32_t start, stop, offset, page; + uint8_t window; + u_char map; + + map = mem->map; + if (map > 4) + return -EINVAL; + + window = exca_readb(socket, ADR_WIN_EN); + mem->flags = (window & MEM_WIN_EN(map)) ? MAP_ACTIVE : 0; + + start = exca_readw(socket, MEM_WIN_SA(map)); + mem->flags |= (start & MEM_WIN_DSIZE) ? MAP_16BIT : 0; + start = (start & 0x0fff) << 12; + + stop = exca_readw(socket, MEM_WIN_EA(map)); + stop = ((stop & 0x0fff) << 12) + 0x0fff; + + offset = exca_readw(socket, MEM_WIN_OA(map)); + mem->flags |= (offset & MEM_WIN_WP) ? MAP_WRPROT : 0; + mem->flags |= (offset & MEM_WIN_REGSET) ? MAP_ATTRIB : 0; + offset = ((offset & 0x3fff) << 12) + start; + mem->card_start = offset & 0x03ffffff; + + page = exca_readb(socket, MEM_WIN_SAU(map)) << 24; + mem->sys_start = start + page; + mem->sys_stop = start + page; + + return 0; +} + +static int cardu_set_mem_map(unsigned int sock, struct pccard_mem_map *mem) +{ + vrc4173_socket_t *socket = &cardu_sockets[sock]; + uint16_t value; + uint8_t window, enable; + u_long sys_start, sys_stop, card_start; + u_char map; + + map = mem->map; + sys_start = mem->sys_start; + sys_stop = mem->sys_stop; + card_start = mem->card_start; + + if (map > 4 || sys_start > sys_stop || ((sys_start ^ sys_stop) >> 24) || + (card_start >> 26)) + return -EINVAL; + + window = exca_readb(socket, ADR_WIN_EN); + enable = MEM_WIN_EN(map); + if (window & enable) { + window &= ~enable; + exca_writeb(socket, ADR_WIN_EN, window); + } + + exca_writeb(socket, MEM_WIN_SAU(map), sys_start >> 24); + + value = (sys_start >> 12) & 0x0fff; + if (mem->flags & MAP_16BIT) value |= MEM_WIN_DSIZE; + exca_writew(socket, MEM_WIN_SA(map), value); + + value = (sys_stop >> 12) & 0x0fff; + exca_writew(socket, MEM_WIN_EA(map), value); + + value = ((card_start - sys_start) >> 12) & 0x3fff; + if (mem->flags & MAP_WRPROT) value |= MEM_WIN_WP; + if (mem->flags & MAP_ATTRIB) value |= MEM_WIN_REGSET; + exca_writew(socket, MEM_WIN_OA(map), value); + + if (mem->flags & MAP_ACTIVE) + exca_writeb(socket, ADR_WIN_EN, window | enable); + + return 0; +} + +static void cardu_proc_setup(unsigned int sock, struct proc_dir_entry *base) +{ +} + +static struct pccard_operations cardu_operations = { + .init = cardu_init, + .register_callback = cardu_register_callback, + .inquire_socket = cardu_inquire_socket, + .get_status = cardu_get_status, + .get_socket = cardu_get_socket, + .set_socket = cardu_set_socket, + .get_io_map = cardu_get_io_map, + .set_io_map = cardu_set_io_map, + .get_mem_map = cardu_get_mem_map, + .set_mem_map = cardu_set_mem_map, + .proc_setup = cardu_proc_setup, +}; + +static void cardu_bh(void *data) +{ + vrc4173_socket_t *socket = (vrc4173_socket_t *)data; + uint16_t events; + + spin_lock_irq(&socket->event_lock); + events = socket->events; + socket->events = 0; + spin_unlock_irq(&socket->event_lock); + + if (socket->handler) + socket->handler(socket->info, events); +} + +static uint16_t get_events(vrc4173_socket_t *socket) +{ + uint16_t events = 0; + uint8_t csc, status; + + status = exca_readb(socket, IF_STATUS); + csc = exca_readb(socket, CARD_SC); + if ((csc & CARD_DT_CHG) && + ((status & (CARD_DETECT1|CARD_DETECT2)) == (CARD_DETECT1|CARD_DETECT2))) + events |= SS_DETECT; + + if ((csc & RDY_CHG) && (status & READY)) + events |= SS_READY; + + if (exca_readb(socket, INT_GEN_CNT) & CARD_TYPE_IO) { + if ((csc & BAT_DEAD_ST_CHG) && (status & STSCHG)) + events |= SS_STSCHG; + } else { + if (csc & (BAT_WAR_CHG|BAT_DEAD_ST_CHG)) { + if ((status & BV_DETECT_MASK) != BV_DETECT_GOOD) { + if (status == BV_DETECT_WARN) events |= SS_BATWARN; + else events |= SS_BATDEAD; + } + } + } + + return events; +} + +static void cardu_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + vrc4173_socket_t *socket = (vrc4173_socket_t *)dev_id; + uint16_t events; + + INIT_WORK(&socket->tq_work, cardu_bh, socket); + + events = get_events(socket); + if (events) { + spin_lock(&socket->event_lock); + socket->events |= events; + spin_unlock(&socket->event_lock); + schedule_work(&socket->tq_work); + } +} + +static int __devinit vrc4173_cardu_probe(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + vrc4173_socket_t *socket; + unsigned long start, len, flags; + int slot, err; + + slot = vrc4173_cardu_slots++; + socket = &cardu_sockets[slot]; + if (socket->noprobe != 0) + return -EBUSY; + + sprintf(socket->name, "NEC VRC4173 CARDU%1d", slot+1); + + if ((err = pci_enable_device(dev)) < 0) + return err; + + start = pci_resource_start(dev, 0); + if (start == 0) + return -ENODEV; + + len = pci_resource_len(dev, 0); + if (len == 0) + return -ENODEV; + + if (((flags = pci_resource_flags(dev, 0)) & IORESOURCE_MEM) == 0) + return -EBUSY; + + if ((err = pci_request_regions(dev, socket->name)) < 0) + return err; + + socket->base = ioremap(start, len); + if (socket->base == NULL) + return -ENODEV; + + socket->dev = dev; + + socket->pcmcia_socket = pcmcia_register_socket(slot, &cardu_operations, 1); + if (socket->pcmcia_socket == NULL) { + iounmap(socket->base); + socket->base = NULL; + return -ENOMEM; + } + + if (request_irq(dev->irq, cardu_interrupt, SA_SHIRQ, socket->name, socket) < 0) { + pcmcia_unregister_socket(socket->pcmcia_socket); + socket->pcmcia_socket = NULL; + iounmap(socket->base); + socket->base = NULL; + return -EBUSY; + } + + printk(KERN_INFO "%s at %#08lx, IRQ %d\n", socket->name, start, dev->irq); + + return 0; +} + +static int __devinit vrc4173_cardu_setup(char *options) +{ + if (options == NULL || *options == '\0') + return 0; + + if (strncmp(options, "cardu1:", 7) == 0) { + options += 7; + if (*options != '\0') { + if (strncmp(options, "noprobe", 7) == 0) { + cardu_sockets[CARDU1].noprobe = 1; + options += 7; + } + + if (*options != ',') + return 0; + } else + return 0; + } + + if (strncmp(options, "cardu2:", 7) == 0) { + options += 7; + if ((*options != '\0') && (strncmp(options, "noprobe", 7) == 0)) + cardu_sockets[CARDU2].noprobe = 1; + } + + return 0; +} + +__setup("vrc4173_cardu=", vrc4173_cardu_setup); + +static struct pci_device_id vrc4173_cardu_id_table[] __devinitdata = { + { .vendor = PCI_VENDOR_ID_NEC, + .device = PCI_DEVICE_ID_NEC_NAPCCARD, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, }, + {0, } +}; + +static struct pci_driver vrc4173_cardu_driver = { + .name = "NEC VRC4173 CARDU", + .probe = vrc4173_cardu_probe, + .id_table = vrc4173_cardu_id_table, +}; + +static int __devinit vrc4173_cardu_init(void) +{ + vrc4173_cardu_slots = 0; + + return pci_module_init(&vrc4173_cardu_driver); +} + +static void __devexit vrc4173_cardu_exit(void) +{ + pci_unregister_driver(&vrc4173_cardu_driver); +} + +module_init(vrc4173_cardu_init); +module_exit(vrc4173_cardu_exit); +MODULE_DEVICE_TABLE(pci, vrc4173_cardu_id_table); diff --git a/drivers/pcmcia/vrc4173_cardu.h b/drivers/pcmcia/vrc4173_cardu.h new file mode 100644 index 000000000000..113726f7cf92 --- /dev/null +++ b/drivers/pcmcia/vrc4173_cardu.h @@ -0,0 +1,247 @@ +/* + * FILE NAME + * drivers/pcmcia/vrc4173_cardu.h + * + * BRIEF MODULE DESCRIPTION + * Include file for NEC VRC4173 CARDU. + * + * Copyright 2002 Yoichi Yuasa <yuasa@hh.iij4u.or.jp> + * + * 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef _VRC4173_CARDU_H +#define _VRC4173_CARDU_H + +#include <linux/pci.h> + +#include <pcmcia/ss.h> + +#define CARDU_MAX_SOCKETS 2 +#define CARDU1 0 +#define CARDU2 1 + +/* + * PCI Configuration Registers + */ +#define BRGCNT 0x3e + #define POST_WR_EN 0x0400 + #define MEM1_PREF_EN 0x0200 + #define MEM0_PREF_EN 0x0100 + #define IREQ_INT 0x0080 + #define CARD_RST 0x0040 + #define MABORT_MODE 0x0020 + #define VGA_EN 0x0008 + #define ISA_EN 0x0004 + #define SERR_EN 0x0002 + #define PERR_EN 0x0001 + +#define SYSCNT 0x80 + #define BAD_VCC_REQ_DISB 0x00200000 + #define PCPCI_EN 0x00080000 + #define CH_ASSIGN_MASK 0x00070000 + #define CH_ASSIGN_NODMA 0x00040000 + #define SUB_ID_WR_EN 0x00000008 + #define ASYN_INT_MODE 0x00000004 + #define PCI_CLK_RIN 0x00000002 + +#define DEVCNT 0x91 + #define ZOOM_VIDEO_EN 0x40 + #define SR_PCI_INT_SEL_MASK 0x18 + #define SR_PCI_INT_SEL_NONE 0x00 + #define PCI_INT_MODE 0x04 + #define IRQ_MODE 0x02 + #define IFG 0x01 + +#define CHIPCNT 0x9c + #define S_PREF_DISB 0x10 + +#define SERRDIS 0x9f + #define SERR_DIS_MAB 0x10 + #define SERR_DIS_TAB 0x08 + #define SERR_DIS_DT_PERR 0x04 + +/* + * ExCA Registers + */ +#define EXCA_REGS_BASE 0x800 +#define EXCA_REGS_SIZE 0x800 + +#define ID_REV 0x000 + #define IF_TYPE_16BIT 0x80 + +#define IF_STATUS 0x001 + #define CARD_PWR 0x40 + #define READY 0x20 + #define CARD_WP 0x10 + #define CARD_DETECT2 0x08 + #define CARD_DETECT1 0x04 + #define BV_DETECT_MASK 0x03 + #define BV_DETECT_GOOD 0x03 /* Memory card */ + #define BV_DETECT_WARN 0x02 + #define BV_DETECT_BAD1 0x01 + #define BV_DETECT_BAD0 0x00 + #define STSCHG 0x02 /* I/O card */ + #define SPKR 0x01 + +#define PWR_CNT 0x002 + #define CARD_OUT_EN 0x80 + #define VCC_MASK 0x18 + #define VCC_3V 0x18 + #define VCC_5V 0x10 + #define VCC_0V 0x00 + #define VPP_MASK 0x03 + #define VPP_12V 0x02 + #define VPP_VCC 0x01 + #define VPP_0V 0x00 + +#define INT_GEN_CNT 0x003 + #define CARD_REST0 0x40 + #define CARD_TYPE_MASK 0x20 + #define CARD_TYPE_IO 0x20 + #define CARD_TYPE_MEM 0x00 + +#define CARD_SC 0x004 + #define CARD_DT_CHG 0x08 + #define RDY_CHG 0x04 + #define BAT_WAR_CHG 0x02 + #define BAT_DEAD_ST_CHG 0x01 + +#define CARD_SCI 0x005 + #define CARD_DT_EN 0x08 + #define RDY_EN 0x04 + #define BAT_WAR_EN 0x02 + #define BAT_DEAD_EN 0x01 + +#define ADR_WIN_EN 0x006 + #define IO_WIN_EN(x) (0x40 << (x)) + #define MEM_WIN_EN(x) (0x01 << (x)) + +#define IO_WIN_CNT 0x007 + #define IO_WIN_CNT_MASK(x) (0x03 << ((x) << 2)) + #define IO_WIN_DATA_AUTOSZ(x) (0x02 << ((x) << 2)) + #define IO_WIN_DATA_16BIT(x) (0x01 << ((x) << 2)) + +#define IO_WIN_SA(x) (0x008 + ((x) << 2)) +#define IO_WIN_EA(x) (0x00a + ((x) << 2)) + +#define MEM_WIN_SA(x) (0x010 + ((x) << 3)) + #define MEM_WIN_DSIZE 0x8000 + +#define MEM_WIN_EA(x) (0x012 + ((x) << 3)) + +#define MEM_WIN_OA(x) (0x014 + ((x) << 3)) + #define MEM_WIN_WP 0x8000 + #define MEM_WIN_REGSET 0x4000 + +#define GEN_CNT 0x016 + #define VS2_STATUS 0x80 + #define VS1_STATUS 0x40 + #define EXCA_REG_RST_EN 0x02 + +#define GLO_CNT 0x01e + #define FUN_INT_LEV 0x08 + #define INT_WB_CLR 0x04 + #define CSC_INT_LEV 0x02 + +#define IO_WIN_OAL(x) (0x036 + ((x) << 1)) +#define IO_WIN_OAH(x) (0x037 + ((x) << 1)) + +#define MEM_WIN_SAU(x) (0x040 + (x)) + +#define IO_SETUP_TIM 0x080 +#define IO_CMD_TIM 0x081 +#define IO_HOLD_TIM 0x082 +#define MEM_SETUP_TIM(x) (0x084 + ((x) << 2)) +#define MEM_CMD_TIM(x) (0x085 + ((x) << 2)) +#define MEM_HOLD_TIM(x) (0x086 + ((x) << 2)) + #define TIM_CLOCKS(x) ((x) - 1) + +#define MEM_TIM_SEL1 0x08c +#define MEM_TIM_SEL2 0x08d + #define MEM_WIN_TIMSEL1(x) (0x03 << (((x) & 3) << 1)) + +#define MEM_WIN_PWEN 0x091 + #define POSTWEN 0x01 + +/* + * CardBus Socket Registers + */ +#define CARDBUS_SOCKET_REGS_BASE 0x000 +#define CARDBUS_SOCKET_REGS_SIZE 0x800 + +#define SKT_EV 0x000 + #define POW_CYC_EV 0x00000008 + #define CCD2_EV 0x00000004 + #define CCD1_EV 0x00000002 + #define CSTSCHG_EV 0x00000001 + +#define SKT_MASK 0x004 + #define POW_CYC_MASK 0x00000008 + #define CCD_MASK 0x00000006 + #define CSC_MASK 0x00000001 + +#define SKT_PRE_STATE 0x008 +#define SKT_FORCE_EV 0x00c + #define VOL_3V_SKT 0x20000000 + #define VOL_5V_SKT 0x10000000 + #define CVS_TEST 0x00004000 + #define VOL_YV_CARD_DT 0x00002000 + #define VOL_XV_CARD_DT 0x00001000 + #define VOL_3V_CARD_DT 0x00000800 + #define VOL_5V_CARD_DT 0x00000400 + #define BAD_VCC_REQ 0x00000200 + #define DATA_LOST 0x00000100 + #define NOT_A_CARD 0x00000080 + #define CREADY 0x00000040 + #define CB_CARD_DT 0x00000020 + #define R2_CARD_DT 0x00000010 + #define POW_UP 0x00000008 + #define CCD20 0x00000004 + #define CCD10 0x00000002 + #define CSTSCHG 0x00000001 + +#define SKT_CNT 0x010 + #define STP_CLK_EN 0x00000080 + #define VCC_CNT_MASK 0x00000070 + #define VCC_CNT_3V 0x00000030 + #define VCC_CNT_5V 0x00000020 + #define VCC_CNT_0V 0x00000000 + #define VPP_CNT_MASK 0x00000007 + #define VPP_CNT_3V 0x00000003 + #define VPP_CNT_5V 0x00000002 + #define VPP_CNT_12V 0x00000001 + #define VPP_CNT_0V 0x00000000 + +typedef struct vrc4173_socket { + int noprobe; + struct pci_dev *dev; + void *base; + void (*handler)(void *, unsigned int); + void *info; + socket_cap_t cap; + spinlock_t event_lock; + uint16_t events; + struct socket_info_t *pcmcia_socket; + struct work_struct tq_work; + char name[20]; +} vrc4173_socket_t; + +#endif /* _VRC4173_CARDU_H */ diff --git a/drivers/pcmcia/yenta_socket.c b/drivers/pcmcia/yenta_socket.c new file mode 100644 index 000000000000..6404d97a12eb --- /dev/null +++ b/drivers/pcmcia/yenta_socket.c @@ -0,0 +1,1160 @@ +/* + * Regular cardbus driver ("yenta_socket") + * + * (C) Copyright 1999, 2000 Linus Torvalds + * + * Changelog: + * Aug 2002: Manfred Spraul <manfred@colorfullife.com> + * Dynamically adjust the size of the bridge resource + * + * May 2003: Dominik Brodowski <linux@brodo.de> + * Merge pci_socket.c and yenta.c into one file + */ +#include <linux/init.h> +#include <linux/pci.h> +#include <linux/sched.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/module.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> + +#include <asm/io.h> + +#include "yenta_socket.h" +#include "i82365.h" + +static int disable_clkrun; +module_param(disable_clkrun, bool, 0444); +MODULE_PARM_DESC(disable_clkrun, "If PC card doesn't function properly, please try this option"); + +#if 0 +#define debug(x,args...) printk(KERN_DEBUG "%s: " x, __func__ , ##args) +#else +#define debug(x,args...) +#endif + +/* Don't ask.. */ +#define to_cycles(ns) ((ns)/120) +#define to_ns(cycles) ((cycles)*120) + +static int yenta_probe_cb_irq(struct yenta_socket *socket); + + +static unsigned int override_bios; +module_param(override_bios, uint, 0000); +MODULE_PARM_DESC (override_bios, "yenta ignore bios resource allocation"); + +/* + * Generate easy-to-use ways of reading a cardbus sockets + * regular memory space ("cb_xxx"), configuration space + * ("config_xxx") and compatibility space ("exca_xxxx") + */ +static inline u32 cb_readl(struct yenta_socket *socket, unsigned reg) +{ + u32 val = readl(socket->base + reg); + debug("%p %04x %08x\n", socket, reg, val); + return val; +} + +static inline void cb_writel(struct yenta_socket *socket, unsigned reg, u32 val) +{ + debug("%p %04x %08x\n", socket, reg, val); + writel(val, socket->base + reg); +} + +static inline u8 config_readb(struct yenta_socket *socket, unsigned offset) +{ + u8 val; + pci_read_config_byte(socket->dev, offset, &val); + debug("%p %04x %02x\n", socket, offset, val); + return val; +} + +static inline u16 config_readw(struct yenta_socket *socket, unsigned offset) +{ + u16 val; + pci_read_config_word(socket->dev, offset, &val); + debug("%p %04x %04x\n", socket, offset, val); + return val; +} + +static inline u32 config_readl(struct yenta_socket *socket, unsigned offset) +{ + u32 val; + pci_read_config_dword(socket->dev, offset, &val); + debug("%p %04x %08x\n", socket, offset, val); + return val; +} + +static inline void config_writeb(struct yenta_socket *socket, unsigned offset, u8 val) +{ + debug("%p %04x %02x\n", socket, offset, val); + pci_write_config_byte(socket->dev, offset, val); +} + +static inline void config_writew(struct yenta_socket *socket, unsigned offset, u16 val) +{ + debug("%p %04x %04x\n", socket, offset, val); + pci_write_config_word(socket->dev, offset, val); +} + +static inline void config_writel(struct yenta_socket *socket, unsigned offset, u32 val) +{ + debug("%p %04x %08x\n", socket, offset, val); + pci_write_config_dword(socket->dev, offset, val); +} + +static inline u8 exca_readb(struct yenta_socket *socket, unsigned reg) +{ + u8 val = readb(socket->base + 0x800 + reg); + debug("%p %04x %02x\n", socket, reg, val); + return val; +} + +static inline u8 exca_readw(struct yenta_socket *socket, unsigned reg) +{ + u16 val; + val = readb(socket->base + 0x800 + reg); + val |= readb(socket->base + 0x800 + reg + 1) << 8; + debug("%p %04x %04x\n", socket, reg, val); + return val; +} + +static inline void exca_writeb(struct yenta_socket *socket, unsigned reg, u8 val) +{ + debug("%p %04x %02x\n", socket, reg, val); + writeb(val, socket->base + 0x800 + reg); +} + +static void exca_writew(struct yenta_socket *socket, unsigned reg, u16 val) +{ + debug("%p %04x %04x\n", socket, reg, val); + writeb(val, socket->base + 0x800 + reg); + writeb(val >> 8, socket->base + 0x800 + reg + 1); +} + +/* + * Ugh, mixed-mode cardbus and 16-bit pccard state: things depend + * on what kind of card is inserted.. + */ +static int yenta_get_status(struct pcmcia_socket *sock, unsigned int *value) +{ + struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket); + unsigned int val; + u32 state = cb_readl(socket, CB_SOCKET_STATE); + + val = (state & CB_3VCARD) ? SS_3VCARD : 0; + val |= (state & CB_XVCARD) ? SS_XVCARD : 0; + val |= (state & (CB_CDETECT1 | CB_CDETECT2 | CB_5VCARD | CB_3VCARD + | CB_XVCARD | CB_YVCARD)) ? 0 : SS_PENDING; + + if (state & CB_CBCARD) { + val |= SS_CARDBUS; + val |= (state & CB_CARDSTS) ? SS_STSCHG : 0; + val |= (state & (CB_CDETECT1 | CB_CDETECT2)) ? 0 : SS_DETECT; + val |= (state & CB_PWRCYCLE) ? SS_POWERON | SS_READY : 0; + } else { + u8 status = exca_readb(socket, I365_STATUS); + val |= ((status & I365_CS_DETECT) == I365_CS_DETECT) ? SS_DETECT : 0; + if (exca_readb(socket, I365_INTCTL) & I365_PC_IOCARD) { + val |= (status & I365_CS_STSCHG) ? 0 : SS_STSCHG; + } else { + val |= (status & I365_CS_BVD1) ? 0 : SS_BATDEAD; + val |= (status & I365_CS_BVD2) ? 0 : SS_BATWARN; + } + val |= (status & I365_CS_WRPROT) ? SS_WRPROT : 0; + val |= (status & I365_CS_READY) ? SS_READY : 0; + val |= (status & I365_CS_POWERON) ? SS_POWERON : 0; + } + + *value = val; + return 0; +} + +static int yenta_Vcc_power(u32 control) +{ + switch (control & CB_SC_VCC_MASK) { + case CB_SC_VCC_5V: return 50; + case CB_SC_VCC_3V: return 33; + default: return 0; + } +} + +static int yenta_Vpp_power(u32 control) +{ + switch (control & CB_SC_VPP_MASK) { + case CB_SC_VPP_12V: return 120; + case CB_SC_VPP_5V: return 50; + case CB_SC_VPP_3V: return 33; + default: return 0; + } +} + +static int yenta_get_socket(struct pcmcia_socket *sock, socket_state_t *state) +{ + struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket); + u8 reg; + u32 control; + + control = cb_readl(socket, CB_SOCKET_CONTROL); + + state->Vcc = yenta_Vcc_power(control); + state->Vpp = yenta_Vpp_power(control); + state->io_irq = socket->io_irq; + + if (cb_readl(socket, CB_SOCKET_STATE) & CB_CBCARD) { + u16 bridge = config_readw(socket, CB_BRIDGE_CONTROL); + if (bridge & CB_BRIDGE_CRST) + state->flags |= SS_RESET; + return 0; + } + + /* 16-bit card state.. */ + reg = exca_readb(socket, I365_POWER); + state->flags = (reg & I365_PWR_AUTO) ? SS_PWR_AUTO : 0; + state->flags |= (reg & I365_PWR_OUT) ? SS_OUTPUT_ENA : 0; + + reg = exca_readb(socket, I365_INTCTL); + state->flags |= (reg & I365_PC_RESET) ? 0 : SS_RESET; + state->flags |= (reg & I365_PC_IOCARD) ? SS_IOCARD : 0; + + reg = exca_readb(socket, I365_CSCINT); + state->csc_mask = (reg & I365_CSC_DETECT) ? SS_DETECT : 0; + if (state->flags & SS_IOCARD) { + state->csc_mask |= (reg & I365_CSC_STSCHG) ? SS_STSCHG : 0; + } else { + state->csc_mask |= (reg & I365_CSC_BVD1) ? SS_BATDEAD : 0; + state->csc_mask |= (reg & I365_CSC_BVD2) ? SS_BATWARN : 0; + state->csc_mask |= (reg & I365_CSC_READY) ? SS_READY : 0; + } + + return 0; +} + +static void yenta_set_power(struct yenta_socket *socket, socket_state_t *state) +{ + u32 reg = 0; /* CB_SC_STPCLK? */ + switch (state->Vcc) { + case 33: reg = CB_SC_VCC_3V; break; + case 50: reg = CB_SC_VCC_5V; break; + default: reg = 0; break; + } + switch (state->Vpp) { + case 33: reg |= CB_SC_VPP_3V; break; + case 50: reg |= CB_SC_VPP_5V; break; + case 120: reg |= CB_SC_VPP_12V; break; + } + if (reg != cb_readl(socket, CB_SOCKET_CONTROL)) + cb_writel(socket, CB_SOCKET_CONTROL, reg); +} + +static int yenta_set_socket(struct pcmcia_socket *sock, socket_state_t *state) +{ + struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket); + u16 bridge; + + yenta_set_power(socket, state); + socket->io_irq = state->io_irq; + bridge = config_readw(socket, CB_BRIDGE_CONTROL) & ~(CB_BRIDGE_CRST | CB_BRIDGE_INTR); + if (cb_readl(socket, CB_SOCKET_STATE) & CB_CBCARD) { + u8 intr; + bridge |= (state->flags & SS_RESET) ? CB_BRIDGE_CRST : 0; + + /* ISA interrupt control? */ + intr = exca_readb(socket, I365_INTCTL); + intr = (intr & ~0xf); + if (!socket->cb_irq) { + intr |= state->io_irq; + bridge |= CB_BRIDGE_INTR; + } + exca_writeb(socket, I365_INTCTL, intr); + } else { + u8 reg; + + reg = exca_readb(socket, I365_INTCTL) & (I365_RING_ENA | I365_INTR_ENA); + reg |= (state->flags & SS_RESET) ? 0 : I365_PC_RESET; + reg |= (state->flags & SS_IOCARD) ? I365_PC_IOCARD : 0; + if (state->io_irq != socket->cb_irq) { + reg |= state->io_irq; + bridge |= CB_BRIDGE_INTR; + } + exca_writeb(socket, I365_INTCTL, reg); + + reg = exca_readb(socket, I365_POWER) & (I365_VCC_MASK|I365_VPP1_MASK); + reg |= I365_PWR_NORESET; + if (state->flags & SS_PWR_AUTO) reg |= I365_PWR_AUTO; + if (state->flags & SS_OUTPUT_ENA) reg |= I365_PWR_OUT; + if (exca_readb(socket, I365_POWER) != reg) + exca_writeb(socket, I365_POWER, reg); + + /* CSC interrupt: no ISA irq for CSC */ + reg = I365_CSC_DETECT; + if (state->flags & SS_IOCARD) { + if (state->csc_mask & SS_STSCHG) reg |= I365_CSC_STSCHG; + } else { + if (state->csc_mask & SS_BATDEAD) reg |= I365_CSC_BVD1; + if (state->csc_mask & SS_BATWARN) reg |= I365_CSC_BVD2; + if (state->csc_mask & SS_READY) reg |= I365_CSC_READY; + } + exca_writeb(socket, I365_CSCINT, reg); + exca_readb(socket, I365_CSC); + if(sock->zoom_video) + sock->zoom_video(sock, state->flags & SS_ZVCARD); + } + config_writew(socket, CB_BRIDGE_CONTROL, bridge); + /* Socket event mask: get card insert/remove events.. */ + cb_writel(socket, CB_SOCKET_EVENT, -1); + cb_writel(socket, CB_SOCKET_MASK, CB_CDMASK); + return 0; +} + +static int yenta_set_io_map(struct pcmcia_socket *sock, struct pccard_io_map *io) +{ + struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket); + int map; + unsigned char ioctl, addr, enable; + + map = io->map; + + if (map > 1) + return -EINVAL; + + enable = I365_ENA_IO(map); + addr = exca_readb(socket, I365_ADDRWIN); + + /* Disable the window before changing it.. */ + if (addr & enable) { + addr &= ~enable; + exca_writeb(socket, I365_ADDRWIN, addr); + } + + exca_writew(socket, I365_IO(map)+I365_W_START, io->start); + exca_writew(socket, I365_IO(map)+I365_W_STOP, io->stop); + + ioctl = exca_readb(socket, I365_IOCTL) & ~I365_IOCTL_MASK(map); + if (io->flags & MAP_0WS) ioctl |= I365_IOCTL_0WS(map); + if (io->flags & MAP_16BIT) ioctl |= I365_IOCTL_16BIT(map); + if (io->flags & MAP_AUTOSZ) ioctl |= I365_IOCTL_IOCS16(map); + exca_writeb(socket, I365_IOCTL, ioctl); + + if (io->flags & MAP_ACTIVE) + exca_writeb(socket, I365_ADDRWIN, addr | enable); + return 0; +} + +static int yenta_set_mem_map(struct pcmcia_socket *sock, struct pccard_mem_map *mem) +{ + struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket); + struct pci_bus_region region; + int map; + unsigned char addr, enable; + unsigned int start, stop, card_start; + unsigned short word; + + pcibios_resource_to_bus(socket->dev, ®ion, mem->res); + + map = mem->map; + start = region.start; + stop = region.end; + card_start = mem->card_start; + + if (map > 4 || start > stop || ((start ^ stop) >> 24) || + (card_start >> 26) || mem->speed > 1000) + return -EINVAL; + + enable = I365_ENA_MEM(map); + addr = exca_readb(socket, I365_ADDRWIN); + if (addr & enable) { + addr &= ~enable; + exca_writeb(socket, I365_ADDRWIN, addr); + } + + exca_writeb(socket, CB_MEM_PAGE(map), start >> 24); + + word = (start >> 12) & 0x0fff; + if (mem->flags & MAP_16BIT) + word |= I365_MEM_16BIT; + if (mem->flags & MAP_0WS) + word |= I365_MEM_0WS; + exca_writew(socket, I365_MEM(map) + I365_W_START, word); + + word = (stop >> 12) & 0x0fff; + switch (to_cycles(mem->speed)) { + case 0: break; + case 1: word |= I365_MEM_WS0; break; + case 2: word |= I365_MEM_WS1; break; + default: word |= I365_MEM_WS1 | I365_MEM_WS0; break; + } + exca_writew(socket, I365_MEM(map) + I365_W_STOP, word); + + word = ((card_start - start) >> 12) & 0x3fff; + if (mem->flags & MAP_WRPROT) + word |= I365_MEM_WRPROT; + if (mem->flags & MAP_ATTRIB) + word |= I365_MEM_REG; + exca_writew(socket, I365_MEM(map) + I365_W_OFF, word); + + if (mem->flags & MAP_ACTIVE) + exca_writeb(socket, I365_ADDRWIN, addr | enable); + return 0; +} + + +static unsigned int yenta_events(struct yenta_socket *socket) +{ + u8 csc; + u32 cb_event; + unsigned int events; + + /* Clear interrupt status for the event */ + cb_event = cb_readl(socket, CB_SOCKET_EVENT); + cb_writel(socket, CB_SOCKET_EVENT, cb_event); + + csc = exca_readb(socket, I365_CSC); + + events = (cb_event & (CB_CD1EVENT | CB_CD2EVENT)) ? SS_DETECT : 0 ; + events |= (csc & I365_CSC_DETECT) ? SS_DETECT : 0; + if (exca_readb(socket, I365_INTCTL) & I365_PC_IOCARD) { + events |= (csc & I365_CSC_STSCHG) ? SS_STSCHG : 0; + } else { + events |= (csc & I365_CSC_BVD1) ? SS_BATDEAD : 0; + events |= (csc & I365_CSC_BVD2) ? SS_BATWARN : 0; + events |= (csc & I365_CSC_READY) ? SS_READY : 0; + } + return events; +} + + +static irqreturn_t yenta_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned int events; + struct yenta_socket *socket = (struct yenta_socket *) dev_id; + + events = yenta_events(socket); + if (events) { + pcmcia_parse_events(&socket->socket, events); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static void yenta_interrupt_wrapper(unsigned long data) +{ + struct yenta_socket *socket = (struct yenta_socket *) data; + + yenta_interrupt(0, (void *)socket, NULL); + socket->poll_timer.expires = jiffies + HZ; + add_timer(&socket->poll_timer); +} + +static void yenta_clear_maps(struct yenta_socket *socket) +{ + int i; + struct resource res = { .start = 0, .end = 0x0fff }; + pccard_io_map io = { 0, 0, 0, 0, 1 }; + pccard_mem_map mem = { .res = &res, }; + + yenta_set_socket(&socket->socket, &dead_socket); + for (i = 0; i < 2; i++) { + io.map = i; + yenta_set_io_map(&socket->socket, &io); + } + for (i = 0; i < 5; i++) { + mem.map = i; + yenta_set_mem_map(&socket->socket, &mem); + } +} + +/* Called at resume and initialization events */ +static int yenta_sock_init(struct pcmcia_socket *sock) +{ + struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket); + u32 state; + u16 bridge; + + bridge = config_readw(socket, CB_BRIDGE_CONTROL) & ~CB_BRIDGE_INTR; + if (!socket->cb_irq) + bridge |= CB_BRIDGE_INTR; + config_writew(socket, CB_BRIDGE_CONTROL, bridge); + + exca_writeb(socket, I365_GBLCTL, 0x00); + exca_writeb(socket, I365_GENCTL, 0x00); + + /* Redo card voltage interrogation */ + state = cb_readl(socket, CB_SOCKET_STATE); + if (!(state & (CB_CDETECT1 | CB_CDETECT2 | CB_5VCARD | + CB_3VCARD | CB_XVCARD | CB_YVCARD))) + cb_writel(socket, CB_SOCKET_FORCE, CB_CVSTEST); + + yenta_clear_maps(socket); + + if (socket->type && socket->type->sock_init) + socket->type->sock_init(socket); + + /* Re-enable CSC interrupts */ + cb_writel(socket, CB_SOCKET_MASK, CB_CDMASK); + + return 0; +} + +static int yenta_sock_suspend(struct pcmcia_socket *sock) +{ + struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket); + + /* Disable CSC interrupts */ + cb_writel(socket, CB_SOCKET_MASK, 0x0); + + return 0; +} + +/* + * Use an adaptive allocation for the memory resource, + * sometimes the memory behind pci bridges is limited: + * 1/8 of the size of the io window of the parent. + * max 4 MB, min 16 kB. + */ +#define BRIDGE_MEM_MAX 4*1024*1024 +#define BRIDGE_MEM_MIN 16*1024 + +#define BRIDGE_IO_MAX 256 +#define BRIDGE_IO_MIN 32 + +#ifndef PCIBIOS_MIN_CARDBUS_IO +#define PCIBIOS_MIN_CARDBUS_IO PCIBIOS_MIN_IO +#endif + +static void yenta_allocate_res(struct yenta_socket *socket, int nr, unsigned type) +{ + struct pci_bus *bus; + struct resource *root, *res; + u32 start, end; + u32 align, size, min; + unsigned offset; + unsigned mask; + + /* The granularity of the memory limit is 4kB, on IO it's 4 bytes */ + mask = ~0xfff; + if (type & IORESOURCE_IO) + mask = ~3; + + offset = 0x1c + 8*nr; + bus = socket->dev->subordinate; + res = socket->dev->resource + PCI_BRIDGE_RESOURCES + nr; + res->name = bus->name; + res->flags = type; + res->start = 0; + res->end = 0; + root = pci_find_parent_resource(socket->dev, res); + + if (!root) + return; + + start = config_readl(socket, offset) & mask; + end = config_readl(socket, offset+4) | ~mask; + if (start && end > start && !override_bios) { + res->start = start; + res->end = end; + if (request_resource(root, res) == 0) + return; + printk(KERN_INFO "yenta %s: Preassigned resource %d busy, reconfiguring...\n", + pci_name(socket->dev), nr); + res->start = res->end = 0; + } + + if (type & IORESOURCE_IO) { + align = 1024; + size = BRIDGE_IO_MAX; + min = BRIDGE_IO_MIN; + start = PCIBIOS_MIN_CARDBUS_IO; + end = ~0U; + } else { + unsigned long avail = root->end - root->start; + int i; + size = BRIDGE_MEM_MAX; + if (size > avail/8) { + size=(avail+1)/8; + /* round size down to next power of 2 */ + i = 0; + while ((size /= 2) != 0) + i++; + size = 1 << i; + } + if (size < BRIDGE_MEM_MIN) + size = BRIDGE_MEM_MIN; + min = BRIDGE_MEM_MIN; + align = size; + start = PCIBIOS_MIN_MEM; + end = ~0U; + } + + do { + if (allocate_resource(root, res, size, start, end, align, NULL, NULL)==0) { + config_writel(socket, offset, res->start); + config_writel(socket, offset+4, res->end); + return; + } + size = size/2; + align = size; + } while (size >= min); + printk(KERN_INFO "yenta %s: no resource of type %x available, trying to continue...\n", + pci_name(socket->dev), type); + res->start = res->end = 0; +} + +/* + * Allocate the bridge mappings for the device.. + */ +static void yenta_allocate_resources(struct yenta_socket *socket) +{ + yenta_allocate_res(socket, 0, IORESOURCE_MEM|IORESOURCE_PREFETCH); + yenta_allocate_res(socket, 1, IORESOURCE_MEM); + yenta_allocate_res(socket, 2, IORESOURCE_IO); + yenta_allocate_res(socket, 3, IORESOURCE_IO); /* PCI isn't clever enough to use this one yet */ +} + + +/* + * Free the bridge mappings for the device.. + */ +static void yenta_free_resources(struct yenta_socket *socket) +{ + int i; + for (i=0;i<4;i++) { + struct resource *res; + res = socket->dev->resource + PCI_BRIDGE_RESOURCES + i; + if (res->start != 0 && res->end != 0) + release_resource(res); + res->start = res->end = 0; + } +} + + +/* + * Close it down - release our resources and go home.. + */ +static void yenta_close(struct pci_dev *dev) +{ + struct yenta_socket *sock = pci_get_drvdata(dev); + + /* we don't want a dying socket registered */ + pcmcia_unregister_socket(&sock->socket); + + /* Disable all events so we don't die in an IRQ storm */ + cb_writel(sock, CB_SOCKET_MASK, 0x0); + exca_writeb(sock, I365_CSCINT, 0); + + if (sock->cb_irq) + free_irq(sock->cb_irq, sock); + else + del_timer_sync(&sock->poll_timer); + + if (sock->base) + iounmap(sock->base); + yenta_free_resources(sock); + + pci_release_regions(dev); + pci_disable_device(dev); + pci_set_drvdata(dev, NULL); +} + + +static struct pccard_operations yenta_socket_operations = { + .init = yenta_sock_init, + .suspend = yenta_sock_suspend, + .get_status = yenta_get_status, + .get_socket = yenta_get_socket, + .set_socket = yenta_set_socket, + .set_io_map = yenta_set_io_map, + .set_mem_map = yenta_set_mem_map, +}; + + +#include "ti113x.h" +#include "ricoh.h" +#include "topic.h" +#include "o2micro.h" + +enum { + CARDBUS_TYPE_DEFAULT = -1, + CARDBUS_TYPE_TI, + CARDBUS_TYPE_TI113X, + CARDBUS_TYPE_TI12XX, + CARDBUS_TYPE_TI1250, + CARDBUS_TYPE_RICOH, + CARDBUS_TYPE_TOPIC97, + CARDBUS_TYPE_O2MICRO, +}; + +/* + * Different cardbus controllers have slightly different + * initialization sequences etc details. List them here.. + */ +static struct cardbus_type cardbus_type[] = { + [CARDBUS_TYPE_TI] = { + .override = ti_override, + .save_state = ti_save_state, + .restore_state = ti_restore_state, + .sock_init = ti_init, + }, + [CARDBUS_TYPE_TI113X] = { + .override = ti113x_override, + .save_state = ti_save_state, + .restore_state = ti_restore_state, + .sock_init = ti_init, + }, + [CARDBUS_TYPE_TI12XX] = { + .override = ti12xx_override, + .save_state = ti_save_state, + .restore_state = ti_restore_state, + .sock_init = ti_init, + }, + [CARDBUS_TYPE_TI1250] = { + .override = ti1250_override, + .save_state = ti_save_state, + .restore_state = ti_restore_state, + .sock_init = ti_init, + }, + [CARDBUS_TYPE_RICOH] = { + .override = ricoh_override, + .save_state = ricoh_save_state, + .restore_state = ricoh_restore_state, + }, + [CARDBUS_TYPE_TOPIC97] = { + .override = topic97_override, + }, + [CARDBUS_TYPE_O2MICRO] = { + .override = o2micro_override, + .restore_state = o2micro_restore_state, + }, +}; + + +/* + * Only probe "regular" interrupts, don't + * touch dangerous spots like the mouse irq, + * because there are mice that apparently + * get really confused if they get fondled + * too intimately. + * + * Default to 11, 10, 9, 7, 6, 5, 4, 3. + */ +static u32 isa_interrupts = 0x0ef8; + +static unsigned int yenta_probe_irq(struct yenta_socket *socket, u32 isa_irq_mask) +{ + int i; + unsigned long val; + u16 bridge_ctrl; + u32 mask; + + /* Set up ISA irq routing to probe the ISA irqs.. */ + bridge_ctrl = config_readw(socket, CB_BRIDGE_CONTROL); + if (!(bridge_ctrl & CB_BRIDGE_INTR)) { + bridge_ctrl |= CB_BRIDGE_INTR; + config_writew(socket, CB_BRIDGE_CONTROL, bridge_ctrl); + } + + /* + * Probe for usable interrupts using the force + * register to generate bogus card status events. + */ + cb_writel(socket, CB_SOCKET_EVENT, -1); + cb_writel(socket, CB_SOCKET_MASK, CB_CSTSMASK); + exca_writeb(socket, I365_CSCINT, 0); + val = probe_irq_on() & isa_irq_mask; + for (i = 1; i < 16; i++) { + if (!((val >> i) & 1)) + continue; + exca_writeb(socket, I365_CSCINT, I365_CSC_STSCHG | (i << 4)); + cb_writel(socket, CB_SOCKET_FORCE, CB_FCARDSTS); + udelay(100); + cb_writel(socket, CB_SOCKET_EVENT, -1); + } + cb_writel(socket, CB_SOCKET_MASK, 0); + exca_writeb(socket, I365_CSCINT, 0); + + mask = probe_irq_mask(val) & 0xffff; + + bridge_ctrl &= ~CB_BRIDGE_INTR; + config_writew(socket, CB_BRIDGE_CONTROL, bridge_ctrl); + + return mask; +} + + +/* interrupt handler, only used during probing */ +static irqreturn_t yenta_probe_handler(int irq, void *dev_id, struct pt_regs *regs) +{ + struct yenta_socket *socket = (struct yenta_socket *) dev_id; + u8 csc; + u32 cb_event; + + /* Clear interrupt status for the event */ + cb_event = cb_readl(socket, CB_SOCKET_EVENT); + cb_writel(socket, CB_SOCKET_EVENT, -1); + csc = exca_readb(socket, I365_CSC); + + if (cb_event || csc) { + socket->probe_status = 1; + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +/* probes the PCI interrupt, use only on override functions */ +static int yenta_probe_cb_irq(struct yenta_socket *socket) +{ + u16 bridge_ctrl; + + if (!socket->cb_irq) + return -1; + + socket->probe_status = 0; + + /* disable ISA interrupts */ + bridge_ctrl = config_readw(socket, CB_BRIDGE_CONTROL); + bridge_ctrl &= ~CB_BRIDGE_INTR; + config_writew(socket, CB_BRIDGE_CONTROL, bridge_ctrl); + + if (request_irq(socket->cb_irq, yenta_probe_handler, SA_SHIRQ, "yenta", socket)) { + printk(KERN_WARNING "Yenta: request_irq() in yenta_probe_cb_irq() failed!\n"); + return -1; + } + + /* generate interrupt, wait */ + exca_writeb(socket, I365_CSCINT, I365_CSC_STSCHG); + cb_writel(socket, CB_SOCKET_EVENT, -1); + cb_writel(socket, CB_SOCKET_MASK, CB_CSTSMASK); + cb_writel(socket, CB_SOCKET_FORCE, CB_FCARDSTS); + + msleep(100); + + /* disable interrupts */ + cb_writel(socket, CB_SOCKET_MASK, 0); + exca_writeb(socket, I365_CSCINT, 0); + cb_writel(socket, CB_SOCKET_EVENT, -1); + exca_readb(socket, I365_CSC); + + free_irq(socket->cb_irq, socket); + + return (int) socket->probe_status; +} + + + +/* + * Set static data that doesn't need re-initializing.. + */ +static void yenta_get_socket_capabilities(struct yenta_socket *socket, u32 isa_irq_mask) +{ + socket->socket.features |= SS_CAP_PAGE_REGS | SS_CAP_PCCARD | SS_CAP_CARDBUS; + socket->socket.map_size = 0x1000; + socket->socket.pci_irq = socket->cb_irq; + socket->socket.irq_mask = yenta_probe_irq(socket, isa_irq_mask); + socket->socket.cb_dev = socket->dev; + + printk(KERN_INFO "Yenta: ISA IRQ mask 0x%04x, PCI irq %d\n", + socket->socket.irq_mask, socket->cb_irq); +} + +/* + * Initialize the standard cardbus registers + */ +static void yenta_config_init(struct yenta_socket *socket) +{ + u16 bridge; + struct pci_dev *dev = socket->dev; + + pci_set_power_state(socket->dev, 0); + + config_writel(socket, CB_LEGACY_MODE_BASE, 0); + config_writel(socket, PCI_BASE_ADDRESS_0, dev->resource[0].start); + config_writew(socket, PCI_COMMAND, + PCI_COMMAND_IO | + PCI_COMMAND_MEMORY | + PCI_COMMAND_MASTER | + PCI_COMMAND_WAIT); + + /* MAGIC NUMBERS! Fixme */ + config_writeb(socket, PCI_CACHE_LINE_SIZE, L1_CACHE_BYTES / 4); + config_writeb(socket, PCI_LATENCY_TIMER, 168); + config_writel(socket, PCI_PRIMARY_BUS, + (176 << 24) | /* sec. latency timer */ + (dev->subordinate->subordinate << 16) | /* subordinate bus */ + (dev->subordinate->secondary << 8) | /* secondary bus */ + dev->subordinate->primary); /* primary bus */ + + /* + * Set up the bridging state: + * - enable write posting. + * - memory window 0 prefetchable, window 1 non-prefetchable + * - PCI interrupts enabled if a PCI interrupt exists.. + */ + bridge = config_readw(socket, CB_BRIDGE_CONTROL); + bridge &= ~(CB_BRIDGE_CRST | CB_BRIDGE_PREFETCH1 | CB_BRIDGE_INTR | CB_BRIDGE_ISAEN | CB_BRIDGE_VGAEN); + bridge |= CB_BRIDGE_PREFETCH0 | CB_BRIDGE_POSTEN | CB_BRIDGE_INTR; + config_writew(socket, CB_BRIDGE_CONTROL, bridge); +} + +/* + * Initialize a cardbus controller. Make sure we have a usable + * interrupt, and that we can map the cardbus area. Fill in the + * socket information structure.. + */ +static int __devinit yenta_probe (struct pci_dev *dev, const struct pci_device_id *id) +{ + struct yenta_socket *socket; + int ret; + + socket = kmalloc(sizeof(struct yenta_socket), GFP_KERNEL); + if (!socket) + return -ENOMEM; + memset(socket, 0, sizeof(*socket)); + + /* prepare pcmcia_socket */ + socket->socket.ops = ¥ta_socket_operations; + socket->socket.resource_ops = &pccard_nonstatic_ops; + socket->socket.dev.dev = &dev->dev; + socket->socket.driver_data = socket; + socket->socket.owner = THIS_MODULE; + + /* prepare struct yenta_socket */ + socket->dev = dev; + pci_set_drvdata(dev, socket); + + /* + * Do some basic sanity checking.. + */ + if (pci_enable_device(dev)) { + ret = -EBUSY; + goto free; + } + + ret = pci_request_regions(dev, "yenta_socket"); + if (ret) + goto disable; + + if (!pci_resource_start(dev, 0)) { + printk(KERN_ERR "No cardbus resource!\n"); + ret = -ENODEV; + goto release; + } + + /* + * Ok, start setup.. Map the cardbus registers, + * and request the IRQ. + */ + socket->base = ioremap(pci_resource_start(dev, 0), 0x1000); + if (!socket->base) { + ret = -ENOMEM; + goto release; + } + + /* + * report the subsystem vendor and device for help debugging + * the irq stuff... + */ + printk(KERN_INFO "Yenta: CardBus bridge found at %s [%04x:%04x]\n", + pci_name(dev), dev->subsystem_vendor, dev->subsystem_device); + + yenta_config_init(socket); + + /* Disable all events */ + cb_writel(socket, CB_SOCKET_MASK, 0x0); + + /* Set up the bridge regions.. */ + yenta_allocate_resources(socket); + + socket->cb_irq = dev->irq; + + /* Do we have special options for the device? */ + if (id->driver_data != CARDBUS_TYPE_DEFAULT && + id->driver_data < ARRAY_SIZE(cardbus_type)) { + socket->type = &cardbus_type[id->driver_data]; + + ret = socket->type->override(socket); + if (ret < 0) + goto unmap; + } + + /* We must finish initialization here */ + + if (!socket->cb_irq || request_irq(socket->cb_irq, yenta_interrupt, SA_SHIRQ, "yenta", socket)) { + /* No IRQ or request_irq failed. Poll */ + socket->cb_irq = 0; /* But zero is a valid IRQ number. */ + init_timer(&socket->poll_timer); + socket->poll_timer.function = yenta_interrupt_wrapper; + socket->poll_timer.data = (unsigned long)socket; + socket->poll_timer.expires = jiffies + HZ; + add_timer(&socket->poll_timer); + } + + /* Figure out what the dang thing can do for the PCMCIA layer... */ + yenta_get_socket_capabilities(socket, isa_interrupts); + printk(KERN_INFO "Socket status: %08x\n", cb_readl(socket, CB_SOCKET_STATE)); + + /* Register it with the pcmcia layer.. */ + ret = pcmcia_register_socket(&socket->socket); + if (ret == 0) + goto out; + + unmap: + iounmap(socket->base); + release: + pci_release_regions(dev); + disable: + pci_disable_device(dev); + free: + kfree(socket); + out: + return ret; +} + + +static int yenta_dev_suspend (struct pci_dev *dev, pm_message_t state) +{ + struct yenta_socket *socket = pci_get_drvdata(dev); + int ret; + + ret = pcmcia_socket_dev_suspend(&dev->dev, state); + + if (socket) { + if (socket->type && socket->type->save_state) + socket->type->save_state(socket); + + /* FIXME: pci_save_state needs to have a better interface */ + pci_save_state(dev); + pci_read_config_dword(dev, 16*4, &socket->saved_state[0]); + pci_read_config_dword(dev, 17*4, &socket->saved_state[1]); + + /* + * Some laptops (IBM T22) do not like us putting the Cardbus + * bridge into D3. At a guess, some other laptop will + * probably require this, so leave it commented out for now. + */ + /* pci_set_power_state(dev, 3); */ + } + + return ret; +} + + +static int yenta_dev_resume (struct pci_dev *dev) +{ + struct yenta_socket *socket = pci_get_drvdata(dev); + + if (socket) { + pci_set_power_state(dev, 0); + /* FIXME: pci_restore_state needs to have a better interface */ + pci_restore_state(dev); + pci_write_config_dword(dev, 16*4, socket->saved_state[0]); + pci_write_config_dword(dev, 17*4, socket->saved_state[1]); + + if (socket->type && socket->type->restore_state) + socket->type->restore_state(socket); + } + + return pcmcia_socket_dev_resume(&dev->dev); +} + + +#define CB_ID(vend,dev,type) \ + { \ + .vendor = vend, \ + .device = dev, \ + .subvendor = PCI_ANY_ID, \ + .subdevice = PCI_ANY_ID, \ + .class = PCI_CLASS_BRIDGE_CARDBUS << 8, \ + .class_mask = ~0, \ + .driver_data = CARDBUS_TYPE_##type, \ + } + +static struct pci_device_id yenta_table [] = { + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1031, TI), + + /* + * TBD: Check if these TI variants can use more + * advanced overrides instead. (I can't get the + * data sheets for these devices. --rmk) + */ + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1210, TI), + + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1130, TI113X), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1131, TI113X), + + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1211, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1220, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1221, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1225, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1251A, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1251B, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1420, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1450, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1451A, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1510, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1520, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1620, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_4410, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_4450, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_4451, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_4510, TI12XX), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_4520, TI12XX), + + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1250, TI1250), + CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1410, TI1250), + + CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_1211, TI12XX), + CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_1225, TI12XX), + CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_1410, TI1250), + CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_1420, TI12XX), + + CB_ID(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C465, RICOH), + CB_ID(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C466, RICOH), + CB_ID(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C475, RICOH), + CB_ID(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C476, RICOH), + CB_ID(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C478, RICOH), + + CB_ID(PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_TOSHIBA_TOPIC97, TOPIC97), + CB_ID(PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_TOSHIBA_TOPIC100, TOPIC97), + + CB_ID(PCI_VENDOR_ID_O2, PCI_ANY_ID, O2MICRO), + + /* match any cardbus bridge */ + CB_ID(PCI_ANY_ID, PCI_ANY_ID, DEFAULT), + { /* all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, yenta_table); + + +static struct pci_driver yenta_cardbus_driver = { + .name = "yenta_cardbus", + .id_table = yenta_table, + .probe = yenta_probe, + .remove = __devexit_p(yenta_close), + .suspend = yenta_dev_suspend, + .resume = yenta_dev_resume, +}; + + +static int __init yenta_socket_init(void) +{ + return pci_register_driver (¥ta_cardbus_driver); +} + + +static void __exit yenta_socket_exit (void) +{ + pci_unregister_driver (¥ta_cardbus_driver); +} + + +module_init(yenta_socket_init); +module_exit(yenta_socket_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/pcmcia/yenta_socket.h b/drivers/pcmcia/yenta_socket.h new file mode 100644 index 000000000000..4e637eef2076 --- /dev/null +++ b/drivers/pcmcia/yenta_socket.h @@ -0,0 +1,127 @@ +#ifndef __YENTA_H +#define __YENTA_H + +#include <asm/io.h> + +#define CB_SOCKET_EVENT 0x00 +#define CB_CSTSEVENT 0x00000001 /* Card status event */ +#define CB_CD1EVENT 0x00000002 /* Card detect 1 change event */ +#define CB_CD2EVENT 0x00000004 /* Card detect 2 change event */ +#define CB_PWREVENT 0x00000008 /* PWRCYCLE change event */ + +#define CB_SOCKET_MASK 0x04 +#define CB_CSTSMASK 0x00000001 /* Card status mask */ +#define CB_CDMASK 0x00000006 /* Card detect 1&2 mask */ +#define CB_PWRMASK 0x00000008 /* PWRCYCLE change mask */ + +#define CB_SOCKET_STATE 0x08 +#define CB_CARDSTS 0x00000001 /* CSTSCHG status */ +#define CB_CDETECT1 0x00000002 /* Card detect status 1 */ +#define CB_CDETECT2 0x00000004 /* Card detect status 2 */ +#define CB_PWRCYCLE 0x00000008 /* Socket powered */ +#define CB_16BITCARD 0x00000010 /* 16-bit card detected */ +#define CB_CBCARD 0x00000020 /* CardBus card detected */ +#define CB_IREQCINT 0x00000040 /* READY(xIRQ)/xCINT high */ +#define CB_NOTACARD 0x00000080 /* Unrecognizable PC card detected */ +#define CB_DATALOST 0x00000100 /* Potential data loss due to card removal */ +#define CB_BADVCCREQ 0x00000200 /* Invalid Vcc request by host software */ +#define CB_5VCARD 0x00000400 /* Card Vcc at 5.0 volts? */ +#define CB_3VCARD 0x00000800 /* Card Vcc at 3.3 volts? */ +#define CB_XVCARD 0x00001000 /* Card Vcc at X.X volts? */ +#define CB_YVCARD 0x00002000 /* Card Vcc at Y.Y volts? */ +#define CB_5VSOCKET 0x10000000 /* Socket Vcc at 5.0 volts? */ +#define CB_3VSOCKET 0x20000000 /* Socket Vcc at 3.3 volts? */ +#define CB_XVSOCKET 0x40000000 /* Socket Vcc at X.X volts? */ +#define CB_YVSOCKET 0x80000000 /* Socket Vcc at Y.Y volts? */ + +#define CB_SOCKET_FORCE 0x0C +#define CB_FCARDSTS 0x00000001 /* Force CSTSCHG */ +#define CB_FCDETECT1 0x00000002 /* Force CD1EVENT */ +#define CB_FCDETECT2 0x00000004 /* Force CD2EVENT */ +#define CB_FPWRCYCLE 0x00000008 /* Force PWREVENT */ +#define CB_F16BITCARD 0x00000010 /* Force 16-bit PCMCIA card */ +#define CB_FCBCARD 0x00000020 /* Force CardBus line */ +#define CB_FNOTACARD 0x00000080 /* Force NOTACARD */ +#define CB_FDATALOST 0x00000100 /* Force data lost */ +#define CB_FBADVCCREQ 0x00000200 /* Force bad Vcc request */ +#define CB_F5VCARD 0x00000400 /* Force 5.0 volt card */ +#define CB_F3VCARD 0x00000800 /* Force 3.3 volt card */ +#define CB_FXVCARD 0x00001000 /* Force X.X volt card */ +#define CB_FYVCARD 0x00002000 /* Force Y.Y volt card */ +#define CB_CVSTEST 0x00004000 /* Card VS test */ + +#define CB_SOCKET_CONTROL 0x10 +#define CB_SC_VPP_MASK 0x00000007 +#define CB_SC_VPP_OFF 0x00000000 +#define CB_SC_VPP_12V 0x00000001 +#define CB_SC_VPP_5V 0x00000002 +#define CB_SC_VPP_3V 0x00000003 +#define CB_SC_VPP_XV 0x00000004 +#define CB_SC_VPP_YV 0x00000005 +#define CB_SC_VCC_MASK 0x00000070 +#define CB_SC_VCC_OFF 0x00000000 +#define CB_SC_VCC_5V 0x00000020 +#define CB_SC_VCC_3V 0x00000030 +#define CB_SC_VCC_XV 0x00000040 +#define CB_SC_VCC_YV 0x00000050 +#define CB_SC_CCLK_STOP 0x00000080 + +#define CB_SOCKET_POWER 0x20 +#define CB_SKTACCES 0x02000000 /* A PC card access has occurred (clear on read) */ +#define CB_SKTMODE 0x01000000 /* Clock frequency has changed (clear on read) */ +#define CB_CLKCTRLEN 0x00010000 /* Clock control enabled (RW) */ +#define CB_CLKCTRL 0x00000001 /* Stop(0) or slow(1) CB clock (RW) */ + +/* + * Cardbus configuration space + */ +#define CB_BRIDGE_BASE(m) (0x1c + 8*(m)) +#define CB_BRIDGE_LIMIT(m) (0x20 + 8*(m)) +#define CB_BRIDGE_CONTROL 0x3e +#define CB_BRIDGE_CPERREN 0x00000001 +#define CB_BRIDGE_CSERREN 0x00000002 +#define CB_BRIDGE_ISAEN 0x00000004 +#define CB_BRIDGE_VGAEN 0x00000008 +#define CB_BRIDGE_MABTMODE 0x00000020 +#define CB_BRIDGE_CRST 0x00000040 +#define CB_BRIDGE_INTR 0x00000080 +#define CB_BRIDGE_PREFETCH0 0x00000100 +#define CB_BRIDGE_PREFETCH1 0x00000200 +#define CB_BRIDGE_POSTEN 0x00000400 +#define CB_LEGACY_MODE_BASE 0x44 + +/* + * ExCA area extensions in Yenta + */ +#define CB_MEM_PAGE(map) (0x40 + (map)) + +struct yenta_socket; + +struct cardbus_type { + int (*override)(struct yenta_socket *); + void (*save_state)(struct yenta_socket *); + void (*restore_state)(struct yenta_socket *); + int (*sock_init)(struct yenta_socket *); +}; + +struct yenta_socket { + struct pci_dev *dev; + int cb_irq, io_irq; + void __iomem *base; + struct timer_list poll_timer; + + struct pcmcia_socket socket; + struct cardbus_type *type; + + /* for PCI interrupt probing */ + unsigned int probe_status; + + /* A few words of private data for special stuff of overrides... */ + unsigned int private[8]; + + /* PCI saved state */ + u32 saved_state[2]; +}; + + +#endif |