summaryrefslogtreecommitdiffstats
path: root/drivers/pcmcia
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:05:51 +0000
commit5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch)
treea94efe259b9009378be6d90eb30d2b019d95c194 /drivers/pcmcia
parentInitial commit. (diff)
downloadlinux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz
linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--drivers/pcmcia/Kconfig279
-rw-r--r--drivers/pcmcia/Makefile71
-rw-r--r--drivers/pcmcia/at91_cf.c415
-rw-r--r--drivers/pcmcia/bcm63xx_pcmcia.c537
-rw-r--r--drivers/pcmcia/bcm63xx_pcmcia.h61
-rw-r--r--drivers/pcmcia/cardbus.c124
-rw-r--r--drivers/pcmcia/cirrus.h147
-rw-r--r--drivers/pcmcia/cistpl.c1610
-rw-r--r--drivers/pcmcia/cs.c919
-rw-r--r--drivers/pcmcia/cs_internal.h175
-rw-r--r--drivers/pcmcia/db1xxx_ss.c603
-rw-r--r--drivers/pcmcia/ds.c1461
-rw-r--r--drivers/pcmcia/electra_cf.c352
-rw-r--r--drivers/pcmcia/i82092.c678
-rw-r--r--drivers/pcmcia/i82092aa.h24
-rw-r--r--drivers/pcmcia/i82365.c1346
-rw-r--r--drivers/pcmcia/i82365.h136
-rw-r--r--drivers/pcmcia/max1600.c122
-rw-r--r--drivers/pcmcia/max1600.h32
-rw-r--r--drivers/pcmcia/o2micro.h183
-rw-r--r--drivers/pcmcia/omap_cf.c355
-rw-r--r--drivers/pcmcia/pcmcia_cis.c434
-rw-r--r--drivers/pcmcia/pcmcia_resource.c957
-rw-r--r--drivers/pcmcia/pd6729.c777
-rw-r--r--drivers/pcmcia/pd6729.h24
-rw-r--r--drivers/pcmcia/pxa2xx_balloon3.c137
-rw-r--r--drivers/pcmcia/pxa2xx_base.c384
-rw-r--r--drivers/pcmcia/pxa2xx_base.h4
-rw-r--r--drivers/pcmcia/pxa2xx_cm_x255.c124
-rw-r--r--drivers/pcmcia/pxa2xx_cm_x270.c103
-rw-r--r--drivers/pcmcia/pxa2xx_cm_x2xx.c44
-rw-r--r--drivers/pcmcia/pxa2xx_colibri.c165
-rw-r--r--drivers/pcmcia/pxa2xx_e740.c127
-rw-r--r--drivers/pcmcia/pxa2xx_hx4700.c118
-rw-r--r--drivers/pcmcia/pxa2xx_mainstone.c122
-rw-r--r--drivers/pcmcia/pxa2xx_palmld.c110
-rw-r--r--drivers/pcmcia/pxa2xx_palmtc.c162
-rw-r--r--drivers/pcmcia/pxa2xx_palmtx.c111
-rw-r--r--drivers/pcmcia/pxa2xx_sharpsl.c258
-rw-r--r--drivers/pcmcia/pxa2xx_stargate2.c137
-rw-r--r--drivers/pcmcia/pxa2xx_trizeps4.c200
-rw-r--r--drivers/pcmcia/pxa2xx_viper.c182
-rw-r--r--drivers/pcmcia/pxa2xx_vpac270.c137
-rw-r--r--drivers/pcmcia/ricoh.h241
-rw-r--r--drivers/pcmcia/rsrc_iodyn.c168
-rw-r--r--drivers/pcmcia/rsrc_mgr.c70
-rw-r--r--drivers/pcmcia/rsrc_nonstatic.c1242
-rw-r--r--drivers/pcmcia/sa1100_generic.c216
-rw-r--r--drivers/pcmcia/sa1100_generic.h22
-rw-r--r--drivers/pcmcia/sa1100_h3600.c163
-rw-r--r--drivers/pcmcia/sa1100_simpad.c115
-rw-r--r--drivers/pcmcia/sa1111_badge4.c158
-rw-r--r--drivers/pcmcia/sa1111_generic.c281
-rw-r--r--drivers/pcmcia/sa1111_generic.h26
-rw-r--r--drivers/pcmcia/sa1111_jornada720.c138
-rw-r--r--drivers/pcmcia/sa1111_lubbock.c156
-rw-r--r--drivers/pcmcia/sa1111_neponset.c81
-rw-r--r--drivers/pcmcia/sa11xx_base.c263
-rw-r--r--drivers/pcmcia/sa11xx_base.h125
-rw-r--r--drivers/pcmcia/soc_common.c894
-rw-r--r--drivers/pcmcia/soc_common.h218
-rw-r--r--drivers/pcmcia/socket_sysfs.c228
-rw-r--r--drivers/pcmcia/tcic.c805
-rw-r--r--drivers/pcmcia/tcic.h266
-rw-r--r--drivers/pcmcia/ti113x.h978
-rw-r--r--drivers/pcmcia/topic.h168
-rw-r--r--drivers/pcmcia/vg468.h106
-rw-r--r--drivers/pcmcia/vrc4171_card.c745
-rw-r--r--drivers/pcmcia/vrc4173_cardu.c591
-rw-r--r--drivers/pcmcia/vrc4173_cardu.h247
-rw-r--r--drivers/pcmcia/xxs1500_ss.c328
-rw-r--r--drivers/pcmcia/yenta_socket.c1458
-rw-r--r--drivers/pcmcia/yenta_socket.h136
73 files changed, 25080 insertions, 0 deletions
diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig
new file mode 100644
index 000000000..73508fca5
--- /dev/null
+++ b/drivers/pcmcia/Kconfig
@@ -0,0 +1,279 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# PCCARD (PCMCIA/CardBus) bus subsystem configuration
+#
+
+menuconfig PCCARD
+ tristate "PCCard (PCMCIA/CardBus) support"
+ depends on !UML
+ 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: 16 bit PCMCIA and 32 bit
+ CardBus cards.
+
+ To compile this driver as modules, choose M here: the
+ module will be called pcmcia_core.
+
+if PCCARD
+
+config PCMCIA
+ tristate "16-bit PCMCIA support"
+ select CRC32
+ 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 in
+ most cases. (see the file <file:Documentation/Changes> for
+ location and details).
+
+ To compile this driver as modules, choose M here: the
+ module will be called pcmcia.
+
+ If unsure, say Y.
+
+config PCMCIA_LOAD_CIS
+ bool "Load CIS updates from userspace"
+ depends on PCMCIA
+ select FW_LOADER
+ default y
+ help
+ Some PCMCIA cards require an updated Card Information Structure (CIS)
+ to be loaded from userspace to work correctly. If you say Y here,
+ and your userspace is arranged correctly, this will be loaded
+ automatically using the in-kernel firmware loader and the hotplug
+ subsystem, instead of relying on cardmgr from pcmcia-cs to do so.
+
+ 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.
+
+config PCMCIA_MAX1600
+ tristate
+
+comment "PC-card bridges"
+
+config YENTA
+ tristate "CardBus yenta-compatible bridge support"
+ depends on PCI
+ select CARDBUS if !EXPERT
+ select PCCARD_NONSTATIC if PCMCIA != n
+ 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 YENTA_O2
+ default y
+ bool "Special initialization for O2Micro bridges" if EXPERT
+ depends on YENTA
+
+config YENTA_RICOH
+ default y
+ bool "Special initialization for Ricoh bridges" if EXPERT
+ depends on YENTA
+
+config YENTA_TI
+ default y
+ bool "Special initialization for TI and EnE bridges" if EXPERT
+ depends on YENTA
+
+config YENTA_ENE_TUNE
+ default y
+ bool "Auto-tune EnE bridges for CB cards" if EXPERT
+ depends on YENTA_TI && CARDBUS
+
+config YENTA_TOSHIBA
+ default y
+ bool "Special initialization for Toshiba ToPIC bridges" if EXPERT
+ depends on YENTA
+
+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 && ISA
+ 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 PCMCIA_ALCHEMY_DEVBOARD
+ tristate "Alchemy Db/Pb1xxx PCMCIA socket services"
+ depends on MIPS_DB1XXX && PCMCIA
+ help
+ Enable this driver of you want PCMCIA support on your Alchemy
+ Db1000, Db/Pb1100, Db/Pb1500, Db/Pb1550, Db/Pb1200, DB1300
+ board. NOT suitable for the PB1000!
+
+ This driver is also available as a module called db1xxx_ss.ko
+
+config PCMCIA_XXS1500
+ tristate "MyCable XXS1500 PCMCIA socket support"
+ depends on PCMCIA && MIPS_XXS1500
+ help
+ Support for the PCMCIA/CF socket interface on MyCable XXS1500
+ systems.
+
+ This driver is also available as a module called xxs1500_ss.ko
+
+config PCMCIA_BCM63XX
+ tristate "bcm63xx pcmcia support"
+ depends on BCM63XX && PCMCIA
+
+config PCMCIA_SOC_COMMON
+ tristate
+
+config PCMCIA_SA11XX_BASE
+ tristate
+
+config PCMCIA_SA1100
+ tristate "SA1100 support"
+ depends on ARM && ARCH_SA1100 && PCMCIA
+ select PCMCIA_SOC_COMMON
+ select PCMCIA_SA11XX_BASE
+ 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 && SA1111 && PCMCIA
+ select PCMCIA_SOC_COMMON
+ select PCMCIA_SA11XX_BASE if ARCH_SA1100
+ select PCMCIA_PXA2XX if ARCH_LUBBOCK && SA1111
+ select PCMCIA_MAX1600 if ASSABET_NEPONSET
+ select PCMCIA_MAX1600 if ARCH_LUBBOCK && SA1111
+ 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
+ depends on (ARCH_LUBBOCK || MACH_MAINSTONE || PXA_SHARPSL \
+ || MACH_ARMCORE || ARCH_PXA_PALM || TRIZEPS_PCMCIA \
+ || ARCOM_PCMCIA || ARCH_PXA_ESERIES || MACH_STARGATE2 \
+ || MACH_VPAC270 || MACH_BALLOON3 || MACH_COLIBRI \
+ || MACH_COLIBRI320 || MACH_H4700)
+ select PCMCIA_SOC_COMMON
+ select PCMCIA_MAX1600 if MACH_MAINSTONE
+ help
+ Say Y here to include support for the PXA2xx PCMCIA controller
+
+config PCMCIA_DEBUG
+ bool "Enable debugging"
+ depends on (PCMCIA_SA1111 || PCMCIA_SA1100 || PCMCIA_PXA2XX)
+ help
+ Say Y here to enable debugging for the SoC PCMCIA layer.
+ You will need to choose the debugging level either via the
+ kernel command line, or module options depending whether
+ you build the drivers as modules.
+
+ The kernel command line options are:
+ sa11xx_core.pc_debug=N
+ pxa2xx_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_PROBE
+ bool
+ default y if ISA && !ARCH_SA1100 && !PARISC
+
+config PCMCIA_VRC4171
+ tristate "NEC VRC4171 Card Controllers support"
+ depends on CPU_VR41XX && ISA && PCMCIA
+
+config PCMCIA_VRC4173
+ tristate "NEC VRC4173 CARDU support"
+ depends on CPU_VR41XX && PCI && PCMCIA
+
+config OMAP_CF
+ tristate "OMAP CompactFlash Controller"
+ depends on PCMCIA && ARCH_OMAP16XX
+ help
+ Say Y here to support the CompactFlash controller on OMAP.
+ Note that this doesn't support "True IDE" mode.
+
+config AT91_CF
+ tristate "AT91 CompactFlash Controller"
+ depends on PCI
+ depends on PCMCIA && ARCH_AT91
+ help
+ Say Y here to support the CompactFlash controller on AT91 chips.
+ Or choose M to compile the driver as a module named "at91_cf".
+
+config ELECTRA_CF
+ tristate "Electra CompactFlash Controller"
+ depends on PCMCIA && PPC_PASEMI
+ help
+ Say Y here to support the CompactFlash controller on the
+ PA Semi Electra eval board.
+
+config PCCARD_NONSTATIC
+ bool
+
+config PCCARD_IODYN
+ bool
+
+endif # PCCARD
diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile
new file mode 100644
index 000000000..01779c5c4
--- /dev/null
+++ b/drivers/pcmcia/Makefile
@@ -0,0 +1,71 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the kernel pcmcia subsystem (c/o David Hinds)
+#
+
+pcmcia_core-y += cs.o socket_sysfs.o
+pcmcia_core-$(CONFIG_CARDBUS) += cardbus.o
+obj-$(CONFIG_PCCARD) += pcmcia_core.o
+
+pcmcia-y += ds.o pcmcia_resource.o cistpl.o pcmcia_cis.o
+obj-$(CONFIG_PCMCIA) += pcmcia.o
+
+pcmcia_rsrc-y += rsrc_mgr.o
+pcmcia_rsrc-$(CONFIG_PCCARD_NONSTATIC) += rsrc_nonstatic.o
+pcmcia_rsrc-$(CONFIG_PCCARD_IODYN) += rsrc_iodyn.o
+obj-$(CONFIG_PCCARD) += pcmcia_rsrc.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_PCMCIA_SOC_COMMON) += soc_common.o
+obj-$(CONFIG_PCMCIA_SA11XX_BASE) += sa11xx_base.o
+obj-$(CONFIG_PCMCIA_SA1100) += sa1100_cs.o
+obj-$(CONFIG_PCMCIA_SA1111) += sa1111_cs.o
+obj-$(CONFIG_PCMCIA_BCM63XX) += bcm63xx_pcmcia.o
+obj-$(CONFIG_PCMCIA_VRC4171) += vrc4171_card.o
+obj-$(CONFIG_PCMCIA_VRC4173) += vrc4173_cardu.o
+obj-$(CONFIG_OMAP_CF) += omap_cf.o
+obj-$(CONFIG_AT91_CF) += at91_cf.o
+obj-$(CONFIG_ELECTRA_CF) += electra_cf.o
+obj-$(CONFIG_PCMCIA_ALCHEMY_DEVBOARD) += db1xxx_ss.o
+obj-$(CONFIG_PCMCIA_MAX1600) += max1600.o
+
+sa1111_cs-y += sa1111_generic.o
+sa1111_cs-$(CONFIG_ASSABET_NEPONSET) += sa1111_neponset.o
+sa1111_cs-$(CONFIG_SA1100_BADGE4) += sa1111_badge4.o
+sa1111_cs-$(CONFIG_SA1100_JORNADA720) += sa1111_jornada720.o
+sa1111_cs-$(CONFIG_ARCH_LUBBOCK) += sa1111_lubbock.o
+
+sa1100_cs-y += sa1100_generic.o
+sa1100_cs-$(CONFIG_SA1100_COLLIE) += pxa2xx_sharpsl.o
+sa1100_cs-$(CONFIG_SA1100_H3100) += sa1100_h3600.o
+sa1100_cs-$(CONFIG_SA1100_H3600) += sa1100_h3600.o
+sa1100_cs-$(CONFIG_SA1100_SIMPAD) += sa1100_simpad.o
+
+pxa2xx_cm_x2xx_cs-y += pxa2xx_cm_x2xx.o pxa2xx_cm_x255.o pxa2xx_cm_x270.o
+pxa2xx-obj-$(CONFIG_MACH_MAINSTONE) += pxa2xx_mainstone.o
+pxa2xx-obj-$(CONFIG_PXA_SHARPSL) += pxa2xx_sharpsl.o
+pxa2xx-obj-$(CONFIG_MACH_ARMCORE) += pxa2xx_cm_x2xx_cs.o
+pxa2xx-obj-$(CONFIG_ARCOM_PCMCIA) += pxa2xx_viper.o
+pxa2xx-obj-$(CONFIG_TRIZEPS_PCMCIA) += pxa2xx_trizeps4.o
+pxa2xx-obj-$(CONFIG_MACH_PALMTX) += pxa2xx_palmtx.o
+pxa2xx-obj-$(CONFIG_MACH_PALMTC) += pxa2xx_palmtc.o
+pxa2xx-obj-$(CONFIG_MACH_PALMLD) += pxa2xx_palmld.o
+pxa2xx-obj-$(CONFIG_MACH_E740) += pxa2xx_e740.o
+pxa2xx-obj-$(CONFIG_MACH_STARGATE2) += pxa2xx_stargate2.o
+pxa2xx-obj-$(CONFIG_MACH_VPAC270) += pxa2xx_vpac270.o
+pxa2xx-obj-$(CONFIG_MACH_BALLOON3) += pxa2xx_balloon3.o
+pxa2xx-obj-$(CONFIG_MACH_COLIBRI) += pxa2xx_colibri.o
+pxa2xx-obj-$(CONFIG_MACH_COLIBRI320) += pxa2xx_colibri.o
+pxa2xx-obj-$(CONFIG_MACH_H4700) += pxa2xx_hx4700.o
+
+obj-$(CONFIG_PCMCIA_PXA2XX) += pxa2xx_base.o $(pxa2xx-obj-y)
+
+obj-$(CONFIG_PCMCIA_XXS1500) += xxs1500_ss.o
diff --git a/drivers/pcmcia/at91_cf.c b/drivers/pcmcia/at91_cf.c
new file mode 100644
index 000000000..7db0e9c74
--- /dev/null
+++ b/drivers/pcmcia/at91_cf.c
@@ -0,0 +1,415 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * at91_cf.c -- AT91 CompactFlash controller driver
+ *
+ * Copyright (C) 2005 David Brownell
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/platform_data/atmel.h>
+#include <linux/io.h>
+#include <linux/sizes.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/atmel-mc.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+
+#include <pcmcia/ss.h>
+
+/*
+ * A0..A10 work in each range; A23 indicates I/O space; A25 is CFRNW;
+ * some other bit in {A24,A22..A11} is nREG to flag memory access
+ * (vs attributes). So more than 2KB/region would just be waste.
+ * Note: These are offsets from the physical base address.
+ */
+#define CF_ATTR_PHYS (0)
+#define CF_IO_PHYS (1 << 23)
+#define CF_MEM_PHYS (0x017ff800)
+
+struct regmap *mc;
+
+/*--------------------------------------------------------------------------*/
+
+struct at91_cf_socket {
+ struct pcmcia_socket socket;
+
+ unsigned present:1;
+
+ struct platform_device *pdev;
+ struct at91_cf_data *board;
+
+ unsigned long phys_baseaddr;
+};
+
+static inline int at91_cf_present(struct at91_cf_socket *cf)
+{
+ return !gpio_get_value(cf->board->det_pin);
+}
+
+/*--------------------------------------------------------------------------*/
+
+static int at91_cf_ss_init(struct pcmcia_socket *s)
+{
+ return 0;
+}
+
+static irqreturn_t at91_cf_irq(int irq, void *_cf)
+{
+ struct at91_cf_socket *cf = _cf;
+
+ if (irq == gpio_to_irq(cf->board->det_pin)) {
+ unsigned present = at91_cf_present(cf);
+
+ /* kick pccard as needed */
+ if (present != cf->present) {
+ cf->present = present;
+ dev_dbg(&cf->pdev->dev, "card %s\n",
+ present ? "present" : "gone");
+ pcmcia_parse_events(&cf->socket, SS_DETECT);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int at91_cf_get_status(struct pcmcia_socket *s, u_int *sp)
+{
+ struct at91_cf_socket *cf;
+
+ if (!sp)
+ return -EINVAL;
+
+ cf = container_of(s, struct at91_cf_socket, socket);
+
+ /* NOTE: CF is always 3VCARD */
+ if (at91_cf_present(cf)) {
+ int rdy = gpio_is_valid(cf->board->irq_pin); /* RDY/nIRQ */
+ int vcc = gpio_is_valid(cf->board->vcc_pin);
+
+ *sp = SS_DETECT | SS_3VCARD;
+ if (!rdy || gpio_get_value(cf->board->irq_pin))
+ *sp |= SS_READY;
+ if (!vcc || gpio_get_value(cf->board->vcc_pin))
+ *sp |= SS_POWERON;
+ } else
+ *sp = 0;
+
+ return 0;
+}
+
+static int
+at91_cf_set_socket(struct pcmcia_socket *sock, struct socket_state_t *s)
+{
+ struct at91_cf_socket *cf;
+
+ cf = container_of(sock, struct at91_cf_socket, socket);
+
+ /* switch Vcc if needed and possible */
+ if (gpio_is_valid(cf->board->vcc_pin)) {
+ switch (s->Vcc) {
+ case 0:
+ gpio_set_value(cf->board->vcc_pin, 0);
+ break;
+ case 33:
+ gpio_set_value(cf->board->vcc_pin, 1);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ /* toggle reset if needed */
+ gpio_set_value(cf->board->rst_pin, s->flags & SS_RESET);
+
+ dev_dbg(&cf->pdev->dev, "Vcc %d, io_irq %d, flags %04x csc %04x\n",
+ s->Vcc, s->io_irq, s->flags, s->csc_mask);
+
+ return 0;
+}
+
+static int at91_cf_ss_suspend(struct pcmcia_socket *s)
+{
+ return at91_cf_set_socket(s, &dead_socket);
+}
+
+/* we already mapped the I/O region */
+static int at91_cf_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io)
+{
+ struct at91_cf_socket *cf;
+ u32 csr;
+
+ cf = container_of(s, struct at91_cf_socket, socket);
+ io->flags &= (MAP_ACTIVE | MAP_16BIT | MAP_AUTOSZ);
+
+ /*
+ * Use 16 bit accesses unless/until we need 8-bit i/o space.
+ *
+ * NOTE: this CF controller ignores IOIS16, so we can't really do
+ * MAP_AUTOSZ. The 16bit mode allows single byte access on either
+ * D0-D7 (even addr) or D8-D15 (odd), so it's close enough for many
+ * purposes (and handles ide-cs).
+ *
+ * The 8bit mode is needed for odd byte access on D0-D7. It seems
+ * some cards only like that way to get at the odd byte, despite
+ * CF 3.0 spec table 35 also giving the D8-D15 option.
+ */
+ if (!(io->flags & (MAP_16BIT | MAP_AUTOSZ))) {
+ csr = AT91_MC_SMC_DBW_8;
+ dev_dbg(&cf->pdev->dev, "8bit i/o bus\n");
+ } else {
+ csr = AT91_MC_SMC_DBW_16;
+ dev_dbg(&cf->pdev->dev, "16bit i/o bus\n");
+ }
+ regmap_update_bits(mc, AT91_MC_SMC_CSR(cf->board->chipselect),
+ AT91_MC_SMC_DBW, csr);
+
+ io->start = cf->socket.io_offset;
+ io->stop = io->start + SZ_2K - 1;
+
+ return 0;
+}
+
+/* pcmcia layer maps/unmaps mem regions */
+static int
+at91_cf_set_mem_map(struct pcmcia_socket *s, struct pccard_mem_map *map)
+{
+ struct at91_cf_socket *cf;
+
+ if (map->card_start)
+ return -EINVAL;
+
+ cf = container_of(s, struct at91_cf_socket, socket);
+
+ map->flags &= (MAP_ACTIVE | MAP_ATTRIB | MAP_16BIT);
+ if (map->flags & MAP_ATTRIB)
+ map->static_start = cf->phys_baseaddr + CF_ATTR_PHYS;
+ else
+ map->static_start = cf->phys_baseaddr + CF_MEM_PHYS;
+
+ return 0;
+}
+
+static struct pccard_operations at91_cf_ops = {
+ .init = at91_cf_ss_init,
+ .suspend = at91_cf_ss_suspend,
+ .get_status = at91_cf_get_status,
+ .set_socket = at91_cf_set_socket,
+ .set_io_map = at91_cf_set_io_map,
+ .set_mem_map = at91_cf_set_mem_map,
+};
+
+/*--------------------------------------------------------------------------*/
+
+#if defined(CONFIG_OF)
+static const struct of_device_id at91_cf_dt_ids[] = {
+ { .compatible = "atmel,at91rm9200-cf" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, at91_cf_dt_ids);
+
+static int at91_cf_dt_init(struct platform_device *pdev)
+{
+ struct at91_cf_data *board;
+
+ board = devm_kzalloc(&pdev->dev, sizeof(*board), GFP_KERNEL);
+ if (!board)
+ return -ENOMEM;
+
+ board->irq_pin = of_get_gpio(pdev->dev.of_node, 0);
+ board->det_pin = of_get_gpio(pdev->dev.of_node, 1);
+ board->vcc_pin = of_get_gpio(pdev->dev.of_node, 2);
+ board->rst_pin = of_get_gpio(pdev->dev.of_node, 3);
+
+ pdev->dev.platform_data = board;
+
+ mc = syscon_regmap_lookup_by_compatible("atmel,at91rm9200-sdramc");
+
+ return PTR_ERR_OR_ZERO(mc);
+}
+#else
+static int at91_cf_dt_init(struct platform_device *pdev)
+{
+ return -ENODEV;
+}
+#endif
+
+static int at91_cf_probe(struct platform_device *pdev)
+{
+ struct at91_cf_socket *cf;
+ struct at91_cf_data *board = pdev->dev.platform_data;
+ struct resource *io;
+ int status;
+
+ if (!board) {
+ status = at91_cf_dt_init(pdev);
+ if (status)
+ return status;
+
+ board = pdev->dev.platform_data;
+ }
+
+ if (!gpio_is_valid(board->det_pin) || !gpio_is_valid(board->rst_pin))
+ return -ENODEV;
+
+ io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!io)
+ return -ENODEV;
+
+ cf = devm_kzalloc(&pdev->dev, sizeof(*cf), GFP_KERNEL);
+ if (!cf)
+ return -ENOMEM;
+
+ cf->board = board;
+ cf->pdev = pdev;
+ cf->phys_baseaddr = io->start;
+ platform_set_drvdata(pdev, cf);
+
+ /* must be a GPIO; ergo must trigger on both edges */
+ status = devm_gpio_request(&pdev->dev, board->det_pin, "cf_det");
+ if (status < 0)
+ return status;
+
+ status = devm_request_irq(&pdev->dev, gpio_to_irq(board->det_pin),
+ at91_cf_irq, 0, "at91_cf detect", cf);
+ if (status < 0)
+ return status;
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ status = devm_gpio_request(&pdev->dev, board->rst_pin, "cf_rst");
+ if (status < 0)
+ goto fail0a;
+
+ if (gpio_is_valid(board->vcc_pin)) {
+ status = devm_gpio_request(&pdev->dev, board->vcc_pin, "cf_vcc");
+ if (status < 0)
+ goto fail0a;
+ }
+
+ /*
+ * The card driver will request this irq later as needed.
+ * but it causes lots of "irqNN: nobody cared" messages
+ * unless we report that we handle everything (sigh).
+ * (Note: DK board doesn't wire the IRQ pin...)
+ */
+ if (gpio_is_valid(board->irq_pin)) {
+ status = devm_gpio_request(&pdev->dev, board->irq_pin, "cf_irq");
+ if (status < 0)
+ goto fail0a;
+
+ status = devm_request_irq(&pdev->dev, gpio_to_irq(board->irq_pin),
+ at91_cf_irq, IRQF_SHARED, "at91_cf", cf);
+ if (status < 0)
+ goto fail0a;
+ cf->socket.pci_irq = gpio_to_irq(board->irq_pin);
+ } else
+ cf->socket.pci_irq = nr_irqs + 1;
+
+ /*
+ * pcmcia layer only remaps "real" memory not iospace
+ * io_offset is set to 0x10000 to avoid the check in static_find_io().
+ * */
+ cf->socket.io_offset = 0x10000;
+ status = pci_ioremap_io(0x10000, cf->phys_baseaddr + CF_IO_PHYS);
+ if (status)
+ goto fail0a;
+
+ /* reserve chip-select regions */
+ if (!devm_request_mem_region(&pdev->dev, io->start, resource_size(io), "at91_cf")) {
+ status = -ENXIO;
+ goto fail0a;
+ }
+
+ dev_info(&pdev->dev, "irqs det #%d, io #%d\n",
+ gpio_to_irq(board->det_pin), gpio_to_irq(board->irq_pin));
+
+ cf->socket.owner = THIS_MODULE;
+ cf->socket.dev.parent = &pdev->dev;
+ cf->socket.ops = &at91_cf_ops;
+ cf->socket.resource_ops = &pccard_static_ops;
+ cf->socket.features = SS_CAP_PCCARD | SS_CAP_STATIC_MAP
+ | SS_CAP_MEM_ALIGN;
+ cf->socket.map_size = SZ_2K;
+ cf->socket.io[0].res = io;
+
+ status = pcmcia_register_socket(&cf->socket);
+ if (status < 0)
+ goto fail0a;
+
+ return 0;
+
+fail0a:
+ device_init_wakeup(&pdev->dev, 0);
+ return status;
+}
+
+static int at91_cf_remove(struct platform_device *pdev)
+{
+ struct at91_cf_socket *cf = platform_get_drvdata(pdev);
+
+ pcmcia_unregister_socket(&cf->socket);
+ device_init_wakeup(&pdev->dev, 0);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int at91_cf_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+ struct at91_cf_socket *cf = platform_get_drvdata(pdev);
+ struct at91_cf_data *board = cf->board;
+
+ if (device_may_wakeup(&pdev->dev)) {
+ enable_irq_wake(gpio_to_irq(board->det_pin));
+ if (gpio_is_valid(board->irq_pin))
+ enable_irq_wake(gpio_to_irq(board->irq_pin));
+ }
+ return 0;
+}
+
+static int at91_cf_resume(struct platform_device *pdev)
+{
+ struct at91_cf_socket *cf = platform_get_drvdata(pdev);
+ struct at91_cf_data *board = cf->board;
+
+ if (device_may_wakeup(&pdev->dev)) {
+ disable_irq_wake(gpio_to_irq(board->det_pin));
+ if (gpio_is_valid(board->irq_pin))
+ disable_irq_wake(gpio_to_irq(board->irq_pin));
+ }
+
+ return 0;
+}
+
+#else
+#define at91_cf_suspend NULL
+#define at91_cf_resume NULL
+#endif
+
+static struct platform_driver at91_cf_driver = {
+ .driver = {
+ .name = "at91_cf",
+ .of_match_table = of_match_ptr(at91_cf_dt_ids),
+ },
+ .probe = at91_cf_probe,
+ .remove = at91_cf_remove,
+ .suspend = at91_cf_suspend,
+ .resume = at91_cf_resume,
+};
+
+module_platform_driver(at91_cf_driver);
+
+MODULE_DESCRIPTION("AT91 Compact Flash Driver");
+MODULE_AUTHOR("David Brownell");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:at91_cf");
diff --git a/drivers/pcmcia/bcm63xx_pcmcia.c b/drivers/pcmcia/bcm63xx_pcmcia.c
new file mode 100644
index 000000000..16f573173
--- /dev/null
+++ b/drivers/pcmcia/bcm63xx_pcmcia.c
@@ -0,0 +1,537 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/gpio.h>
+
+#include <bcm63xx_regs.h>
+#include <bcm63xx_io.h>
+#include "bcm63xx_pcmcia.h"
+
+#define PFX "bcm63xx_pcmcia: "
+
+#ifdef CONFIG_CARDBUS
+/* if cardbus is used, platform device needs reference to actual pci
+ * device */
+static struct pci_dev *bcm63xx_cb_dev;
+#endif
+
+/*
+ * read/write helper for pcmcia regs
+ */
+static inline u32 pcmcia_readl(struct bcm63xx_pcmcia_socket *skt, u32 off)
+{
+ return bcm_readl(skt->base + off);
+}
+
+static inline void pcmcia_writel(struct bcm63xx_pcmcia_socket *skt,
+ u32 val, u32 off)
+{
+ bcm_writel(val, skt->base + off);
+}
+
+/*
+ * This callback should (re-)initialise the socket, turn on status
+ * interrupts and PCMCIA bus, and wait for power to stabilise so that
+ * the card status signals report correctly.
+ *
+ * Hardware cannot do that.
+ */
+static int bcm63xx_pcmcia_sock_init(struct pcmcia_socket *sock)
+{
+ return 0;
+}
+
+/*
+ * This callback should remove power on the socket, disable IRQs from
+ * the card, turn off status interrupts, and disable the PCMCIA bus.
+ *
+ * Hardware cannot do that.
+ */
+static int bcm63xx_pcmcia_suspend(struct pcmcia_socket *sock)
+{
+ 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.
+ */
+static int bcm63xx_pcmcia_set_socket(struct pcmcia_socket *sock,
+ socket_state_t *state)
+{
+ struct bcm63xx_pcmcia_socket *skt;
+ unsigned long flags;
+ u32 val;
+
+ skt = sock->driver_data;
+
+ spin_lock_irqsave(&skt->lock, flags);
+
+ /* note: hardware cannot control socket power, so we will
+ * always report SS_POWERON */
+
+ /* apply socket reset */
+ val = pcmcia_readl(skt, PCMCIA_C1_REG);
+ if (state->flags & SS_RESET)
+ val |= PCMCIA_C1_RESET_MASK;
+ else
+ val &= ~PCMCIA_C1_RESET_MASK;
+
+ /* reverse reset logic for cardbus card */
+ if (skt->card_detected && (skt->card_type & CARD_CARDBUS))
+ val ^= PCMCIA_C1_RESET_MASK;
+
+ pcmcia_writel(skt, val, PCMCIA_C1_REG);
+
+ /* keep requested state for event reporting */
+ skt->requested_state = *state;
+
+ spin_unlock_irqrestore(&skt->lock, flags);
+
+ return 0;
+}
+
+/*
+ * identity cardtype from VS[12] input, CD[12] input while only VS2 is
+ * floating, and CD[12] input while only VS1 is floating
+ */
+enum {
+ IN_VS1 = (1 << 0),
+ IN_VS2 = (1 << 1),
+ IN_CD1_VS2H = (1 << 2),
+ IN_CD2_VS2H = (1 << 3),
+ IN_CD1_VS1H = (1 << 4),
+ IN_CD2_VS1H = (1 << 5),
+};
+
+static const u8 vscd_to_cardtype[] = {
+
+ /* VS1 float, VS2 float */
+ [IN_VS1 | IN_VS2] = (CARD_PCCARD | CARD_5V),
+
+ /* VS1 grounded, VS2 float */
+ [IN_VS2] = (CARD_PCCARD | CARD_5V | CARD_3V),
+
+ /* VS1 grounded, VS2 grounded */
+ [0] = (CARD_PCCARD | CARD_5V | CARD_3V | CARD_XV),
+
+ /* VS1 tied to CD1, VS2 float */
+ [IN_VS1 | IN_VS2 | IN_CD1_VS1H] = (CARD_CARDBUS | CARD_3V),
+
+ /* VS1 grounded, VS2 tied to CD2 */
+ [IN_VS2 | IN_CD2_VS2H] = (CARD_CARDBUS | CARD_3V | CARD_XV),
+
+ /* VS1 tied to CD2, VS2 grounded */
+ [IN_VS1 | IN_CD2_VS1H] = (CARD_CARDBUS | CARD_3V | CARD_XV | CARD_YV),
+
+ /* VS1 float, VS2 grounded */
+ [IN_VS1] = (CARD_PCCARD | CARD_XV),
+
+ /* VS1 float, VS2 tied to CD2 */
+ [IN_VS1 | IN_VS2 | IN_CD2_VS2H] = (CARD_CARDBUS | CARD_3V),
+
+ /* VS1 float, VS2 tied to CD1 */
+ [IN_VS1 | IN_VS2 | IN_CD1_VS2H] = (CARD_CARDBUS | CARD_XV | CARD_YV),
+
+ /* VS1 tied to CD2, VS2 float */
+ [IN_VS1 | IN_VS2 | IN_CD2_VS1H] = (CARD_CARDBUS | CARD_YV),
+
+ /* VS2 grounded, VS1 is tied to CD1, CD2 is grounded */
+ [IN_VS1 | IN_CD1_VS1H] = 0, /* ignore cardbay */
+};
+
+/*
+ * poll hardware to check card insertion status
+ */
+static unsigned int __get_socket_status(struct bcm63xx_pcmcia_socket *skt)
+{
+ unsigned int stat;
+ u32 val;
+
+ stat = 0;
+
+ /* check CD for card presence */
+ val = pcmcia_readl(skt, PCMCIA_C1_REG);
+
+ if (!(val & PCMCIA_C1_CD1_MASK) && !(val & PCMCIA_C1_CD2_MASK))
+ stat |= SS_DETECT;
+
+ /* if new insertion, detect cardtype */
+ if ((stat & SS_DETECT) && !skt->card_detected) {
+ unsigned int stat = 0;
+
+ /* float VS1, float VS2 */
+ val |= PCMCIA_C1_VS1OE_MASK;
+ val |= PCMCIA_C1_VS2OE_MASK;
+ pcmcia_writel(skt, val, PCMCIA_C1_REG);
+
+ /* wait for output to stabilize and read VS[12] */
+ udelay(10);
+ val = pcmcia_readl(skt, PCMCIA_C1_REG);
+ stat |= (val & PCMCIA_C1_VS1_MASK) ? IN_VS1 : 0;
+ stat |= (val & PCMCIA_C1_VS2_MASK) ? IN_VS2 : 0;
+
+ /* drive VS1 low, float VS2 */
+ val &= ~PCMCIA_C1_VS1OE_MASK;
+ val |= PCMCIA_C1_VS2OE_MASK;
+ pcmcia_writel(skt, val, PCMCIA_C1_REG);
+
+ /* wait for output to stabilize and read CD[12] */
+ udelay(10);
+ val = pcmcia_readl(skt, PCMCIA_C1_REG);
+ stat |= (val & PCMCIA_C1_CD1_MASK) ? IN_CD1_VS2H : 0;
+ stat |= (val & PCMCIA_C1_CD2_MASK) ? IN_CD2_VS2H : 0;
+
+ /* float VS1, drive VS2 low */
+ val |= PCMCIA_C1_VS1OE_MASK;
+ val &= ~PCMCIA_C1_VS2OE_MASK;
+ pcmcia_writel(skt, val, PCMCIA_C1_REG);
+
+ /* wait for output to stabilize and read CD[12] */
+ udelay(10);
+ val = pcmcia_readl(skt, PCMCIA_C1_REG);
+ stat |= (val & PCMCIA_C1_CD1_MASK) ? IN_CD1_VS1H : 0;
+ stat |= (val & PCMCIA_C1_CD2_MASK) ? IN_CD2_VS1H : 0;
+
+ /* guess cardtype from all this */
+ skt->card_type = vscd_to_cardtype[stat];
+ if (!skt->card_type)
+ dev_err(&skt->socket.dev, "unsupported card type\n");
+
+ /* drive both VS pin to 0 again */
+ val &= ~(PCMCIA_C1_VS1OE_MASK | PCMCIA_C1_VS2OE_MASK);
+
+ /* enable correct logic */
+ val &= ~(PCMCIA_C1_EN_PCMCIA_MASK | PCMCIA_C1_EN_CARDBUS_MASK);
+ if (skt->card_type & CARD_PCCARD)
+ val |= PCMCIA_C1_EN_PCMCIA_MASK;
+ else
+ val |= PCMCIA_C1_EN_CARDBUS_MASK;
+
+ pcmcia_writel(skt, val, PCMCIA_C1_REG);
+ }
+ skt->card_detected = (stat & SS_DETECT) ? 1 : 0;
+
+ /* report card type/voltage */
+ if (skt->card_type & CARD_CARDBUS)
+ stat |= SS_CARDBUS;
+ if (skt->card_type & CARD_3V)
+ stat |= SS_3VCARD;
+ if (skt->card_type & CARD_XV)
+ stat |= SS_XVCARD;
+ stat |= SS_POWERON;
+
+ if (gpio_get_value(skt->pd->ready_gpio))
+ stat |= SS_READY;
+
+ return stat;
+}
+
+/*
+ * core request to get current socket status
+ */
+static int bcm63xx_pcmcia_get_status(struct pcmcia_socket *sock,
+ unsigned int *status)
+{
+ struct bcm63xx_pcmcia_socket *skt;
+
+ skt = sock->driver_data;
+
+ spin_lock_bh(&skt->lock);
+ *status = __get_socket_status(skt);
+ spin_unlock_bh(&skt->lock);
+
+ return 0;
+}
+
+/*
+ * socket polling timer callback
+ */
+static void bcm63xx_pcmcia_poll(struct timer_list *t)
+{
+ struct bcm63xx_pcmcia_socket *skt;
+ unsigned int stat, events;
+
+ skt = from_timer(skt, t, timer);
+
+ spin_lock_bh(&skt->lock);
+
+ stat = __get_socket_status(skt);
+
+ /* keep only changed bits, and mask with required one from the
+ * core */
+ events = (stat ^ skt->old_status) & skt->requested_state.csc_mask;
+ skt->old_status = stat;
+ spin_unlock_bh(&skt->lock);
+
+ if (events)
+ pcmcia_parse_events(&skt->socket, events);
+
+ mod_timer(&skt->timer,
+ jiffies + msecs_to_jiffies(BCM63XX_PCMCIA_POLL_RATE));
+}
+
+static int bcm63xx_pcmcia_set_io_map(struct pcmcia_socket *sock,
+ struct pccard_io_map *map)
+{
+ /* this doesn't seem to be called by pcmcia layer if static
+ * mapping is used */
+ return 0;
+}
+
+static int bcm63xx_pcmcia_set_mem_map(struct pcmcia_socket *sock,
+ struct pccard_mem_map *map)
+{
+ struct bcm63xx_pcmcia_socket *skt;
+ struct resource *res;
+
+ skt = sock->driver_data;
+ if (map->flags & MAP_ATTRIB)
+ res = skt->attr_res;
+ else
+ res = skt->common_res;
+
+ map->static_start = res->start + map->card_start;
+ return 0;
+}
+
+static struct pccard_operations bcm63xx_pcmcia_operations = {
+ .init = bcm63xx_pcmcia_sock_init,
+ .suspend = bcm63xx_pcmcia_suspend,
+ .get_status = bcm63xx_pcmcia_get_status,
+ .set_socket = bcm63xx_pcmcia_set_socket,
+ .set_io_map = bcm63xx_pcmcia_set_io_map,
+ .set_mem_map = bcm63xx_pcmcia_set_mem_map,
+};
+
+/*
+ * register pcmcia socket to core
+ */
+static int bcm63xx_drv_pcmcia_probe(struct platform_device *pdev)
+{
+ struct bcm63xx_pcmcia_socket *skt;
+ struct pcmcia_socket *sock;
+ struct resource *res, *irq_res;
+ unsigned int regmem_size = 0, iomem_size = 0;
+ u32 val;
+ int ret;
+
+ skt = kzalloc(sizeof(*skt), GFP_KERNEL);
+ if (!skt)
+ return -ENOMEM;
+ spin_lock_init(&skt->lock);
+ sock = &skt->socket;
+ sock->driver_data = skt;
+
+ /* make sure we have all resources we need */
+ skt->common_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ skt->attr_res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+ irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ skt->pd = pdev->dev.platform_data;
+ if (!skt->common_res || !skt->attr_res || !irq_res || !skt->pd) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* remap pcmcia registers */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ regmem_size = resource_size(res);
+ if (!request_mem_region(res->start, regmem_size, "bcm63xx_pcmcia")) {
+ ret = -EINVAL;
+ goto err;
+ }
+ skt->reg_res = res;
+
+ skt->base = ioremap(res->start, regmem_size);
+ if (!skt->base) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* remap io registers */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 3);
+ iomem_size = resource_size(res);
+ skt->io_base = ioremap(res->start, iomem_size);
+ if (!skt->io_base) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* resources are static */
+ sock->resource_ops = &pccard_static_ops;
+ sock->ops = &bcm63xx_pcmcia_operations;
+ sock->owner = THIS_MODULE;
+ sock->dev.parent = &pdev->dev;
+ sock->features = SS_CAP_STATIC_MAP | SS_CAP_PCCARD;
+ sock->io_offset = (unsigned long)skt->io_base;
+ sock->pci_irq = irq_res->start;
+
+#ifdef CONFIG_CARDBUS
+ sock->cb_dev = bcm63xx_cb_dev;
+ if (bcm63xx_cb_dev)
+ sock->features |= SS_CAP_CARDBUS;
+#endif
+
+ /* assume common & attribute memory have the same size */
+ sock->map_size = resource_size(skt->common_res);
+
+ /* initialize polling timer */
+ timer_setup(&skt->timer, bcm63xx_pcmcia_poll, 0);
+
+ /* initialize pcmcia control register, drive VS[12] to 0,
+ * leave CB IDSEL to the old value since it is set by the PCI
+ * layer */
+ val = pcmcia_readl(skt, PCMCIA_C1_REG);
+ val &= PCMCIA_C1_CBIDSEL_MASK;
+ val |= PCMCIA_C1_EN_PCMCIA_GPIO_MASK;
+ pcmcia_writel(skt, val, PCMCIA_C1_REG);
+
+ /*
+ * Hardware has only one set of timings registers, not one for
+ * each memory access type, so we configure them for the
+ * slowest one: attribute memory.
+ */
+ val = PCMCIA_C2_DATA16_MASK;
+ val |= 10 << PCMCIA_C2_RWCOUNT_SHIFT;
+ val |= 6 << PCMCIA_C2_INACTIVE_SHIFT;
+ val |= 3 << PCMCIA_C2_SETUP_SHIFT;
+ val |= 3 << PCMCIA_C2_HOLD_SHIFT;
+ pcmcia_writel(skt, val, PCMCIA_C2_REG);
+
+ ret = pcmcia_register_socket(sock);
+ if (ret)
+ goto err;
+
+ /* start polling socket */
+ mod_timer(&skt->timer,
+ jiffies + msecs_to_jiffies(BCM63XX_PCMCIA_POLL_RATE));
+
+ platform_set_drvdata(pdev, skt);
+ return 0;
+
+err:
+ if (skt->io_base)
+ iounmap(skt->io_base);
+ if (skt->base)
+ iounmap(skt->base);
+ if (skt->reg_res)
+ release_mem_region(skt->reg_res->start, regmem_size);
+ kfree(skt);
+ return ret;
+}
+
+static int bcm63xx_drv_pcmcia_remove(struct platform_device *pdev)
+{
+ struct bcm63xx_pcmcia_socket *skt;
+ struct resource *res;
+
+ skt = platform_get_drvdata(pdev);
+ del_timer_sync(&skt->timer);
+ iounmap(skt->base);
+ iounmap(skt->io_base);
+ res = skt->reg_res;
+ release_mem_region(res->start, resource_size(res));
+ kfree(skt);
+ return 0;
+}
+
+struct platform_driver bcm63xx_pcmcia_driver = {
+ .probe = bcm63xx_drv_pcmcia_probe,
+ .remove = bcm63xx_drv_pcmcia_remove,
+ .driver = {
+ .name = "bcm63xx_pcmcia",
+ .owner = THIS_MODULE,
+ },
+};
+
+#ifdef CONFIG_CARDBUS
+static int bcm63xx_cb_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ /* keep pci device */
+ bcm63xx_cb_dev = dev;
+ return platform_driver_register(&bcm63xx_pcmcia_driver);
+}
+
+static void bcm63xx_cb_exit(struct pci_dev *dev)
+{
+ platform_driver_unregister(&bcm63xx_pcmcia_driver);
+ bcm63xx_cb_dev = NULL;
+}
+
+static const struct pci_device_id bcm63xx_cb_table[] = {
+ {
+ .vendor = PCI_VENDOR_ID_BROADCOM,
+ .device = BCM6348_CPU_ID,
+ .subvendor = PCI_VENDOR_ID_BROADCOM,
+ .subdevice = PCI_ANY_ID,
+ .class = PCI_CLASS_BRIDGE_CARDBUS << 8,
+ .class_mask = ~0,
+ },
+
+ {
+ .vendor = PCI_VENDOR_ID_BROADCOM,
+ .device = BCM6358_CPU_ID,
+ .subvendor = PCI_VENDOR_ID_BROADCOM,
+ .subdevice = PCI_ANY_ID,
+ .class = PCI_CLASS_BRIDGE_CARDBUS << 8,
+ .class_mask = ~0,
+ },
+
+ { },
+};
+
+MODULE_DEVICE_TABLE(pci, bcm63xx_cb_table);
+
+static struct pci_driver bcm63xx_cardbus_driver = {
+ .name = "bcm63xx_cardbus",
+ .id_table = bcm63xx_cb_table,
+ .probe = bcm63xx_cb_probe,
+ .remove = bcm63xx_cb_exit,
+};
+#endif
+
+/*
+ * if cardbus support is enabled, register our platform device after
+ * our fake cardbus bridge has been registered
+ */
+static int __init bcm63xx_pcmcia_init(void)
+{
+#ifdef CONFIG_CARDBUS
+ return pci_register_driver(&bcm63xx_cardbus_driver);
+#else
+ return platform_driver_register(&bcm63xx_pcmcia_driver);
+#endif
+}
+
+static void __exit bcm63xx_pcmcia_exit(void)
+{
+#ifdef CONFIG_CARDBUS
+ return pci_unregister_driver(&bcm63xx_cardbus_driver);
+#else
+ platform_driver_unregister(&bcm63xx_pcmcia_driver);
+#endif
+}
+
+module_init(bcm63xx_pcmcia_init);
+module_exit(bcm63xx_pcmcia_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Maxime Bizon <mbizon@freebox.fr>");
+MODULE_DESCRIPTION("Linux PCMCIA Card Services: bcm63xx Socket Controller");
diff --git a/drivers/pcmcia/bcm63xx_pcmcia.h b/drivers/pcmcia/bcm63xx_pcmcia.h
new file mode 100644
index 000000000..2122c59a1
--- /dev/null
+++ b/drivers/pcmcia/bcm63xx_pcmcia.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef BCM63XX_PCMCIA_H_
+#define BCM63XX_PCMCIA_H_
+
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <pcmcia/ss.h>
+#include <bcm63xx_dev_pcmcia.h>
+
+/* socket polling rate in ms */
+#define BCM63XX_PCMCIA_POLL_RATE 500
+
+enum {
+ CARD_CARDBUS = (1 << 0),
+ CARD_PCCARD = (1 << 1),
+ CARD_5V = (1 << 2),
+ CARD_3V = (1 << 3),
+ CARD_XV = (1 << 4),
+ CARD_YV = (1 << 5),
+};
+
+struct bcm63xx_pcmcia_socket {
+ struct pcmcia_socket socket;
+
+ /* platform specific data */
+ struct bcm63xx_pcmcia_platform_data *pd;
+
+ /* all regs access are protected by this spinlock */
+ spinlock_t lock;
+
+ /* pcmcia registers resource */
+ struct resource *reg_res;
+
+ /* base remapped address of registers */
+ void __iomem *base;
+
+ /* whether a card is detected at the moment */
+ int card_detected;
+
+ /* type of detected card (mask of above enum) */
+ u8 card_type;
+
+ /* keep last socket status to implement event reporting */
+ unsigned int old_status;
+
+ /* backup of requested socket state */
+ socket_state_t requested_state;
+
+ /* timer used for socket status polling */
+ struct timer_list timer;
+
+ /* attribute/common memory resources */
+ struct resource *attr_res;
+ struct resource *common_res;
+ struct resource *io_res;
+
+ /* base address of io memory */
+ void __iomem *io_base;
+};
+
+#endif /* BCM63XX_PCMCIA_H_ */
diff --git a/drivers/pcmcia/cardbus.c b/drivers/pcmcia/cardbus.c
new file mode 100644
index 000000000..45c8252c8
--- /dev/null
+++ b/drivers/pcmcia/cardbus.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cardbus.c -- 16-bit PCMCIA core support
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include <pcmcia/ss.h>
+#include <pcmcia/cistpl.h>
+
+#include "cs_internal.h"
+
+static void cardbus_config_irq_and_cls(struct pci_bus *bus, int irq)
+{
+ struct pci_dev *dev;
+
+ list_for_each_entry(dev, &bus->devices, bus_list) {
+ u8 irq_pin;
+
+ /*
+ * Since there is only one interrupt available to
+ * CardBus devices, all devices downstream of this
+ * device must be using this IRQ.
+ */
+ 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);
+ }
+
+ /*
+ * Some controllers transfer very slowly with 0 CLS.
+ * Configure it. This may fail as CLS configuration
+ * is mandatory only for MWI.
+ */
+ pci_set_cacheline_size(dev);
+
+ if (dev->subordinate)
+ cardbus_config_irq_and_cls(dev->subordinate, irq);
+ }
+}
+
+/**
+ * cb_alloc() - add CardBus device
+ * @s: the pcmcia_socket where the CardBus device is located
+ *
+ * cb_alloc() allocates the kernel data structures for a Cardbus device
+ * and handles the lowest level PCI device setup issues.
+ */
+int __ref cb_alloc(struct pcmcia_socket *s)
+{
+ struct pci_bus *bus = s->cb_dev->subordinate;
+ struct pci_dev *dev;
+ unsigned int max, pass;
+
+ pci_lock_rescan_remove();
+
+ s->functions = pci_scan_slot(bus, PCI_DEVFN(0, 0));
+ pci_fixup_cardbus(bus);
+
+ max = bus->busn_res.start;
+ for (pass = 0; pass < 2; pass++)
+ for_each_pci_bridge(dev, bus)
+ 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_config_irq_and_cls(bus, s->pci_irq);
+
+ /* socket specific tune function */
+ if (s->tune_bridge)
+ s->tune_bridge(s, bus);
+
+ pci_bus_add_devices(bus);
+
+ pci_unlock_rescan_remove();
+ return 0;
+}
+
+/**
+ * cb_free() - remove CardBus device
+ * @s: the pcmcia_socket where the CardBus device was located
+ *
+ * cb_free() handles the lowest level PCI device cleanup.
+ */
+void cb_free(struct pcmcia_socket *s)
+{
+ struct pci_dev *bridge, *dev, *tmp;
+ struct pci_bus *bus;
+
+ bridge = s->cb_dev;
+ if (!bridge)
+ return;
+
+ bus = bridge->subordinate;
+ if (!bus)
+ return;
+
+ pci_lock_rescan_remove();
+
+ list_for_each_entry_safe(dev, tmp, &bus->devices, bus_list)
+ pci_stop_and_remove_bus_device(dev);
+
+ pci_unlock_rescan_remove();
+}
diff --git a/drivers/pcmcia/cirrus.h b/drivers/pcmcia/cirrus.h
new file mode 100644
index 000000000..446a4576e
--- /dev/null
+++ b/drivers/pcmcia/cirrus.h
@@ -0,0 +1,147 @@
+/*
+ * 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
+
+#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 000000000..cf109d9a1
--- /dev/null
+++ b/drivers/pcmcia/cistpl.c
@@ -0,0 +1,1610 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cistpl.c -- 16-bit PCMCIA Card Information Structure parser
+ *
+ * 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/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/pci.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <linux/security.h>
+#include <asm/byteorder.h>
+#include <asm/unaligned.h>
+
+#include <pcmcia/ss.h>
+#include <pcmcia/cisreg.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ds.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
+
+/* Bits in IRQInfo1 field */
+#define IRQ_INFO2_VALID 0x10
+
+/* 16-bit CIS? */
+static int cis_width;
+module_param(cis_width, int, 0444);
+
+void release_cis_mem(struct pcmcia_socket *s)
+{
+ mutex_lock(&s->ops_mutex);
+ 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;
+ }
+ mutex_unlock(&s->ops_mutex);
+}
+
+/**
+ * set_cis_map() - map the card memory at "card_offset" into virtual space.
+ *
+ * If flags & MAP_ATTRIB, map the attribute space, otherwise
+ * map the memory space.
+ *
+ * Must be called with ops_mutex held.
+ */
+static void __iomem *set_cis_map(struct pcmcia_socket *s,
+ unsigned int card_offset, unsigned int flags)
+{
+ pccard_mem_map *mem = &s->cis_mem;
+ int ret;
+
+ if (!(s->features & SS_CAP_STATIC_MAP) && (mem->res == NULL)) {
+ mem->res = pcmcia_find_mem_region(0, s->map_size,
+ s->map_size, 0, s);
+ if (mem->res == NULL) {
+ dev_notice(&s->dev, "cs: unable to map card memory!\n");
+ return NULL;
+ }
+ s->cis_virt = NULL;
+ }
+
+ if (!(s->features & SS_CAP_STATIC_MAP) && (!s->cis_virt))
+ s->cis_virt = ioremap(mem->res->start, s->map_size);
+
+ mem->card_start = card_offset;
+ mem->flags = flags;
+
+ ret = s->ops->set_mem_map(s, mem);
+ if (ret) {
+ iounmap(s->cis_virt);
+ s->cis_virt = NULL;
+ return NULL;
+ }
+
+ 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;
+}
+
+
+/* Bits in attr field */
+#define IS_ATTR 1
+#define IS_INDIRECT 8
+
+/**
+ * pcmcia_read_cis_mem() - low-level function to read CIS memory
+ *
+ * must be called with ops_mutex held
+ */
+int pcmcia_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;
+
+ dev_dbg(&s->dev, "pcmcia_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) {
+ dev_dbg(&s->dev, "could not map memory\n");
+ 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;
+
+ if (addr > CISTPL_MAX_CIS_SIZE) {
+ dev_dbg(&s->dev,
+ "attempt to read CIS mem at addr %#x", addr);
+ memset(ptr, 0xff, len);
+ return -1;
+ }
+
+ 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) {
+ dev_dbg(&s->dev, "could not map memory\n");
+ 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;
+ }
+ }
+ dev_dbg(&s->dev, " %#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;
+}
+
+
+/**
+ * pcmcia_write_cis_mem() - low-level function to write CIS memory
+ *
+ * Probably only useful for writing one-byte registers. Must be called
+ * with ops_mutex held.
+ */
+int pcmcia_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;
+
+ dev_dbg(&s->dev,
+ "pcmcia_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) {
+ dev_dbg(&s->dev, "could not map memory\n");
+ return -EINVAL;
+ }
+
+ 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) {
+ dev_dbg(&s->dev, "could not map memory\n");
+ return -EINVAL;
+ }
+
+ 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;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * read_cis_cache() - read CIS memory or its associated cache
+ *
+ * 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 int read_cis_cache(struct pcmcia_socket *s, int attr, u_int addr,
+ size_t len, void *ptr)
+{
+ struct cis_cache_entry *cis;
+ int ret = 0;
+
+ if (s->state & SOCKET_CARDBUS)
+ return -EINVAL;
+
+ mutex_lock(&s->ops_mutex);
+ if (s->fake_cis) {
+ if (s->fake_cis_len >= addr+len)
+ memcpy(ptr, s->fake_cis+addr, len);
+ else {
+ memset(ptr, 0xff, len);
+ ret = -EINVAL;
+ }
+ mutex_unlock(&s->ops_mutex);
+ return ret;
+ }
+
+ list_for_each_entry(cis, &s->cis_cache, node) {
+ if (cis->addr == addr && cis->len == len && cis->attr == attr) {
+ memcpy(ptr, cis->cache, len);
+ mutex_unlock(&s->ops_mutex);
+ return 0;
+ }
+ }
+
+ ret = pcmcia_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);
+ }
+ }
+ mutex_unlock(&s->ops_mutex);
+
+ return ret;
+}
+
+static void
+remove_cis_cache(struct pcmcia_socket *s, int attr, u_int addr, u_int len)
+{
+ struct cis_cache_entry *cis;
+
+ mutex_lock(&s->ops_mutex);
+ 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;
+ }
+ mutex_unlock(&s->ops_mutex);
+}
+
+/**
+ * destroy_cis_cache() - destroy the CIS cache
+ * @s: pcmcia_socket for which CIS cache shall be destroyed
+ *
+ * This destroys the CIS cache but keeps any fake CIS alive. Must be
+ * called with ops_mutex held.
+ */
+void destroy_cis_cache(struct pcmcia_socket *s)
+{
+ struct list_head *l, *n;
+ struct cis_cache_entry *cis;
+
+ list_for_each_safe(l, n, &s->cis_cache) {
+ cis = list_entry(l, struct cis_cache_entry, node);
+ list_del(&cis->node);
+ kfree(cis);
+ }
+}
+
+/**
+ * verify_cis_cache() - does the CIS match what is in the CIS cache?
+ */
+int verify_cis_cache(struct pcmcia_socket *s)
+{
+ struct cis_cache_entry *cis;
+ char *buf;
+ int ret;
+
+ if (s->state & SOCKET_CARDBUS)
+ return -EINVAL;
+
+ buf = kmalloc(256, GFP_KERNEL);
+ if (buf == NULL) {
+ dev_warn(&s->dev, "no memory for verifying CIS\n");
+ return -ENOMEM;
+ }
+ mutex_lock(&s->ops_mutex);
+ list_for_each_entry(cis, &s->cis_cache, node) {
+ int len = cis->len;
+
+ if (len > 256)
+ len = 256;
+
+ ret = pcmcia_read_cis_mem(s, cis->attr, cis->addr, len, buf);
+ if (ret || memcmp(buf, cis->cache, len) != 0) {
+ kfree(buf);
+ mutex_unlock(&s->ops_mutex);
+ return -1;
+ }
+ }
+ kfree(buf);
+ mutex_unlock(&s->ops_mutex);
+ return 0;
+}
+
+/**
+ * pcmcia_replace_cis() - use a replacement CIS instead of the card's CIS
+ *
+ * For really bad cards, we provide a facility for uploading a
+ * replacement CIS.
+ */
+int pcmcia_replace_cis(struct pcmcia_socket *s,
+ const u8 *data, const size_t len)
+{
+ if (len > CISTPL_MAX_CIS_SIZE) {
+ dev_warn(&s->dev, "replacement CIS too big\n");
+ return -EINVAL;
+ }
+ mutex_lock(&s->ops_mutex);
+ kfree(s->fake_cis);
+ s->fake_cis = kmalloc(len, GFP_KERNEL);
+ if (s->fake_cis == NULL) {
+ dev_warn(&s->dev, "no memory to replace CIS\n");
+ mutex_unlock(&s->ops_mutex);
+ return -ENOMEM;
+ }
+ s->fake_cis_len = len;
+ memcpy(s->fake_cis, data, len);
+ dev_info(&s->dev, "Using replacement CIS\n");
+ mutex_unlock(&s->ops_mutex);
+ return 0;
+}
+
+/* The high-level CIS tuple services */
+
+struct tuple_flags {
+ u_int link_space:4;
+ u_int has_link:1;
+ u_int mfc_fn:3;
+ u_int space:4;
+};
+
+#define LINK_SPACE(f) (((struct tuple_flags *)(&(f)))->link_space)
+#define HAS_LINK(f) (((struct tuple_flags *)(&(f)))->has_link)
+#define MFC_FN(f) (((struct tuple_flags *)(&(f)))->mfc_fn)
+#define SPACE(f) (((struct tuple_flags *)(&(f)))->space)
+
+int pccard_get_first_tuple(struct pcmcia_socket *s, unsigned int function,
+ tuple_t *tuple)
+{
+ if (!s)
+ return -EINVAL;
+
+ if (!(s->state & SOCKET_PRESENT) || (s->state & SOCKET_CARDBUS))
+ return -ENODEV;
+ tuple->TupleLink = tuple->Flags = 0;
+
+ /* Assume presence of a LONGLINK_C to address 0 */
+ tuple->CISOffset = tuple->LinkOffset = 0;
+ SPACE(tuple->Flags) = HAS_LINK(tuple->Flags) = 1;
+
+ if ((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) == 0) {
+ tuple->DesiredTuple = CISTPL_LINKTARGET;
+ if (pccard_get_next_tuple(s, function, tuple) != 0)
+ return -ENOSPC;
+ } else
+ tuple->CISOffset = tuple->TupleLink = 0;
+ tuple->DesiredTuple = req;
+ }
+ return pccard_get_next_tuple(s, function, tuple);
+}
+
+static int follow_link(struct pcmcia_socket *s, tuple_t *tuple)
+{
+ u_char link[5];
+ u_int ofs;
+ int ret;
+
+ if (MFC_FN(tuple->Flags)) {
+ /* Get indirect link from the MFC tuple */
+ ret = read_cis_cache(s, LINK_SPACE(tuple->Flags),
+ tuple->LinkOffset, 5, link);
+ if (ret)
+ return -1;
+ ofs = get_unaligned_le32(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 (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... */
+ ret = read_cis_cache(s, SPACE(tuple->Flags), ofs, 5, link);
+ if (ret)
+ return -1;
+ 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;
+ }
+ ret = read_cis_cache(s, SPACE(tuple->Flags), ofs, 5, link);
+ if (ret)
+ return -1;
+ 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;
+ int ret;
+
+ if (!s)
+ return -EINVAL;
+ if (!(s->state & SOCKET_PRESENT) || (s->state & SOCKET_CARDBUS))
+ return -ENODEV;
+
+ 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 {
+ ret = read_cis_cache(s, attr, ofs, 2, link);
+ if (ret)
+ return -1;
+ if (link[0] == CISTPL_NULL) {
+ ofs++;
+ continue;
+ }
+ }
+
+ /* End of chain? Follow long link if possible */
+ if (link[0] == CISTPL_END) {
+ ofs = follow_link(s, tuple);
+ if (ofs < 0)
+ return -ENOSPC;
+ attr = SPACE(tuple->Flags);
+ ret = read_cis_cache(s, attr, ofs, 2, link);
+ if (ret)
+ return -1;
+ }
+
+ /* 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;
+ ret = read_cis_cache(s, attr, ofs+2, 4,
+ &tuple->LinkOffset);
+ if (ret)
+ return -1;
+ break;
+ case CISTPL_LONGLINK_C:
+ HAS_LINK(tuple->Flags) = 1;
+ LINK_SPACE(tuple->Flags) = attr & ~IS_ATTR;
+ ret = read_cis_cache(s, attr, ofs+2, 4,
+ &tuple->LinkOffset);
+ if (ret)
+ return -1;
+ 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 */
+ ret = read_cis_cache(s, attr, ofs+2,
+ 1, &tmp);
+ if (ret)
+ return -1;
+ 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) {
+ dev_dbg(&s->dev, "cs: overrun in pcmcia_get_next_tuple\n");
+ return -ENOSPC;
+ }
+
+ tuple->TupleCode = link[0];
+ tuple->TupleLink = link[1];
+ tuple->CISOffset = ofs + 2;
+ return 0;
+}
+
+int pccard_get_tuple_data(struct pcmcia_socket *s, tuple_t *tuple)
+{
+ u_int len;
+ int ret;
+
+ if (!s)
+ return -EINVAL;
+
+ if (tuple->TupleLink < tuple->TupleOffset)
+ return -ENOSPC;
+ len = tuple->TupleLink - tuple->TupleOffset;
+ tuple->TupleDataLen = tuple->TupleLink;
+ if (len == 0)
+ return 0;
+ ret = read_cis_cache(s, SPACE(tuple->Flags),
+ tuple->CISOffset + tuple->TupleOffset,
+ min(len, (u_int) tuple->TupleDataMax),
+ tuple->TupleData);
+ if (ret)
+ return -1;
+ return 0;
+}
+
+
+/* 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 -EINVAL;
+ device->dev[i].speed = SPEED_CVT(*p);
+ while (*p & 0x80)
+ if (++p == q)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (++p == q)
+ return -EINVAL;
+ if (*p == 0xff)
+ break;
+ scale = *p & 7;
+ if (scale == 7)
+ return -EINVAL;
+ device->dev[i].size = ((*p >> 3) + 1) * (512 << (scale*2));
+ device->ndev++;
+ if (++p == q)
+ break;
+ }
+
+ return 0;
+}
+
+
+static int parse_checksum(tuple_t *tuple, cistpl_checksum_t *csum)
+{
+ u_char *p;
+ if (tuple->TupleDataLen < 5)
+ return -EINVAL;
+ p = (u_char *) tuple->TupleData;
+ csum->addr = tuple->CISOffset + get_unaligned_le16(p) - 2;
+ csum->len = get_unaligned_le16(p + 2);
+ csum->sum = *(p + 4);
+ return 0;
+}
+
+
+static int parse_longlink(tuple_t *tuple, cistpl_longlink_t *link)
+{
+ if (tuple->TupleDataLen < 4)
+ return -EINVAL;
+ link->addr = get_unaligned_le32(tuple->TupleData);
+ return 0;
+}
+
+
+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 -EINVAL;
+ for (i = 0; i < link->nfn; i++) {
+ link->fn[i].space = *p; p++;
+ link->fn[i].addr = get_unaligned_le32(p);
+ p += 4;
+ }
+ return 0;
+}
+
+
+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 -EINVAL;
+ 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 -EINVAL;
+ }
+ if ((*p == 0xff) || (++p == q))
+ break;
+ }
+ if (found) {
+ *found = ns;
+ return 0;
+ }
+
+ return (ns == max) ? 0 : -EINVAL;
+}
+
+
+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 -EINVAL;
+
+ 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 0;
+}
+
+
+static int parse_manfid(tuple_t *tuple, cistpl_manfid_t *m)
+{
+ if (tuple->TupleDataLen < 4)
+ return -EINVAL;
+ m->manf = get_unaligned_le16(tuple->TupleData);
+ m->card = get_unaligned_le16(tuple->TupleData + 2);
+ return 0;
+}
+
+
+static int parse_funcid(tuple_t *tuple, cistpl_funcid_t *f)
+{
+ u_char *p;
+ if (tuple->TupleDataLen < 2)
+ return -EINVAL;
+ p = (u_char *)tuple->TupleData;
+ f->func = p[0];
+ f->sysinit = p[1];
+ return 0;
+}
+
+
+static int parse_funce(tuple_t *tuple, cistpl_funce_t *f)
+{
+ u_char *p;
+ int i;
+ if (tuple->TupleDataLen < 1)
+ return -EINVAL;
+ p = (u_char *)tuple->TupleData;
+ f->type = p[0];
+ for (i = 1; i < tuple->TupleDataLen; i++)
+ f->data[i-1] = p[i];
+ return 0;
+}
+
+
+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 -EINVAL;
+ 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 0;
+}
+
+/* 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 -EINVAL;
+ 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 -EINVAL;
+ features = *p; p++;
+
+ /* Power options */
+ if ((features & 3) > 0) {
+ p = parse_power(p, q, &entry->vcc);
+ if (p == NULL)
+ return -EINVAL;
+ } else
+ entry->vcc.present = 0;
+ if ((features & 3) > 1) {
+ p = parse_power(p, q, &entry->vpp1);
+ if (p == NULL)
+ return -EINVAL;
+ } else
+ entry->vpp1.present = 0;
+ if ((features & 3) > 2) {
+ p = parse_power(p, q, &entry->vpp2);
+ if (p == NULL)
+ return -EINVAL;
+ } else
+ entry->vpp2.present = 0;
+
+ /* Timing options */
+ if (features & 0x04) {
+ p = parse_timing(p, q, &entry->timing);
+ if (p == NULL)
+ return -EINVAL;
+ } 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 -EINVAL;
+ } else
+ entry->io.nwin = 0;
+
+ /* Interrupt options */
+ if (features & 0x10) {
+ p = parse_irq(p, q, &entry->irq);
+ if (p == NULL)
+ return -EINVAL;
+ } 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 = get_unaligned_le16(p) << 8;
+ entry->mem.win[0].card_addr = 0;
+ entry->mem.win[0].host_addr = 0;
+ p += 2;
+ if (p > q)
+ return -EINVAL;
+ break;
+ case 0x40:
+ entry->mem.nwin = 1;
+ entry->mem.win[0].len = get_unaligned_le16(p) << 8;
+ entry->mem.win[0].card_addr = get_unaligned_le16(p + 2) << 8;
+ entry->mem.win[0].host_addr = 0;
+ p += 4;
+ if (p > q)
+ return -EINVAL;
+ break;
+ case 0x60:
+ p = parse_mem(p, q, &entry->mem);
+ if (p == NULL)
+ return -EINVAL;
+ break;
+ }
+
+ /* Misc features */
+ if (features & 0x80) {
+ if (p == q)
+ return -EINVAL;
+ entry->flags |= (*p << 8);
+ while (*p & 0x80)
+ if (++p == q)
+ return -EINVAL;
+ p++;
+ }
+
+ entry->subtuples = q-p;
+
+ return 0;
+}
+
+
+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 0;
+}
+
+
+static int parse_vers_2(tuple_t *tuple, cistpl_vers_2_t *v2)
+{
+ u_char *p, *q;
+
+ if (tuple->TupleDataLen < 10)
+ return -EINVAL;
+
+ p = tuple->TupleData;
+ q = p + tuple->TupleDataLen;
+
+ v2->vers = p[0];
+ v2->comply = p[1];
+ v2->dindex = get_unaligned_le16(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 -EINVAL;
+ org->data_org = *p;
+ if (++p == q)
+ return -EINVAL;
+ for (i = 0; i < 30; i++) {
+ org->desc[i] = *p;
+ if (*p == '\0')
+ break;
+ if (++p == q)
+ return -EINVAL;
+ }
+ return 0;
+}
+
+
+static int parse_format(tuple_t *tuple, cistpl_format_t *fmt)
+{
+ u_char *p;
+
+ if (tuple->TupleDataLen < 10)
+ return -EINVAL;
+
+ p = tuple->TupleData;
+
+ fmt->type = p[0];
+ fmt->edc = p[1];
+ fmt->offset = get_unaligned_le32(p + 2);
+ fmt->length = get_unaligned_le32(p + 6);
+
+ return 0;
+}
+
+
+int pcmcia_parse_tuple(tuple_t *tuple, cisparse_t *parse)
+{
+ int ret = 0;
+
+ if (tuple->TupleDataLen > tuple->TupleDataMax)
+ return -EINVAL;
+ switch (tuple->TupleCode) {
+ case CISTPL_DEVICE:
+ case CISTPL_DEVICE_A:
+ ret = parse_device(tuple, &parse->device);
+ break;
+ 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 = 0;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ if (ret)
+ pr_debug("parse_tuple failed %d\n", ret);
+ return ret;
+}
+EXPORT_SYMBOL(pcmcia_parse_tuple);
+
+
+/**
+ * pccard_validate_cis() - check whether card has a sensible CIS
+ * @s: the struct pcmcia_socket we are to check
+ * @info: returns the number of tuples in the (valid) CIS, or 0
+ *
+ * This tries to determine if a card has a sensible CIS. In @info, 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.
+ *
+ * The function returns 0 on success.
+ */
+int pccard_validate_cis(struct pcmcia_socket *s, unsigned int *info)
+{
+ tuple_t *tuple;
+ cisparse_t *p;
+ unsigned int count = 0;
+ int ret, reserved, dev_ok = 0, ident_ok = 0;
+
+ if (!s)
+ return -EINVAL;
+
+ if (s->functions || !(s->state & SOCKET_PRESENT)) {
+ WARN_ON(1);
+ return -EINVAL;
+ }
+
+ /* We do not want to validate the CIS cache... */
+ mutex_lock(&s->ops_mutex);
+ destroy_cis_cache(s);
+ mutex_unlock(&s->ops_mutex);
+
+ tuple = kmalloc(sizeof(*tuple), GFP_KERNEL);
+ if (tuple == NULL) {
+ dev_warn(&s->dev, "no memory to validate CIS\n");
+ return -ENOMEM;
+ }
+ p = kmalloc(sizeof(*p), GFP_KERNEL);
+ if (p == NULL) {
+ kfree(tuple);
+ dev_warn(&s->dev, "no memory to validate CIS\n");
+ return -ENOMEM;
+ }
+
+ count = reserved = 0;
+ tuple->DesiredTuple = RETURN_FIRST_TUPLE;
+ tuple->Attributes = TUPLE_RETURN_COMMON;
+ ret = pccard_get_first_tuple(s, BIND_FN_ALL, tuple);
+ if (ret != 0)
+ 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, BIND_FN_ALL, CISTPL_CFTABLE_ENTRY, p)) ||
+ (!pccard_read_tuple(s, BIND_FN_ALL, CISTPL_CFTABLE_ENTRY_CB, p)))
+ 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, BIND_FN_ALL, CISTPL_MANFID, p) == 0) ||
+ (pccard_read_tuple(s, BIND_FN_ALL, CISTPL_VERS_1, p) == 0) ||
+ (pccard_read_tuple(s, BIND_FN_ALL, CISTPL_VERS_2, p) != -ENOSPC))
+ ident_ok++;
+
+ if (!dev_ok && !ident_ok)
+ goto done;
+
+ for (count = 1; count < MAX_TUPLES; count++) {
+ ret = pccard_get_next_tuple(s, BIND_FN_ALL, tuple);
+ if (ret != 0)
+ break;
+ if (((tuple->TupleCode > 0x23) && (tuple->TupleCode < 0x40)) ||
+ ((tuple->TupleCode > 0x47) && (tuple->TupleCode < 0x80)) ||
+ ((tuple->TupleCode > 0x90) && (tuple->TupleCode < 0xff)))
+ reserved++;
+ }
+ if ((count == MAX_TUPLES) || (reserved > 5) ||
+ ((!dev_ok || !ident_ok) && (count > 10)))
+ count = 0;
+
+ ret = 0;
+
+done:
+ /* invalidate CIS cache on failure */
+ if (!dev_ok || !ident_ok || !count) {
+ mutex_lock(&s->ops_mutex);
+ destroy_cis_cache(s);
+ mutex_unlock(&s->ops_mutex);
+ /* We differentiate between dev_ok, ident_ok and count
+ failures to allow for an override for anonymous cards
+ in ds.c */
+ if (!dev_ok || !ident_ok)
+ ret = -EIO;
+ else
+ ret = -EFAULT;
+ }
+
+ if (info)
+ *info = count;
+ kfree(tuple);
+ kfree(p);
+ return ret;
+}
+
+
+#define to_socket(_dev) container_of(_dev, struct pcmcia_socket, dev)
+
+static ssize_t pccard_extract_cis(struct pcmcia_socket *s, char *buf,
+ loff_t off, size_t count)
+{
+ tuple_t tuple;
+ int status, i;
+ loff_t pointer = 0;
+ ssize_t ret = 0;
+ u_char *tuplebuffer;
+ u_char *tempbuffer;
+
+ tuplebuffer = kmalloc_array(256, sizeof(u_char), GFP_KERNEL);
+ if (!tuplebuffer)
+ return -ENOMEM;
+
+ tempbuffer = kmalloc_array(258, sizeof(u_char), GFP_KERNEL);
+ if (!tempbuffer) {
+ ret = -ENOMEM;
+ goto free_tuple;
+ }
+
+ memset(&tuple, 0, sizeof(tuple_t));
+
+ tuple.Attributes = TUPLE_RETURN_LINK | TUPLE_RETURN_COMMON;
+ tuple.DesiredTuple = RETURN_FIRST_TUPLE;
+ tuple.TupleOffset = 0;
+
+ status = pccard_get_first_tuple(s, BIND_FN_ALL, &tuple);
+ while (!status) {
+ tuple.TupleData = tuplebuffer;
+ tuple.TupleDataMax = 255;
+ memset(tuplebuffer, 0, sizeof(u_char) * 255);
+
+ status = pccard_get_tuple_data(s, &tuple);
+ if (status)
+ break;
+
+ if (off < (pointer + 2 + tuple.TupleDataLen)) {
+ tempbuffer[0] = tuple.TupleCode & 0xff;
+ tempbuffer[1] = tuple.TupleLink & 0xff;
+ for (i = 0; i < tuple.TupleDataLen; i++)
+ tempbuffer[i + 2] = tuplebuffer[i] & 0xff;
+
+ for (i = 0; i < (2 + tuple.TupleDataLen); i++) {
+ if (((i + pointer) >= off) &&
+ (i + pointer) < (off + count)) {
+ buf[ret] = tempbuffer[i];
+ ret++;
+ }
+ }
+ }
+
+ pointer += 2 + tuple.TupleDataLen;
+
+ if (pointer >= (off + count))
+ break;
+
+ if (tuple.TupleCode == CISTPL_END)
+ break;
+ status = pccard_get_next_tuple(s, BIND_FN_ALL, &tuple);
+ }
+
+ kfree(tempbuffer);
+ free_tuple:
+ kfree(tuplebuffer);
+
+ return ret;
+}
+
+
+static ssize_t pccard_show_cis(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ unsigned int size = 0x200;
+
+ if (off >= size)
+ count = 0;
+ else {
+ struct pcmcia_socket *s;
+ unsigned int chains = 1;
+
+ if (off + count > size)
+ count = size - off;
+
+ s = to_socket(container_of(kobj, struct device, kobj));
+
+ if (!(s->state & SOCKET_PRESENT))
+ return -ENODEV;
+ if (!s->functions && pccard_validate_cis(s, &chains))
+ return -EIO;
+ if (!chains)
+ return -ENODATA;
+
+ count = pccard_extract_cis(s, buf, off, count);
+ }
+
+ return count;
+}
+
+
+static ssize_t pccard_store_cis(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct pcmcia_socket *s;
+ int error;
+
+ error = security_locked_down(LOCKDOWN_PCMCIA_CIS);
+ if (error)
+ return error;
+
+ s = to_socket(container_of(kobj, struct device, kobj));
+
+ if (off)
+ return -EINVAL;
+
+ if (count >= CISTPL_MAX_CIS_SIZE)
+ return -EINVAL;
+
+ if (!(s->state & SOCKET_PRESENT))
+ return -ENODEV;
+
+ error = pcmcia_replace_cis(s, buf, count);
+ if (error)
+ return -EIO;
+
+ pcmcia_parse_uevents(s, PCMCIA_UEVENT_REQUERY);
+
+ return count;
+}
+
+
+const struct bin_attribute pccard_cis_attr = {
+ .attr = { .name = "cis", .mode = S_IRUGO | S_IWUSR },
+ .size = 0x200,
+ .read = pccard_show_cis,
+ .write = pccard_store_cis,
+};
diff --git a/drivers/pcmcia/cs.c b/drivers/pcmcia/cs.c
new file mode 100644
index 000000000..820cce7c8
--- /dev/null
+++ b/drivers/pcmcia/cs.c
@@ -0,0 +1,919 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cs.c -- Kernel Card Services - core services
+ *
+ * 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/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/device.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <asm/irq.h>
+
+#include <pcmcia/ss.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/cisreg.h>
+#include <pcmcia/ds.h>
+#include "cs_internal.h"
+
+
+/* Module parameters */
+
+MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>");
+MODULE_DESCRIPTION("Linux Kernel Card Services");
+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 */
+
+
+socket_state_t dead_socket = {
+ .csc_mask = SS_DETECT,
+};
+EXPORT_SYMBOL(dead_socket);
+
+
+/* List of all sockets, protected by a rwsem */
+LIST_HEAD(pcmcia_socket_list);
+EXPORT_SYMBOL(pcmcia_socket_list);
+
+DECLARE_RWSEM(pcmcia_socket_list_rwsem);
+EXPORT_SYMBOL(pcmcia_socket_list_rwsem);
+
+
+struct pcmcia_socket *pcmcia_get_socket(struct pcmcia_socket *skt)
+{
+ struct device *dev = get_device(&skt->dev);
+ if (!dev)
+ return NULL;
+ return dev_get_drvdata(dev);
+}
+EXPORT_SYMBOL(pcmcia_get_socket);
+
+
+void pcmcia_put_socket(struct pcmcia_socket *skt)
+{
+ put_device(&skt->dev);
+}
+EXPORT_SYMBOL(pcmcia_put_socket);
+
+
+static void pcmcia_release_socket(struct device *dev)
+{
+ struct pcmcia_socket *socket = dev_get_drvdata(dev);
+
+ complete(&socket->socket_released);
+}
+
+static int pccardd(void *__skt);
+
+/**
+ * pcmcia_register_socket - add a new pcmcia socket device
+ * @socket: the &socket to register
+ */
+int pcmcia_register_socket(struct pcmcia_socket *socket)
+{
+ struct task_struct *tsk;
+ int ret;
+
+ if (!socket || !socket->ops || !socket->dev.parent || !socket->resource_ops)
+ return -EINVAL;
+
+ dev_dbg(&socket->dev, "pcmcia_register_socket(0x%p)\n", socket->ops);
+
+ /* 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);
+
+#ifndef CONFIG_CARDBUS
+ /*
+ * If we do not support Cardbus, ensure that
+ * the Cardbus socket capability is disabled.
+ */
+ socket->features &= ~SS_CAP_CARDBUS;
+#endif
+
+ /* set proper values in socket->dev */
+ dev_set_drvdata(&socket->dev, socket);
+ socket->dev.class = &pcmcia_socket_class;
+ dev_set_name(&socket->dev, "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);
+ mutex_init(&socket->skt_mutex);
+ mutex_init(&socket->ops_mutex);
+ spin_lock_init(&socket->thread_lock);
+
+ if (socket->resource_ops->init) {
+ mutex_lock(&socket->ops_mutex);
+ ret = socket->resource_ops->init(socket);
+ mutex_unlock(&socket->ops_mutex);
+ if (ret)
+ goto err;
+ }
+
+ tsk = kthread_run(pccardd, socket, "pccardd");
+ if (IS_ERR(tsk)) {
+ ret = PTR_ERR(tsk);
+ goto err;
+ }
+
+ wait_for_completion(&socket->thread_done);
+ if (!socket->thread) {
+ dev_warn(&socket->dev,
+ "PCMCIA: warning: socket thread did not start\n");
+ return -EIO;
+ }
+
+ pcmcia_parse_events(socket, SS_DETECT);
+
+ /*
+ * Let's try to get the PCMCIA module for 16-bit PCMCIA support.
+ * If it fails, it doesn't matter -- we still have 32-bit CardBus
+ * support to offer, so this is not a failure mode.
+ */
+ request_module_nowait("pcmcia");
+
+ 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
+ * @socket: the &socket to unregister
+ */
+void pcmcia_unregister_socket(struct pcmcia_socket *socket)
+{
+ if (!socket)
+ return;
+
+ dev_dbg(&socket->dev, "pcmcia_unregister_socket(0x%p)\n", socket->ops);
+
+ if (socket->thread)
+ kthread_stop(socket->thread);
+
+ /* 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 */
+ if (socket->resource_ops->exit) {
+ mutex_lock(&socket->ops_mutex);
+ socket->resource_ops->exit(socket);
+ mutex_unlock(&socket->ops_mutex);
+ }
+ 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);
+
+static int socket_reset(struct pcmcia_socket *skt)
+{
+ int status, i;
+
+ dev_dbg(&skt->dev, "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 -ENODEV;
+
+ if (status & SS_READY)
+ return 0;
+
+ msleep(unreset_check * 10);
+ }
+
+ dev_err(&skt->dev, "time out after reset\n");
+ return -ETIMEDOUT;
+}
+
+/*
+ * socket_setup() and socket_shutdown() 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.
+ * socket_shutdown() unconfigures a socket and turns off socket power.
+ */
+static void socket_shutdown(struct pcmcia_socket *s)
+{
+ int status;
+
+ dev_dbg(&s->dev, "shutdown\n");
+
+ if (s->callback)
+ s->callback->remove(s);
+
+ mutex_lock(&s->ops_mutex);
+ s->state &= SOCKET_INUSE | SOCKET_PRESENT;
+ msleep(shutdown_delay * 10);
+ s->state &= SOCKET_INUSE;
+
+ /* Blank out the socket state */
+ s->socket = dead_socket;
+ s->ops->init(s);
+ s->ops->set_socket(s, &s->socket);
+ s->lock_count = 0;
+ kfree(s->fake_cis);
+ s->fake_cis = NULL;
+ s->functions = 0;
+
+ /* From here on we can be sure that only we (that is, the
+ * pccardd thread) accesses this socket, and all (16-bit)
+ * PCMCIA interactions are gone. Therefore, release
+ * ops_mutex so that we don't get a sysfs-related lockdep
+ * warning.
+ */
+ mutex_unlock(&s->ops_mutex);
+
+#ifdef CONFIG_CARDBUS
+ cb_free(s);
+#endif
+
+ /* give socket some time to power down */
+ msleep(100);
+
+ s->ops->get_status(s, &status);
+ if (status & SS_POWERON) {
+ dev_err(&s->dev,
+ "*** DANGER *** unable to remove socket power\n");
+ }
+
+ s->state &= ~SOCKET_INUSE;
+}
+
+static int socket_setup(struct pcmcia_socket *skt, int initial_delay)
+{
+ int status, i;
+
+ dev_dbg(&skt->dev, "setup\n");
+
+ skt->ops->get_status(skt, &status);
+ if (!(status & SS_DETECT))
+ return -ENODEV;
+
+ msleep(initial_delay * 10);
+
+ for (i = 0; i < 100; i++) {
+ skt->ops->get_status(skt, &status);
+ if (!(status & SS_DETECT))
+ return -ENODEV;
+
+ if (!(status & SS_PENDING))
+ break;
+
+ msleep(100);
+ }
+
+ if (status & SS_PENDING) {
+ dev_err(&skt->dev, "voltage interrogation timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ if (status & SS_CARDBUS) {
+ if (!(skt->features & SS_CAP_CARDBUS)) {
+ dev_err(&skt->dev, "cardbus cards are not supported\n");
+ return -EINVAL;
+ }
+ skt->state |= SOCKET_CARDBUS;
+ } else
+ skt->state &= ~SOCKET_CARDBUS;
+
+ /*
+ * 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 {
+ dev_err(&skt->dev, "unsupported voltage key\n");
+ return -EIO;
+ }
+
+ if (skt->power_hook)
+ skt->power_hook(skt, HOOK_POWER_PRE);
+
+ 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)) {
+ dev_err(&skt->dev, "unable to apply power\n");
+ return -EIO;
+ }
+
+ status = socket_reset(skt);
+
+ if (skt->power_hook)
+ skt->power_hook(skt, HOOK_POWER_POST);
+
+ return status;
+}
+
+/*
+ * 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;
+
+ dev_dbg(&skt->dev, "insert\n");
+
+ mutex_lock(&skt->ops_mutex);
+ if (skt->state & SOCKET_INUSE) {
+ mutex_unlock(&skt->ops_mutex);
+ return -EINVAL;
+ }
+ skt->state |= SOCKET_INUSE;
+
+ ret = socket_setup(skt, setup_delay);
+ if (ret == 0) {
+ skt->state |= SOCKET_PRESENT;
+
+ dev_notice(&skt->dev, "pccard: %s card inserted into slot %d\n",
+ (skt->state & SOCKET_CARDBUS) ? "CardBus" : "PCMCIA",
+ skt->sock);
+
+#ifdef CONFIG_CARDBUS
+ if (skt->state & SOCKET_CARDBUS) {
+ cb_alloc(skt);
+ skt->state |= SOCKET_CARDBUS_CONFIG;
+ }
+#endif
+ dev_dbg(&skt->dev, "insert done\n");
+ mutex_unlock(&skt->ops_mutex);
+
+ if (!(skt->state & SOCKET_CARDBUS) && (skt->callback))
+ skt->callback->add(skt);
+ } else {
+ mutex_unlock(&skt->ops_mutex);
+ socket_shutdown(skt);
+ }
+
+ return ret;
+}
+
+static int socket_suspend(struct pcmcia_socket *skt)
+{
+ if ((skt->state & SOCKET_SUSPEND) && !(skt->state & SOCKET_IN_RESUME))
+ return -EBUSY;
+
+ mutex_lock(&skt->ops_mutex);
+ /* store state on first suspend, but not after spurious wakeups */
+ if (!(skt->state & SOCKET_IN_RESUME))
+ skt->suspended_state = skt->state;
+
+ skt->socket = dead_socket;
+ skt->ops->set_socket(skt, &skt->socket);
+ if (skt->ops->suspend)
+ skt->ops->suspend(skt);
+ skt->state |= SOCKET_SUSPEND;
+ skt->state &= ~SOCKET_IN_RESUME;
+ mutex_unlock(&skt->ops_mutex);
+ return 0;
+}
+
+static int socket_early_resume(struct pcmcia_socket *skt)
+{
+ mutex_lock(&skt->ops_mutex);
+ skt->socket = dead_socket;
+ skt->ops->init(skt);
+ skt->ops->set_socket(skt, &skt->socket);
+ if (skt->state & SOCKET_PRESENT)
+ skt->resume_status = socket_setup(skt, resume_delay);
+ skt->state |= SOCKET_IN_RESUME;
+ mutex_unlock(&skt->ops_mutex);
+ return 0;
+}
+
+static int socket_late_resume(struct pcmcia_socket *skt)
+{
+ int ret = 0;
+
+ mutex_lock(&skt->ops_mutex);
+ skt->state &= ~(SOCKET_SUSPEND | SOCKET_IN_RESUME);
+ mutex_unlock(&skt->ops_mutex);
+
+ if (!(skt->state & SOCKET_PRESENT)) {
+ ret = socket_insert(skt);
+ if (ret == -ENODEV)
+ ret = 0;
+ return ret;
+ }
+
+ if (skt->resume_status) {
+ socket_shutdown(skt);
+ return 0;
+ }
+
+ if (skt->suspended_state != skt->state) {
+ dev_dbg(&skt->dev,
+ "suspend state 0x%x != resume state 0x%x\n",
+ skt->suspended_state, skt->state);
+
+ socket_shutdown(skt);
+ return socket_insert(skt);
+ }
+
+ if (!(skt->state & SOCKET_CARDBUS) && (skt->callback))
+ ret = skt->callback->early_resume(skt);
+ return ret;
+}
+
+/*
+ * Finalize the resume. In case of a cardbus socket, we have
+ * to rebind the devices as we can't be certain that it has been
+ * replaced, or not.
+ */
+static int socket_complete_resume(struct pcmcia_socket *skt)
+{
+ int ret = 0;
+#ifdef CONFIG_CARDBUS
+ if (skt->state & SOCKET_CARDBUS) {
+ /* We can't be sure the CardBus card is the same
+ * as the one previously inserted. Therefore, remove
+ * and re-add... */
+ cb_free(skt);
+ ret = cb_alloc(skt);
+ if (ret)
+ cb_free(skt);
+ }
+#endif
+ return ret;
+}
+
+/*
+ * 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 err;
+ if (!(skt->state & SOCKET_SUSPEND))
+ return -EBUSY;
+
+ socket_early_resume(skt);
+ err = socket_late_resume(skt);
+ if (!err)
+ err = socket_complete_resume(skt);
+ return err;
+}
+
+static void socket_remove(struct pcmcia_socket *skt)
+{
+ dev_notice(&skt->dev, "pccard: card ejected from slot %d\n", skt->sock);
+ socket_shutdown(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;
+ int ret;
+
+ skt->thread = current;
+ skt->socket = dead_socket;
+ skt->ops->init(skt);
+ skt->ops->set_socket(skt, &skt->socket);
+
+ /* register with the device core */
+ ret = device_register(&skt->dev);
+ if (ret) {
+ dev_warn(&skt->dev, "PCMCIA: unable to register socket\n");
+ skt->thread = NULL;
+ complete(&skt->thread_done);
+ put_device(&skt->dev);
+ return 0;
+ }
+ ret = pccard_sysfs_add_socket(&skt->dev);
+ if (ret)
+ dev_warn(&skt->dev, "err %d adding socket attributes\n", ret);
+
+ complete(&skt->thread_done);
+
+ /* wait for userspace to catch up */
+ msleep(250);
+
+ set_freezable();
+ for (;;) {
+ unsigned long flags;
+ unsigned int events;
+ unsigned int sysfs_events;
+
+ spin_lock_irqsave(&skt->thread_lock, flags);
+ events = skt->thread_events;
+ skt->thread_events = 0;
+ sysfs_events = skt->sysfs_events;
+ skt->sysfs_events = 0;
+ spin_unlock_irqrestore(&skt->thread_lock, flags);
+
+ mutex_lock(&skt->skt_mutex);
+ if (events & SS_DETECT)
+ socket_detect_change(skt);
+
+ if (sysfs_events) {
+ if (sysfs_events & PCMCIA_UEVENT_EJECT)
+ socket_remove(skt);
+ if (sysfs_events & PCMCIA_UEVENT_INSERT)
+ socket_insert(skt);
+ if ((sysfs_events & PCMCIA_UEVENT_SUSPEND) &&
+ !(skt->state & SOCKET_CARDBUS)) {
+ if (skt->callback)
+ ret = skt->callback->suspend(skt);
+ else
+ ret = 0;
+ if (!ret) {
+ socket_suspend(skt);
+ msleep(100);
+ }
+ }
+ if ((sysfs_events & PCMCIA_UEVENT_RESUME) &&
+ !(skt->state & SOCKET_CARDBUS)) {
+ ret = socket_resume(skt);
+ if (!ret && skt->callback)
+ skt->callback->resume(skt);
+ }
+ if ((sysfs_events & PCMCIA_UEVENT_REQUERY) &&
+ !(skt->state & SOCKET_CARDBUS)) {
+ if (!ret && skt->callback)
+ skt->callback->requery(skt);
+ }
+ }
+ mutex_unlock(&skt->skt_mutex);
+
+ if (events || sysfs_events)
+ continue;
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (kthread_should_stop())
+ break;
+
+ schedule();
+
+ try_to_freeze();
+ }
+ /* make sure we are running before we exit */
+ __set_current_state(TASK_RUNNING);
+
+ /* shut down socket, if a device is still present */
+ if (skt->state & SOCKET_PRESENT) {
+ mutex_lock(&skt->skt_mutex);
+ socket_remove(skt);
+ mutex_unlock(&skt->skt_mutex);
+ }
+
+ /* remove from the device core */
+ pccard_sysfs_remove_socket(&skt->dev);
+ device_unregister(&skt->dev);
+
+ return 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)
+{
+ unsigned long flags;
+ dev_dbg(&s->dev, "parse_events: events %08x\n", events);
+ if (s->thread) {
+ spin_lock_irqsave(&s->thread_lock, flags);
+ s->thread_events |= events;
+ spin_unlock_irqrestore(&s->thread_lock, flags);
+
+ wake_up_process(s->thread);
+ }
+} /* pcmcia_parse_events */
+EXPORT_SYMBOL(pcmcia_parse_events);
+
+/**
+ * pcmcia_parse_uevents() - tell pccardd to issue manual commands
+ * @s: the PCMCIA socket we wan't to command
+ * @events: events to pass to pccardd
+ *
+ * userspace-issued insert, eject, suspend and resume commands must be
+ * handled by pccardd to avoid any sysfs-related deadlocks. Valid events
+ * are PCMCIA_UEVENT_EJECT (for eject), PCMCIA_UEVENT__INSERT (for insert),
+ * PCMCIA_UEVENT_RESUME (for resume), PCMCIA_UEVENT_SUSPEND (for suspend)
+ * and PCMCIA_UEVENT_REQUERY (for re-querying the PCMCIA card).
+ */
+void pcmcia_parse_uevents(struct pcmcia_socket *s, u_int events)
+{
+ unsigned long flags;
+ dev_dbg(&s->dev, "parse_uevents: events %08x\n", events);
+ if (s->thread) {
+ spin_lock_irqsave(&s->thread_lock, flags);
+ s->sysfs_events |= events;
+ spin_unlock_irqrestore(&s->thread_lock, flags);
+
+ wake_up_process(s->thread);
+ }
+}
+EXPORT_SYMBOL(pcmcia_parse_uevents);
+
+
+/* register pcmcia_callback */
+int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c)
+{
+ int ret = 0;
+
+ /* s->skt_mutex also protects s->callback */
+ mutex_lock(&s->skt_mutex);
+
+ if (c) {
+ /* registration */
+ if (s->callback) {
+ ret = -EBUSY;
+ goto err;
+ }
+
+ s->callback = c;
+
+ if ((s->state & (SOCKET_PRESENT|SOCKET_CARDBUS)) == SOCKET_PRESENT)
+ s->callback->add(s);
+ } else
+ s->callback = NULL;
+ err:
+ mutex_unlock(&s->skt_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(pccard_register_pcmcia);
+
+
+/* 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 pcmcia_reset_card(struct pcmcia_socket *skt)
+{
+ int ret;
+
+ dev_dbg(&skt->dev, "resetting socket\n");
+
+ mutex_lock(&skt->skt_mutex);
+ do {
+ if (!(skt->state & SOCKET_PRESENT)) {
+ dev_dbg(&skt->dev, "can't reset, not present\n");
+ ret = -ENODEV;
+ break;
+ }
+ if (skt->state & SOCKET_SUSPEND) {
+ dev_dbg(&skt->dev, "can't reset, suspended\n");
+ ret = -EBUSY;
+ break;
+ }
+ if (skt->state & SOCKET_CARDBUS) {
+ dev_dbg(&skt->dev, "can't reset, is cardbus\n");
+ ret = -EPERM;
+ break;
+ }
+
+ if (skt->callback)
+ skt->callback->suspend(skt);
+ mutex_lock(&skt->ops_mutex);
+ ret = socket_reset(skt);
+ mutex_unlock(&skt->ops_mutex);
+ if ((ret == 0) && (skt->callback))
+ skt->callback->resume(skt);
+
+ ret = 0;
+ } while (0);
+ mutex_unlock(&skt->skt_mutex);
+
+ return ret;
+} /* reset_card */
+EXPORT_SYMBOL(pcmcia_reset_card);
+
+
+static int pcmcia_socket_uevent(struct device *dev,
+ struct kobj_uevent_env *env)
+{
+ struct pcmcia_socket *s = container_of(dev, struct pcmcia_socket, dev);
+
+ if (add_uevent_var(env, "SOCKET_NO=%u", s->sock))
+ return -ENOMEM;
+
+ return 0;
+}
+
+
+static struct completion pcmcia_unload;
+
+static void pcmcia_release_socket_class(struct class *data)
+{
+ complete(&pcmcia_unload);
+}
+
+
+#ifdef CONFIG_PM
+
+static int __pcmcia_pm_op(struct device *dev,
+ int (*callback) (struct pcmcia_socket *skt))
+{
+ struct pcmcia_socket *s = container_of(dev, struct pcmcia_socket, dev);
+ int ret;
+
+ mutex_lock(&s->skt_mutex);
+ ret = callback(s);
+ mutex_unlock(&s->skt_mutex);
+
+ return ret;
+}
+
+static int pcmcia_socket_dev_suspend_noirq(struct device *dev)
+{
+ return __pcmcia_pm_op(dev, socket_suspend);
+}
+
+static int pcmcia_socket_dev_resume_noirq(struct device *dev)
+{
+ return __pcmcia_pm_op(dev, socket_early_resume);
+}
+
+static int __used pcmcia_socket_dev_resume(struct device *dev)
+{
+ return __pcmcia_pm_op(dev, socket_late_resume);
+}
+
+static void __used pcmcia_socket_dev_complete(struct device *dev)
+{
+ WARN(__pcmcia_pm_op(dev, socket_complete_resume),
+ "failed to complete resume");
+}
+
+static const struct dev_pm_ops pcmcia_socket_pm_ops = {
+ /* dev_resume may be called with IRQs enabled */
+ SET_SYSTEM_SLEEP_PM_OPS(NULL,
+ pcmcia_socket_dev_resume)
+
+ /* late suspend must be called with IRQs disabled */
+ .suspend_noirq = pcmcia_socket_dev_suspend_noirq,
+ .freeze_noirq = pcmcia_socket_dev_suspend_noirq,
+ .poweroff_noirq = pcmcia_socket_dev_suspend_noirq,
+
+ /* early resume must be called with IRQs disabled */
+ .resume_noirq = pcmcia_socket_dev_resume_noirq,
+ .thaw_noirq = pcmcia_socket_dev_resume_noirq,
+ .restore_noirq = pcmcia_socket_dev_resume_noirq,
+ .complete = pcmcia_socket_dev_complete,
+};
+
+#define PCMCIA_SOCKET_CLASS_PM_OPS (&pcmcia_socket_pm_ops)
+
+#else /* CONFIG_PM */
+
+#define PCMCIA_SOCKET_CLASS_PM_OPS NULL
+
+#endif /* CONFIG_PM */
+
+struct class pcmcia_socket_class = {
+ .name = "pcmcia_socket",
+ .dev_uevent = pcmcia_socket_uevent,
+ .dev_release = pcmcia_release_socket,
+ .class_release = pcmcia_release_socket_class,
+ .pm = PCMCIA_SOCKET_CLASS_PM_OPS,
+};
+EXPORT_SYMBOL(pcmcia_socket_class);
+
+
+static int __init init_pcmcia_cs(void)
+{
+ init_completion(&pcmcia_unload);
+ return class_register(&pcmcia_socket_class);
+}
+
+static void __exit exit_pcmcia_cs(void)
+{
+ class_unregister(&pcmcia_socket_class);
+ wait_for_completion(&pcmcia_unload);
+}
+
+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 000000000..580369f3c
--- /dev/null
+++ b/drivers/pcmcia/cs_internal.h
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * cs_internal.h -- definitions internal to the PCMCIA core modules
+ *
+ * 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 - 2010 Dominik Brodowski
+ *
+ * This file contains definitions _only_ needed by the PCMCIA core modules.
+ * It must not be included by PCMCIA socket drivers or by PCMCIA device
+ * drivers.
+ */
+
+#ifndef _LINUX_CS_INTERNAL_H
+#define _LINUX_CS_INTERNAL_H
+
+#include <linux/kref.h>
+
+/* Flags in client state */
+#define CLIENT_WIN_REQ(i) (0x1<<(i))
+
+/* Flag to access all functions */
+#define BIND_FN_ALL 0xff
+
+/* Each card function gets one of these guys */
+typedef struct config_t {
+ struct kref ref;
+ unsigned int state;
+
+ struct resource io[MAX_IO_WIN]; /* io ports */
+ struct resource mem[MAX_WIN]; /* mem areas */
+} config_t;
+
+
+struct cis_cache_entry {
+ struct list_head node;
+ unsigned int addr;
+ unsigned int len;
+ unsigned int attr;
+ unsigned char cache[];
+};
+
+struct pccard_resource_ops {
+ int (*validate_mem) (struct pcmcia_socket *s);
+ int (*find_io) (struct pcmcia_socket *s,
+ unsigned int attr,
+ unsigned int *base,
+ unsigned int num,
+ unsigned int align,
+ struct resource **parent);
+ struct resource* (*find_mem) (unsigned long base, unsigned long num,
+ unsigned long align, int low,
+ struct pcmcia_socket *s);
+ int (*init) (struct pcmcia_socket *s);
+ void (*exit) (struct pcmcia_socket *s);
+};
+
+/* 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_IN_RESUME 0x0040
+#define SOCKET_SUSPEND 0x0080
+#define SOCKET_WIN_REQ(i) (0x0100<<(i))
+#define SOCKET_CARDBUS 0x8000
+#define SOCKET_CARDBUS_CONFIG 0x10000
+
+
+/*
+ * Stuff internal to module "pcmcia_rsrc":
+ */
+extern int static_init(struct pcmcia_socket *s);
+extern struct resource *pcmcia_make_resource(resource_size_t start,
+ resource_size_t end,
+ unsigned long flags, const char *name);
+
+/*
+ * Stuff internal to module "pcmcia_core":
+ */
+
+/* socket_sysfs.c */
+extern int pccard_sysfs_add_socket(struct device *dev);
+extern void pccard_sysfs_remove_socket(struct device *dev);
+
+/* cardbus.c */
+int cb_alloc(struct pcmcia_socket *s);
+void cb_free(struct pcmcia_socket *s);
+
+
+
+/*
+ * Stuff exported by module "pcmcia_core" to module "pcmcia"
+ */
+
+struct pcmcia_callback{
+ struct module *owner;
+ int (*add) (struct pcmcia_socket *s);
+ int (*remove) (struct pcmcia_socket *s);
+ void (*requery) (struct pcmcia_socket *s);
+ int (*validate) (struct pcmcia_socket *s, unsigned int *i);
+ int (*suspend) (struct pcmcia_socket *s);
+ int (*early_resume) (struct pcmcia_socket *s);
+ int (*resume) (struct pcmcia_socket *s);
+};
+
+/* cs.c */
+extern struct rw_semaphore pcmcia_socket_list_rwsem;
+extern struct list_head pcmcia_socket_list;
+extern struct class pcmcia_socket_class;
+
+int pccard_register_pcmcia(struct pcmcia_socket *s, struct pcmcia_callback *c);
+struct pcmcia_socket *pcmcia_get_socket_by_nr(unsigned int nr);
+
+void pcmcia_parse_uevents(struct pcmcia_socket *socket, unsigned int events);
+#define PCMCIA_UEVENT_EJECT 0x0001
+#define PCMCIA_UEVENT_INSERT 0x0002
+#define PCMCIA_UEVENT_SUSPEND 0x0004
+#define PCMCIA_UEVENT_RESUME 0x0008
+#define PCMCIA_UEVENT_REQUERY 0x0010
+
+struct pcmcia_socket *pcmcia_get_socket(struct pcmcia_socket *skt);
+void pcmcia_put_socket(struct pcmcia_socket *skt);
+
+/*
+ * Stuff internal to module "pcmcia".
+ */
+/* ds.c */
+extern struct bus_type pcmcia_bus_type;
+
+struct pcmcia_device;
+
+/* pcmcia_resource.c */
+extern int pcmcia_release_configuration(struct pcmcia_device *p_dev);
+extern int pcmcia_validate_mem(struct pcmcia_socket *s);
+extern struct resource *pcmcia_find_mem_region(u_long base,
+ u_long num,
+ u_long align,
+ int low,
+ struct pcmcia_socket *s);
+
+void pcmcia_cleanup_irq(struct pcmcia_socket *s);
+int pcmcia_setup_irq(struct pcmcia_device *p_dev);
+
+/* cistpl.c */
+extern const struct bin_attribute pccard_cis_attr;
+
+int pcmcia_read_cis_mem(struct pcmcia_socket *s, int attr,
+ u_int addr, u_int len, void *ptr);
+int pcmcia_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 pccard_read_tuple(struct pcmcia_socket *s, unsigned int function,
+ cisdata_t code, void *parse);
+int pcmcia_replace_cis(struct pcmcia_socket *s,
+ const u8 *data, const size_t len);
+int pccard_validate_cis(struct pcmcia_socket *s, unsigned int *count);
+int verify_cis_cache(struct pcmcia_socket *s);
+
+int pccard_get_first_tuple(struct pcmcia_socket *s, unsigned int function,
+ tuple_t *tuple);
+
+int pccard_get_next_tuple(struct pcmcia_socket *s, unsigned int function,
+ tuple_t *tuple);
+
+int pccard_get_tuple_data(struct pcmcia_socket *s, tuple_t *tuple);
+
+#endif /* _LINUX_CS_INTERNAL_H */
diff --git a/drivers/pcmcia/db1xxx_ss.c b/drivers/pcmcia/db1xxx_ss.c
new file mode 100644
index 000000000..a7c7c7cd2
--- /dev/null
+++ b/drivers/pcmcia/db1xxx_ss.c
@@ -0,0 +1,603 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PCMCIA socket code for the Alchemy Db1xxx/Pb1xxx boards.
+ *
+ * Copyright (c) 2009 Manuel Lauss <manuel.lauss@gmail.com>
+ *
+ */
+
+/* This is a fairly generic PCMCIA socket driver suitable for the
+ * following Alchemy Development boards:
+ * Db1000, Db/Pb1500, Db/Pb1100, Db/Pb1550, Db/Pb1200, Db1300
+ *
+ * The Db1000 is used as a reference: Per-socket card-, carddetect- and
+ * statuschange IRQs connected to SoC GPIOs, control and status register
+ * bits arranged in per-socket groups in an external PLD. All boards
+ * listed here use this layout, including bit positions and meanings.
+ * Of course there are exceptions in later boards:
+ *
+ * - Pb1100/Pb1500: single socket only; voltage key bits VS are
+ * at STATUS[5:4] (instead of STATUS[1:0]).
+ * - Au1200-based: additional card-eject irqs, irqs not gpios!
+ * - Db1300: Db1200-like, no pwr ctrl, single socket (#1).
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/pm.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/resource.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <pcmcia/ss.h>
+
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-db1x00/bcsr.h>
+
+#define MEM_MAP_SIZE 0x400000
+#define IO_MAP_SIZE 0x1000
+
+struct db1x_pcmcia_sock {
+ struct pcmcia_socket socket;
+ int nr; /* socket number */
+ void *virt_io;
+
+ phys_addr_t phys_io;
+ phys_addr_t phys_attr;
+ phys_addr_t phys_mem;
+
+ /* previous flags for set_socket() */
+ unsigned int old_flags;
+
+ /* interrupt sources: linux irq numbers! */
+ int insert_irq; /* default carddetect irq */
+ int stschg_irq; /* card-status-change irq */
+ int card_irq; /* card irq */
+ int eject_irq; /* db1200/pb1200 have these */
+ int insert_gpio; /* db1000 carddetect gpio */
+
+#define BOARD_TYPE_DEFAULT 0 /* most boards */
+#define BOARD_TYPE_DB1200 1 /* IRQs aren't gpios */
+#define BOARD_TYPE_PB1100 2 /* VS bits slightly different */
+#define BOARD_TYPE_DB1300 3 /* no power control */
+ int board_type;
+};
+
+#define to_db1x_socket(x) container_of(x, struct db1x_pcmcia_sock, socket)
+
+static int db1300_card_inserted(struct db1x_pcmcia_sock *sock)
+{
+ return bcsr_read(BCSR_SIGSTAT) & (1 << 8);
+}
+
+/* DB/PB1200: check CPLD SIGSTATUS register bit 10/12 */
+static int db1200_card_inserted(struct db1x_pcmcia_sock *sock)
+{
+ unsigned short sigstat;
+
+ sigstat = bcsr_read(BCSR_SIGSTAT);
+ return sigstat & 1 << (8 + 2 * sock->nr);
+}
+
+/* carddetect gpio: low-active */
+static int db1000_card_inserted(struct db1x_pcmcia_sock *sock)
+{
+ return !gpio_get_value(sock->insert_gpio);
+}
+
+static int db1x_card_inserted(struct db1x_pcmcia_sock *sock)
+{
+ switch (sock->board_type) {
+ case BOARD_TYPE_DB1200:
+ return db1200_card_inserted(sock);
+ case BOARD_TYPE_DB1300:
+ return db1300_card_inserted(sock);
+ default:
+ return db1000_card_inserted(sock);
+ }
+}
+
+/* STSCHG tends to bounce heavily when cards are inserted/ejected.
+ * To avoid this, the interrupt is normally disabled and only enabled
+ * after reset to a card has been de-asserted.
+ */
+static inline void set_stschg(struct db1x_pcmcia_sock *sock, int en)
+{
+ if (sock->stschg_irq != -1) {
+ if (en)
+ enable_irq(sock->stschg_irq);
+ else
+ disable_irq(sock->stschg_irq);
+ }
+}
+
+static irqreturn_t db1000_pcmcia_cdirq(int irq, void *data)
+{
+ struct db1x_pcmcia_sock *sock = data;
+
+ pcmcia_parse_events(&sock->socket, SS_DETECT);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t db1000_pcmcia_stschgirq(int irq, void *data)
+{
+ struct db1x_pcmcia_sock *sock = data;
+
+ pcmcia_parse_events(&sock->socket, SS_STSCHG);
+
+ return IRQ_HANDLED;
+}
+
+/* Db/Pb1200 have separate per-socket insertion and ejection
+ * interrupts which stay asserted as long as the card is
+ * inserted/missing. The one which caused us to be called
+ * needs to be disabled and the other one enabled.
+ */
+static irqreturn_t db1200_pcmcia_cdirq(int irq, void *data)
+{
+ disable_irq_nosync(irq);
+ return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t db1200_pcmcia_cdirq_fn(int irq, void *data)
+{
+ struct db1x_pcmcia_sock *sock = data;
+
+ /* Wait a bit for the signals to stop bouncing. */
+ msleep(100);
+ if (irq == sock->insert_irq)
+ enable_irq(sock->eject_irq);
+ else
+ enable_irq(sock->insert_irq);
+
+ pcmcia_parse_events(&sock->socket, SS_DETECT);
+
+ return IRQ_HANDLED;
+}
+
+static int db1x_pcmcia_setup_irqs(struct db1x_pcmcia_sock *sock)
+{
+ int ret;
+
+ if (sock->stschg_irq != -1) {
+ ret = request_irq(sock->stschg_irq, db1000_pcmcia_stschgirq,
+ 0, "pcmcia_stschg", sock);
+ if (ret)
+ return ret;
+ }
+
+ /* Db/Pb1200 have separate per-socket insertion and ejection
+ * interrupts, which should show edge behaviour but don't.
+ * So interrupts are disabled until both insertion and
+ * ejection handler have been registered and the currently
+ * active one disabled.
+ */
+ if ((sock->board_type == BOARD_TYPE_DB1200) ||
+ (sock->board_type == BOARD_TYPE_DB1300)) {
+ ret = request_threaded_irq(sock->insert_irq, db1200_pcmcia_cdirq,
+ db1200_pcmcia_cdirq_fn, 0, "pcmcia_insert", sock);
+ if (ret)
+ goto out1;
+
+ ret = request_threaded_irq(sock->eject_irq, db1200_pcmcia_cdirq,
+ db1200_pcmcia_cdirq_fn, 0, "pcmcia_eject", sock);
+ if (ret) {
+ free_irq(sock->insert_irq, sock);
+ goto out1;
+ }
+
+ /* enable the currently silent one */
+ if (db1x_card_inserted(sock))
+ enable_irq(sock->eject_irq);
+ else
+ enable_irq(sock->insert_irq);
+ } else {
+ /* all other (older) Db1x00 boards use a GPIO to show
+ * card detection status: use both-edge triggers.
+ */
+ irq_set_irq_type(sock->insert_irq, IRQ_TYPE_EDGE_BOTH);
+ ret = request_irq(sock->insert_irq, db1000_pcmcia_cdirq,
+ 0, "pcmcia_carddetect", sock);
+
+ if (ret)
+ goto out1;
+ }
+
+ return 0; /* all done */
+
+out1:
+ if (sock->stschg_irq != -1)
+ free_irq(sock->stschg_irq, sock);
+
+ return ret;
+}
+
+static void db1x_pcmcia_free_irqs(struct db1x_pcmcia_sock *sock)
+{
+ if (sock->stschg_irq != -1)
+ free_irq(sock->stschg_irq, sock);
+
+ free_irq(sock->insert_irq, sock);
+ if (sock->eject_irq != -1)
+ free_irq(sock->eject_irq, sock);
+}
+
+/*
+ * configure a PCMCIA socket on the Db1x00 series of boards (and
+ * compatibles).
+ *
+ * 2 external registers are involved:
+ * pcmcia_status (offset 0x04): bits [0:1/2:3]: read card voltage id
+ * pcmcia_control(offset 0x10):
+ * bits[0:1] set vcc for card
+ * bits[2:3] set vpp for card
+ * bit 4: enable data buffers
+ * bit 7: reset# for card
+ * add 8 for second socket.
+ */
+static int db1x_pcmcia_configure(struct pcmcia_socket *skt,
+ struct socket_state_t *state)
+{
+ struct db1x_pcmcia_sock *sock = to_db1x_socket(skt);
+ unsigned short cr_clr, cr_set;
+ unsigned int changed;
+ int v, p, ret;
+
+ /* card voltage setup */
+ cr_clr = (0xf << (sock->nr * 8)); /* clear voltage settings */
+ cr_set = 0;
+ v = p = ret = 0;
+
+ switch (state->Vcc) {
+ case 50:
+ ++v;
+ fallthrough;
+ case 33:
+ ++v;
+ fallthrough;
+ case 0:
+ break;
+ default:
+ printk(KERN_INFO "pcmcia%d unsupported Vcc %d\n",
+ sock->nr, state->Vcc);
+ }
+
+ switch (state->Vpp) {
+ case 12:
+ ++p;
+ fallthrough;
+ case 33:
+ case 50:
+ ++p;
+ fallthrough;
+ case 0:
+ break;
+ default:
+ printk(KERN_INFO "pcmcia%d unsupported Vpp %d\n",
+ sock->nr, state->Vpp);
+ }
+
+ /* sanity check: Vpp must be 0, 12, or Vcc */
+ if (((state->Vcc == 33) && (state->Vpp == 50)) ||
+ ((state->Vcc == 50) && (state->Vpp == 33))) {
+ printk(KERN_INFO "pcmcia%d bad Vcc/Vpp combo (%d %d)\n",
+ sock->nr, state->Vcc, state->Vpp);
+ v = p = 0;
+ ret = -EINVAL;
+ }
+
+ /* create new voltage code */
+ if (sock->board_type != BOARD_TYPE_DB1300)
+ cr_set |= ((v << 2) | p) << (sock->nr * 8);
+
+ changed = state->flags ^ sock->old_flags;
+
+ if (changed & SS_RESET) {
+ if (state->flags & SS_RESET) {
+ set_stschg(sock, 0);
+ /* assert reset, disable io buffers */
+ cr_clr |= (1 << (7 + (sock->nr * 8)));
+ cr_clr |= (1 << (4 + (sock->nr * 8)));
+ } else {
+ /* de-assert reset, enable io buffers */
+ cr_set |= 1 << (7 + (sock->nr * 8));
+ cr_set |= 1 << (4 + (sock->nr * 8));
+ }
+ }
+
+ /* update PCMCIA configuration */
+ bcsr_mod(BCSR_PCMCIA, cr_clr, cr_set);
+
+ sock->old_flags = state->flags;
+
+ /* reset was taken away: give card time to initialize properly */
+ if ((changed & SS_RESET) && !(state->flags & SS_RESET)) {
+ msleep(500);
+ set_stschg(sock, 1);
+ }
+
+ return ret;
+}
+
+/* VCC bits at [3:2]/[11:10] */
+#define GET_VCC(cr, socknr) \
+ ((((cr) >> 2) >> ((socknr) * 8)) & 3)
+
+/* VS bits at [0:1]/[3:2] */
+#define GET_VS(sr, socknr) \
+ (((sr) >> (2 * (socknr))) & 3)
+
+/* reset bits at [7]/[15] */
+#define GET_RESET(cr, socknr) \
+ ((cr) & (1 << (7 + (8 * (socknr)))))
+
+static int db1x_pcmcia_get_status(struct pcmcia_socket *skt,
+ unsigned int *value)
+{
+ struct db1x_pcmcia_sock *sock = to_db1x_socket(skt);
+ unsigned short cr, sr;
+ unsigned int status;
+
+ status = db1x_card_inserted(sock) ? SS_DETECT : 0;
+
+ cr = bcsr_read(BCSR_PCMCIA);
+ sr = bcsr_read(BCSR_STATUS);
+
+ /* PB1100/PB1500: voltage key bits are at [5:4] */
+ if (sock->board_type == BOARD_TYPE_PB1100)
+ sr >>= 4;
+
+ /* determine card type */
+ switch (GET_VS(sr, sock->nr)) {
+ case 0:
+ case 2:
+ status |= SS_3VCARD; /* 3V card */
+ case 3:
+ break; /* 5V card: set nothing */
+ default:
+ status |= SS_XVCARD; /* treated as unsupported in core */
+ }
+
+ /* if Vcc is not zero, we have applied power to a card */
+ status |= GET_VCC(cr, sock->nr) ? SS_POWERON : 0;
+
+ /* DB1300: power always on, but don't tell when no card present */
+ if ((sock->board_type == BOARD_TYPE_DB1300) && (status & SS_DETECT))
+ status = SS_POWERON | SS_3VCARD | SS_DETECT;
+
+ /* reset de-asserted? then we're ready */
+ status |= (GET_RESET(cr, sock->nr)) ? SS_READY : SS_RESET;
+
+ *value = status;
+
+ return 0;
+}
+
+static int db1x_pcmcia_sock_init(struct pcmcia_socket *skt)
+{
+ return 0;
+}
+
+static int db1x_pcmcia_sock_suspend(struct pcmcia_socket *skt)
+{
+ return 0;
+}
+
+static int au1x00_pcmcia_set_io_map(struct pcmcia_socket *skt,
+ struct pccard_io_map *map)
+{
+ struct db1x_pcmcia_sock *sock = to_db1x_socket(skt);
+
+ map->start = (u32)sock->virt_io;
+ map->stop = map->start + IO_MAP_SIZE;
+
+ return 0;
+}
+
+static int au1x00_pcmcia_set_mem_map(struct pcmcia_socket *skt,
+ struct pccard_mem_map *map)
+{
+ struct db1x_pcmcia_sock *sock = to_db1x_socket(skt);
+
+ if (map->flags & MAP_ATTRIB)
+ map->static_start = sock->phys_attr + map->card_start;
+ else
+ map->static_start = sock->phys_mem + map->card_start;
+
+ return 0;
+}
+
+static struct pccard_operations db1x_pcmcia_operations = {
+ .init = db1x_pcmcia_sock_init,
+ .suspend = db1x_pcmcia_sock_suspend,
+ .get_status = db1x_pcmcia_get_status,
+ .set_socket = db1x_pcmcia_configure,
+ .set_io_map = au1x00_pcmcia_set_io_map,
+ .set_mem_map = au1x00_pcmcia_set_mem_map,
+};
+
+static int db1x_pcmcia_socket_probe(struct platform_device *pdev)
+{
+ struct db1x_pcmcia_sock *sock;
+ struct resource *r;
+ int ret, bid;
+
+ sock = kzalloc(sizeof(struct db1x_pcmcia_sock), GFP_KERNEL);
+ if (!sock)
+ return -ENOMEM;
+
+ sock->nr = pdev->id;
+
+ bid = BCSR_WHOAMI_BOARD(bcsr_read(BCSR_WHOAMI));
+ switch (bid) {
+ case BCSR_WHOAMI_PB1500:
+ case BCSR_WHOAMI_PB1500R2:
+ case BCSR_WHOAMI_PB1100:
+ sock->board_type = BOARD_TYPE_PB1100;
+ break;
+ case BCSR_WHOAMI_DB1000 ... BCSR_WHOAMI_PB1550_SDR:
+ sock->board_type = BOARD_TYPE_DEFAULT;
+ break;
+ case BCSR_WHOAMI_PB1200 ... BCSR_WHOAMI_DB1200:
+ sock->board_type = BOARD_TYPE_DB1200;
+ break;
+ case BCSR_WHOAMI_DB1300:
+ sock->board_type = BOARD_TYPE_DB1300;
+ break;
+ default:
+ printk(KERN_INFO "db1xxx-ss: unknown board %d!\n", bid);
+ ret = -ENODEV;
+ goto out0;
+ };
+
+ /*
+ * gather resources necessary and optional nice-to-haves to
+ * operate a socket:
+ * This includes IRQs for Carddetection/ejection, the card
+ * itself and optional status change detection.
+ * Also, the memory areas covered by a socket. For these
+ * we require the real 36bit addresses (see the au1000.h
+ * header for more information).
+ */
+
+ /* card: irq assigned to the card itself. */
+ r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "card");
+ sock->card_irq = r ? r->start : 0;
+
+ /* insert: irq which triggers on card insertion/ejection
+ * BIG FAT NOTE: on DB1000/1100/1500/1550 we pass a GPIO here!
+ */
+ r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "insert");
+ sock->insert_irq = r ? r->start : -1;
+ if (sock->board_type == BOARD_TYPE_DEFAULT) {
+ sock->insert_gpio = r ? r->start : -1;
+ sock->insert_irq = r ? gpio_to_irq(r->start) : -1;
+ }
+
+ /* stschg: irq which trigger on card status change (optional) */
+ r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "stschg");
+ sock->stschg_irq = r ? r->start : -1;
+
+ /* eject: irq which triggers on ejection (DB1200/PB1200 only) */
+ r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "eject");
+ sock->eject_irq = r ? r->start : -1;
+
+ ret = -ENODEV;
+
+ /* 36bit PCMCIA Attribute area address */
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcmcia-attr");
+ if (!r) {
+ printk(KERN_ERR "pcmcia%d has no 'pseudo-attr' resource!\n",
+ sock->nr);
+ goto out0;
+ }
+ sock->phys_attr = r->start;
+
+ /* 36bit PCMCIA Memory area address */
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcmcia-mem");
+ if (!r) {
+ printk(KERN_ERR "pcmcia%d has no 'pseudo-mem' resource!\n",
+ sock->nr);
+ goto out0;
+ }
+ sock->phys_mem = r->start;
+
+ /* 36bit PCMCIA IO area address */
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcmcia-io");
+ if (!r) {
+ printk(KERN_ERR "pcmcia%d has no 'pseudo-io' resource!\n",
+ sock->nr);
+ goto out0;
+ }
+ sock->phys_io = r->start;
+
+ /*
+ * 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.
+ */
+ sock->virt_io = (void *)(ioremap(sock->phys_io, IO_MAP_SIZE) -
+ mips_io_port_base);
+
+ if (!sock->virt_io) {
+ printk(KERN_ERR "pcmcia%d: cannot remap IO area\n",
+ sock->nr);
+ ret = -ENOMEM;
+ goto out0;
+ }
+
+ sock->socket.ops = &db1x_pcmcia_operations;
+ sock->socket.owner = THIS_MODULE;
+ sock->socket.pci_irq = sock->card_irq;
+ sock->socket.features = SS_CAP_STATIC_MAP | SS_CAP_PCCARD;
+ sock->socket.map_size = MEM_MAP_SIZE;
+ sock->socket.io_offset = (unsigned long)sock->virt_io;
+ sock->socket.dev.parent = &pdev->dev;
+ sock->socket.resource_ops = &pccard_static_ops;
+
+ platform_set_drvdata(pdev, sock);
+
+ ret = db1x_pcmcia_setup_irqs(sock);
+ if (ret) {
+ printk(KERN_ERR "pcmcia%d cannot setup interrupts\n",
+ sock->nr);
+ goto out1;
+ }
+
+ set_stschg(sock, 0);
+
+ ret = pcmcia_register_socket(&sock->socket);
+ if (ret) {
+ printk(KERN_ERR "pcmcia%d failed to register\n", sock->nr);
+ goto out2;
+ }
+
+ printk(KERN_INFO "Alchemy Db/Pb1xxx pcmcia%d @ io/attr/mem %09llx"
+ "(%p) %09llx %09llx card/insert/stschg/eject irqs @ %d "
+ "%d %d %d\n", sock->nr, sock->phys_io, sock->virt_io,
+ sock->phys_attr, sock->phys_mem, sock->card_irq,
+ sock->insert_irq, sock->stschg_irq, sock->eject_irq);
+
+ return 0;
+
+out2:
+ db1x_pcmcia_free_irqs(sock);
+out1:
+ iounmap((void *)(sock->virt_io + (u32)mips_io_port_base));
+out0:
+ kfree(sock);
+ return ret;
+}
+
+static int db1x_pcmcia_socket_remove(struct platform_device *pdev)
+{
+ struct db1x_pcmcia_sock *sock = platform_get_drvdata(pdev);
+
+ db1x_pcmcia_free_irqs(sock);
+ pcmcia_unregister_socket(&sock->socket);
+ iounmap((void *)(sock->virt_io + (u32)mips_io_port_base));
+ kfree(sock);
+
+ return 0;
+}
+
+static struct platform_driver db1x_pcmcia_socket_driver = {
+ .driver = {
+ .name = "db1xxx_pcmcia",
+ },
+ .probe = db1x_pcmcia_socket_probe,
+ .remove = db1x_pcmcia_socket_remove,
+};
+
+module_platform_driver(db1x_pcmcia_socket_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("PCMCIA Socket Services for Alchemy Db/Pb1x00 boards");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/drivers/pcmcia/ds.c b/drivers/pcmcia/ds.c
new file mode 100644
index 000000000..bf2e856f5
--- /dev/null
+++ b/drivers/pcmcia/ds.c
@@ -0,0 +1,1461 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ds.c -- 16-bit PCMCIA core support
+ *
+ * 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 - 2010 Dominik Brodowski
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/crc32.h>
+#include <linux/firmware.h>
+#include <linux/kref.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.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");
+
+
+/*====================================================================*/
+
+static void pcmcia_check_driver(struct pcmcia_driver *p_drv)
+{
+ const struct pcmcia_device_id *did = p_drv->id_table;
+ unsigned int i;
+ u32 hash;
+
+ if (!p_drv->probe || !p_drv->remove)
+ printk(KERN_DEBUG "pcmcia: %s lacks a requisite callback "
+ "function\n", p_drv->name);
+
+ while (did && did->match_flags) {
+ for (i = 0; i < 4; i++) {
+ if (!did->prod_id[i])
+ continue;
+
+ hash = crc32(0, did->prod_id[i], strlen(did->prod_id[i]));
+ if (hash == did->prod_id_hash[i])
+ continue;
+
+ printk(KERN_DEBUG "pcmcia: %s: invalid hash for "
+ "product string \"%s\": is 0x%x, should "
+ "be 0x%x\n", p_drv->name, did->prod_id[i],
+ did->prod_id_hash[i], hash);
+ printk(KERN_DEBUG "pcmcia: see "
+ "Documentation/pcmcia/devicetable.rst for "
+ "details\n");
+ }
+ did++;
+ }
+
+ return;
+}
+
+
+/*======================================================================*/
+
+
+struct pcmcia_dynid {
+ struct list_head node;
+ struct pcmcia_device_id id;
+};
+
+/**
+ * pcmcia_store_new_id - add a new PCMCIA device ID to this driver and re-probe devices
+ * @driver: target device driver
+ * @buf: buffer for scanning device ID data
+ * @count: input size
+ *
+ * Adds a new dynamic PCMCIA device ID to this driver,
+ * and causes the driver to probe for all devices again.
+ */
+static ssize_t
+new_id_store(struct device_driver *driver, const char *buf, size_t count)
+{
+ struct pcmcia_dynid *dynid;
+ struct pcmcia_driver *pdrv = to_pcmcia_drv(driver);
+ __u16 match_flags, manf_id, card_id;
+ __u8 func_id, function, device_no;
+ __u32 prod_id_hash[4] = {0, 0, 0, 0};
+ int fields = 0;
+ int retval = 0;
+
+ fields = sscanf(buf, "%hx %hx %hx %hhx %hhx %hhx %x %x %x %x",
+ &match_flags, &manf_id, &card_id, &func_id, &function, &device_no,
+ &prod_id_hash[0], &prod_id_hash[1], &prod_id_hash[2], &prod_id_hash[3]);
+ if (fields < 6)
+ return -EINVAL;
+
+ dynid = kzalloc(sizeof(struct pcmcia_dynid), GFP_KERNEL);
+ if (!dynid)
+ return -ENOMEM;
+
+ dynid->id.match_flags = match_flags;
+ dynid->id.manf_id = manf_id;
+ dynid->id.card_id = card_id;
+ dynid->id.func_id = func_id;
+ dynid->id.function = function;
+ dynid->id.device_no = device_no;
+ memcpy(dynid->id.prod_id_hash, prod_id_hash, sizeof(__u32) * 4);
+
+ mutex_lock(&pdrv->dynids.lock);
+ list_add_tail(&dynid->node, &pdrv->dynids.list);
+ mutex_unlock(&pdrv->dynids.lock);
+
+ retval = driver_attach(&pdrv->drv);
+
+ if (retval)
+ return retval;
+ return count;
+}
+static DRIVER_ATTR_WO(new_id);
+
+static void
+pcmcia_free_dynids(struct pcmcia_driver *drv)
+{
+ struct pcmcia_dynid *dynid, *n;
+
+ mutex_lock(&drv->dynids.lock);
+ list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) {
+ list_del(&dynid->node);
+ kfree(dynid);
+ }
+ mutex_unlock(&drv->dynids.lock);
+}
+
+static int
+pcmcia_create_newid_file(struct pcmcia_driver *drv)
+{
+ int error = 0;
+ if (drv->probe != NULL)
+ error = driver_create_file(&drv->drv, &driver_attr_new_id);
+ return error;
+}
+
+static void
+pcmcia_remove_newid_file(struct pcmcia_driver *drv)
+{
+ driver_remove_file(&drv->drv, &driver_attr_new_id);
+}
+
+/**
+ * pcmcia_register_driver - register a PCMCIA driver with the bus core
+ * @driver: the &driver being registered
+ *
+ * Registers a PCMCIA driver with the PCMCIA bus core.
+ */
+int pcmcia_register_driver(struct pcmcia_driver *driver)
+{
+ int error;
+
+ if (!driver)
+ return -EINVAL;
+
+ pcmcia_check_driver(driver);
+
+ /* initialize common fields */
+ driver->drv.bus = &pcmcia_bus_type;
+ driver->drv.owner = driver->owner;
+ driver->drv.name = driver->name;
+ mutex_init(&driver->dynids.lock);
+ INIT_LIST_HEAD(&driver->dynids.list);
+
+ pr_debug("registering driver %s\n", driver->name);
+
+ error = driver_register(&driver->drv);
+ if (error < 0)
+ return error;
+
+ error = pcmcia_create_newid_file(driver);
+ if (error)
+ driver_unregister(&driver->drv);
+
+ return error;
+}
+EXPORT_SYMBOL(pcmcia_register_driver);
+
+/**
+ * pcmcia_unregister_driver - unregister a PCMCIA driver with the bus core
+ * @driver: the &driver being unregistered
+ */
+void pcmcia_unregister_driver(struct pcmcia_driver *driver)
+{
+ pr_debug("unregistering driver %s\n", driver->name);
+ pcmcia_remove_newid_file(driver);
+ driver_unregister(&driver->drv);
+ pcmcia_free_dynids(driver);
+}
+EXPORT_SYMBOL(pcmcia_unregister_driver);
+
+
+/* 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_function(struct kref *ref)
+{
+ struct config_t *c = container_of(ref, struct config_t, ref);
+ pr_debug("releasing config_t\n");
+ kfree(c);
+}
+
+static void pcmcia_release_dev(struct device *dev)
+{
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+ int i;
+ dev_dbg(dev, "releasing device\n");
+ pcmcia_put_socket(p_dev->socket);
+ for (i = 0; i < 4; i++)
+ kfree(p_dev->prod_id[i]);
+ kfree(p_dev->devname);
+ kref_put(&p_dev->function_config->ref, pcmcia_release_function);
+ kfree(p_dev);
+}
+
+
+static int pcmcia_device_probe(struct device *dev)
+{
+ struct pcmcia_device *p_dev;
+ struct pcmcia_driver *p_drv;
+ struct pcmcia_socket *s;
+ cistpl_config_t cis_config;
+ int ret = 0;
+
+ dev = get_device(dev);
+ if (!dev)
+ return -ENODEV;
+
+ p_dev = to_pcmcia_dev(dev);
+ p_drv = to_pcmcia_drv(dev->driver);
+ s = p_dev->socket;
+
+ dev_dbg(dev, "trying to bind to %s\n", p_drv->name);
+
+ if ((!p_drv->probe) || (!p_dev->function_config) ||
+ (!try_module_get(p_drv->owner))) {
+ ret = -EINVAL;
+ goto put_dev;
+ }
+
+ /* set up some more device information */
+ ret = pccard_read_tuple(p_dev->socket, p_dev->func, CISTPL_CONFIG,
+ &cis_config);
+ if (!ret) {
+ p_dev->config_base = cis_config.base;
+ p_dev->config_regs = cis_config.rmask[0];
+ dev_dbg(dev, "base %x, regs %x", p_dev->config_base,
+ p_dev->config_regs);
+ } else {
+ dev_info(dev,
+ "pcmcia: could not parse base and rmask0 of CIS\n");
+ p_dev->config_base = 0;
+ p_dev->config_regs = 0;
+ }
+
+ ret = p_drv->probe(p_dev);
+ if (ret) {
+ dev_dbg(dev, "binding to %s failed with %d\n",
+ p_drv->name, ret);
+ goto put_module;
+ }
+ dev_dbg(dev, "%s bound: Vpp %d.%d, idx %x, IRQ %d", p_drv->name,
+ p_dev->vpp/10, p_dev->vpp%10, p_dev->config_index, p_dev->irq);
+ dev_dbg(dev, "resources: ioport %pR %pR iomem %pR %pR %pR",
+ p_dev->resource[0], p_dev->resource[1], p_dev->resource[2],
+ p_dev->resource[3], p_dev->resource[4]);
+
+ mutex_lock(&s->ops_mutex);
+ if ((s->pcmcia_pfc) &&
+ (p_dev->socket->device_count == 1) && (p_dev->device_no == 0))
+ pcmcia_parse_uevents(s, PCMCIA_UEVENT_REQUERY);
+ mutex_unlock(&s->ops_mutex);
+
+put_module:
+ if (ret)
+ module_put(p_drv->owner);
+put_dev:
+ if (ret)
+ put_device(dev);
+ return ret;
+}
+
+
+/*
+ * Removes a PCMCIA card from the device tree and socket list.
+ */
+static void pcmcia_card_remove(struct pcmcia_socket *s, struct pcmcia_device *leftover)
+{
+ struct pcmcia_device *p_dev;
+ struct pcmcia_device *tmp;
+
+ dev_dbg(leftover ? &leftover->dev : &s->dev,
+ "pcmcia_card_remove(%d) %s\n", s->sock,
+ leftover ? leftover->devname : "");
+
+ mutex_lock(&s->ops_mutex);
+ if (!leftover)
+ s->device_count = 0;
+ else
+ s->device_count = 1;
+ mutex_unlock(&s->ops_mutex);
+
+ /* unregister all pcmcia_devices registered with this socket, except leftover */
+ list_for_each_entry_safe(p_dev, tmp, &s->devices_list, socket_device_list) {
+ if (p_dev == leftover)
+ continue;
+
+ mutex_lock(&s->ops_mutex);
+ list_del(&p_dev->socket_device_list);
+ mutex_unlock(&s->ops_mutex);
+
+ dev_dbg(&p_dev->dev, "unregistering device\n");
+ device_unregister(&p_dev->dev);
+ }
+
+ return;
+}
+
+static int pcmcia_device_remove(struct device *dev)
+{
+ struct pcmcia_device *p_dev;
+ struct pcmcia_driver *p_drv;
+ int i;
+
+ p_dev = to_pcmcia_dev(dev);
+ p_drv = to_pcmcia_drv(dev->driver);
+
+ dev_dbg(dev, "removing device\n");
+
+ /* If we're removing the primary module driving a
+ * pseudo multi-function card, we need to unbind
+ * all devices
+ */
+ if ((p_dev->socket->pcmcia_pfc) &&
+ (p_dev->socket->device_count > 0) &&
+ (p_dev->device_no == 0))
+ pcmcia_card_remove(p_dev->socket, p_dev);
+
+ /* detach the "instance" */
+ if (!p_drv)
+ return 0;
+
+ if (p_drv->remove)
+ p_drv->remove(p_dev);
+
+ /* check for proper unloading */
+ if (p_dev->_irq || p_dev->_io || p_dev->_locked)
+ dev_info(dev,
+ "pcmcia: driver %s did not release config properly\n",
+ p_drv->name);
+
+ for (i = 0; i < MAX_WIN; i++)
+ if (p_dev->_win & CLIENT_WIN_REQ(i))
+ dev_info(dev,
+ "pcmcia: driver %s did not release window properly\n",
+ p_drv->name);
+
+ /* references from pcmcia_probe_device */
+ pcmcia_put_dev(p_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;
+
+ vers1 = kmalloc(sizeof(*vers1), GFP_KERNEL);
+ if (!vers1)
+ return -ENOMEM;
+
+ if (!pccard_read_tuple(p_dev->socket, BIND_FN_ALL,
+ CISTPL_MANFID, &manf_id)) {
+ mutex_lock(&p_dev->socket->ops_mutex);
+ 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;
+ mutex_unlock(&p_dev->socket->ops_mutex);
+ }
+
+ if (!pccard_read_tuple(p_dev->socket, p_dev->func,
+ CISTPL_FUNCID, &func_id)) {
+ mutex_lock(&p_dev->socket->ops_mutex);
+ p_dev->func_id = func_id.func;
+ p_dev->has_func_id = 1;
+ mutex_unlock(&p_dev->socket->ops_mutex);
+ } 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;
+
+ devgeo = kmalloc(sizeof(*devgeo), GFP_KERNEL);
+ if (!devgeo) {
+ kfree(vers1);
+ return -ENOMEM;
+ }
+ if (!pccard_read_tuple(p_dev->socket, p_dev->func,
+ CISTPL_DEVICE_GEO, devgeo)) {
+ dev_dbg(&p_dev->dev,
+ "mem device geometry probably means "
+ "FUNCID_MEMORY\n");
+ mutex_lock(&p_dev->socket->ops_mutex);
+ p_dev->func_id = CISTPL_FUNCID_MEMORY;
+ p_dev->has_func_id = 1;
+ mutex_unlock(&p_dev->socket->ops_mutex);
+ }
+ kfree(devgeo);
+ }
+
+ if (!pccard_read_tuple(p_dev->socket, BIND_FN_ALL, CISTPL_VERS_1,
+ vers1)) {
+ mutex_lock(&p_dev->socket->ops_mutex);
+ for (i = 0; i < min_t(unsigned int, 4, vers1->ns); i++) {
+ char *tmp;
+ unsigned int length;
+ char *new;
+
+ tmp = vers1->str + vers1->ofs[i];
+
+ length = strlen(tmp) + 1;
+ if ((length < 2) || (length > 255))
+ continue;
+
+ new = kstrdup(tmp, GFP_KERNEL);
+ if (!new)
+ continue;
+
+ tmp = p_dev->prod_id[i];
+ p_dev->prod_id[i] = new;
+ kfree(tmp);
+ }
+ mutex_unlock(&p_dev->socket->ops_mutex);
+ }
+
+ kfree(vers1);
+ return 0;
+}
+
+
+static struct pcmcia_device *pcmcia_device_add(struct pcmcia_socket *s,
+ unsigned int function)
+{
+ struct pcmcia_device *p_dev, *tmp_dev;
+ int i;
+
+ s = pcmcia_get_socket(s);
+ if (!s)
+ return NULL;
+
+ pr_debug("adding device to %d, function %d\n", s->sock, function);
+
+ p_dev = kzalloc(sizeof(struct pcmcia_device), GFP_KERNEL);
+ if (!p_dev)
+ goto err_put;
+
+ mutex_lock(&s->ops_mutex);
+ p_dev->device_no = (s->device_count++);
+ mutex_unlock(&s->ops_mutex);
+
+ /* max of 2 PFC devices */
+ if ((p_dev->device_no >= 2) && (function == 0))
+ goto err_free;
+
+ /* max of 4 devices overall */
+ if (p_dev->device_no >= 4)
+ goto err_free;
+
+ p_dev->socket = s;
+ p_dev->func = function;
+
+ p_dev->dev.bus = &pcmcia_bus_type;
+ p_dev->dev.parent = s->dev.parent;
+ p_dev->dev.release = pcmcia_release_dev;
+ /* by default don't allow DMA */
+ p_dev->dma_mask = 0;
+ p_dev->dev.dma_mask = &p_dev->dma_mask;
+ p_dev->devname = kasprintf(GFP_KERNEL, "pcmcia%s", dev_name(&p_dev->dev));
+ if (!p_dev->devname)
+ goto err_free;
+ dev_dbg(&p_dev->dev, "devname is %s\n", p_dev->devname);
+
+ mutex_lock(&s->ops_mutex);
+
+ /*
+ * p_dev->function_config must be the same for all card functions.
+ * Note that this is serialized by ops_mutex, so that only one
+ * such struct will be created.
+ */
+ list_for_each_entry(tmp_dev, &s->devices_list, socket_device_list)
+ if (p_dev->func == tmp_dev->func) {
+ p_dev->function_config = tmp_dev->function_config;
+ p_dev->irq = tmp_dev->irq;
+ kref_get(&p_dev->function_config->ref);
+ }
+
+ /* Add to the list in pcmcia_bus_socket */
+ list_add(&p_dev->socket_device_list, &s->devices_list);
+
+ if (pcmcia_setup_irq(p_dev))
+ dev_warn(&p_dev->dev,
+ "IRQ setup failed -- device might not work\n");
+
+ if (!p_dev->function_config) {
+ config_t *c;
+ dev_dbg(&p_dev->dev, "creating config_t\n");
+ c = kzalloc(sizeof(struct config_t), GFP_KERNEL);
+ if (!c) {
+ mutex_unlock(&s->ops_mutex);
+ goto err_unreg;
+ }
+ p_dev->function_config = c;
+ kref_init(&c->ref);
+ for (i = 0; i < MAX_IO_WIN; i++) {
+ c->io[i].name = p_dev->devname;
+ c->io[i].flags = IORESOURCE_IO;
+ }
+ for (i = 0; i < MAX_WIN; i++) {
+ c->mem[i].name = p_dev->devname;
+ c->mem[i].flags = IORESOURCE_MEM;
+ }
+ }
+ for (i = 0; i < MAX_IO_WIN; i++)
+ p_dev->resource[i] = &p_dev->function_config->io[i];
+ for (; i < (MAX_IO_WIN + MAX_WIN); i++)
+ p_dev->resource[i] = &p_dev->function_config->mem[i-MAX_IO_WIN];
+
+ mutex_unlock(&s->ops_mutex);
+
+ dev_notice(&p_dev->dev, "pcmcia: registering new device %s (IRQ: %d)\n",
+ p_dev->devname, p_dev->irq);
+
+ pcmcia_device_query(p_dev);
+
+ dev_set_name(&p_dev->dev, "%d.%d", p_dev->socket->sock, p_dev->device_no);
+ if (device_register(&p_dev->dev)) {
+ mutex_lock(&s->ops_mutex);
+ list_del(&p_dev->socket_device_list);
+ s->device_count--;
+ mutex_unlock(&s->ops_mutex);
+ put_device(&p_dev->dev);
+ return NULL;
+ }
+
+ return p_dev;
+
+ err_unreg:
+ mutex_lock(&s->ops_mutex);
+ list_del(&p_dev->socket_device_list);
+ mutex_unlock(&s->ops_mutex);
+
+ err_free:
+ mutex_lock(&s->ops_mutex);
+ s->device_count--;
+ mutex_unlock(&s->ops_mutex);
+
+ for (i = 0; i < 4; i++)
+ kfree(p_dev->prod_id[i]);
+ kfree(p_dev->devname);
+ kfree(p_dev);
+ err_put:
+ pcmcia_put_socket(s);
+
+ return NULL;
+}
+
+
+static int pcmcia_card_add(struct pcmcia_socket *s)
+{
+ cistpl_longlink_mfc_t mfc;
+ unsigned int no_funcs, i, no_chains;
+ int ret = -EAGAIN;
+
+ mutex_lock(&s->ops_mutex);
+ if (!(s->resource_setup_done)) {
+ dev_dbg(&s->dev,
+ "no resources available, delaying card_add\n");
+ mutex_unlock(&s->ops_mutex);
+ return -EAGAIN; /* try again, but later... */
+ }
+
+ if (pcmcia_validate_mem(s)) {
+ dev_dbg(&s->dev, "validating mem resources failed, "
+ "delaying card_add\n");
+ mutex_unlock(&s->ops_mutex);
+ return -EAGAIN; /* try again, but later... */
+ }
+ mutex_unlock(&s->ops_mutex);
+
+ ret = pccard_validate_cis(s, &no_chains);
+ if (ret || !no_chains) {
+#if defined(CONFIG_MTD_PCMCIA_ANONYMOUS)
+ /* Set up as an anonymous card. If we don't have anonymous
+ memory support then just error the card as there is no
+ point trying to second guess.
+
+ Note: some cards have just a device entry, it may be
+ worth extending support to cover these in future */
+ if (ret == -EIO) {
+ dev_info(&s->dev, "no CIS, assuming an anonymous memory card.\n");
+ pcmcia_replace_cis(s, "\xFF", 1);
+ no_chains = 1;
+ ret = 0;
+ } else
+#endif
+ {
+ dev_dbg(&s->dev, "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;
+ s->functions = no_funcs;
+
+ for (i = 0; i < no_funcs; i++)
+ pcmcia_device_add(s, i);
+
+ return ret;
+}
+
+
+static int pcmcia_requery_callback(struct device *dev, void *_data)
+{
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+ if (!p_dev->dev.driver) {
+ dev_dbg(dev, "update device information\n");
+ pcmcia_device_query(p_dev);
+ }
+
+ return 0;
+}
+
+
+static void pcmcia_requery(struct pcmcia_socket *s)
+{
+ int has_pfc;
+
+ if (!(s->state & SOCKET_PRESENT))
+ return;
+
+ if (s->functions == 0) {
+ pcmcia_card_add(s);
+ return;
+ }
+
+ /* some device information might have changed because of a CIS
+ * update or because we can finally read it correctly... so
+ * determine it again, overwriting old values if necessary. */
+ bus_for_each_dev(&pcmcia_bus_type, NULL, NULL, pcmcia_requery_callback);
+
+ /* if the CIS changed, we need to check whether the number of
+ * functions changed. */
+ if (s->fake_cis) {
+ int old_funcs, new_funcs;
+ cistpl_longlink_mfc_t mfc;
+
+ /* does this cis override add or remove functions? */
+ old_funcs = s->functions;
+
+ if (!pccard_read_tuple(s, BIND_FN_ALL, CISTPL_LONGLINK_MFC,
+ &mfc))
+ new_funcs = mfc.nfn;
+ else
+ new_funcs = 1;
+ if (old_funcs != new_funcs) {
+ /* we need to re-start */
+ pcmcia_card_remove(s, NULL);
+ s->functions = 0;
+ pcmcia_card_add(s);
+ }
+ }
+
+ /* If the PCMCIA device consists of two pseudo devices,
+ * call pcmcia_device_add() -- which will fail if both
+ * devices are already registered. */
+ mutex_lock(&s->ops_mutex);
+ has_pfc = s->pcmcia_pfc;
+ mutex_unlock(&s->ops_mutex);
+ if (has_pfc)
+ pcmcia_device_add(s, 0);
+
+ /* we re-scan all devices, not just the ones connected to this
+ * socket. This does not matter, though. */
+ if (bus_rescan_devices(&pcmcia_bus_type))
+ dev_warn(&s->dev, "rescanning the bus failed\n");
+}
+
+
+#ifdef CONFIG_PCMCIA_LOAD_CIS
+
+/**
+ * pcmcia_load_firmware - load CIS from userspace if device-provided is broken
+ * @dev: the pcmcia device which needs a CIS override
+ * @filename: requested filename in /lib/firmware/
+ *
+ * This uses the in-kernel firmware loading mechanism to use a "fake CIS" if
+ * the one provided by the card is broken. The firmware files reside in
+ * /lib/firmware/ in userspace.
+ */
+static int pcmcia_load_firmware(struct pcmcia_device *dev, char *filename)
+{
+ struct pcmcia_socket *s = dev->socket;
+ const struct firmware *fw;
+ int ret = -ENOMEM;
+ cistpl_longlink_mfc_t mfc;
+ int old_funcs, new_funcs = 1;
+
+ if (!filename)
+ return -EINVAL;
+
+ dev_dbg(&dev->dev, "trying to load CIS file %s\n", filename);
+
+ if (request_firmware(&fw, filename, &dev->dev) == 0) {
+ if (fw->size >= CISTPL_MAX_CIS_SIZE) {
+ ret = -EINVAL;
+ dev_err(&dev->dev, "pcmcia: CIS override is too big\n");
+ goto release;
+ }
+
+ if (!pcmcia_replace_cis(s, fw->data, fw->size))
+ ret = 0;
+ else {
+ dev_err(&dev->dev, "pcmcia: CIS override failed\n");
+ goto release;
+ }
+
+ /* we need to re-start if the number of functions changed */
+ old_funcs = s->functions;
+ if (!pccard_read_tuple(s, BIND_FN_ALL, CISTPL_LONGLINK_MFC,
+ &mfc))
+ new_funcs = mfc.nfn;
+
+ if (old_funcs != new_funcs)
+ ret = -EBUSY;
+
+ /* update information */
+ pcmcia_device_query(dev);
+
+ /* requery (as number of functions might have changed) */
+ pcmcia_parse_uevents(s, PCMCIA_UEVENT_REQUERY);
+ }
+ release:
+ release_firmware(fw);
+
+ return ret;
+}
+
+#else /* !CONFIG_PCMCIA_LOAD_CIS */
+
+static inline int pcmcia_load_firmware(struct pcmcia_device *dev,
+ char *filename)
+{
+ return -ENODEV;
+}
+
+#endif
+
+
+static inline int pcmcia_devmatch(struct pcmcia_device *dev,
+ const struct pcmcia_device_id *did)
+{
+ if (did->match_flags & PCMCIA_DEV_ID_MATCH_MANF_ID) {
+ if ((!dev->has_manf_id) || (dev->manf_id != did->manf_id))
+ return 0;
+ }
+
+ if (did->match_flags & PCMCIA_DEV_ID_MATCH_CARD_ID) {
+ if ((!dev->has_card_id) || (dev->card_id != did->card_id))
+ return 0;
+ }
+
+ if (did->match_flags & PCMCIA_DEV_ID_MATCH_FUNCTION) {
+ if (dev->func != did->function)
+ return 0;
+ }
+
+ if (did->match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID1) {
+ if (!dev->prod_id[0])
+ return 0;
+ if (strcmp(did->prod_id[0], dev->prod_id[0]))
+ return 0;
+ }
+
+ if (did->match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID2) {
+ if (!dev->prod_id[1])
+ return 0;
+ if (strcmp(did->prod_id[1], dev->prod_id[1]))
+ return 0;
+ }
+
+ if (did->match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID3) {
+ if (!dev->prod_id[2])
+ return 0;
+ if (strcmp(did->prod_id[2], dev->prod_id[2]))
+ return 0;
+ }
+
+ if (did->match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID4) {
+ if (!dev->prod_id[3])
+ return 0;
+ if (strcmp(did->prod_id[3], dev->prod_id[3]))
+ return 0;
+ }
+
+ if (did->match_flags & PCMCIA_DEV_ID_MATCH_DEVICE_NO) {
+ dev_dbg(&dev->dev, "this is a pseudo-multi-function device\n");
+ mutex_lock(&dev->socket->ops_mutex);
+ dev->socket->pcmcia_pfc = 1;
+ mutex_unlock(&dev->socket->ops_mutex);
+ if (dev->device_no != did->device_no)
+ return 0;
+ }
+
+ if (did->match_flags & PCMCIA_DEV_ID_MATCH_FUNC_ID) {
+ int ret;
+
+ if ((!dev->has_func_id) || (dev->func_id != did->func_id))
+ return 0;
+
+ /* if this is a pseudo-multi-function device,
+ * we need explicit matches */
+ if (dev->socket->pcmcia_pfc)
+ return 0;
+ if (dev->device_no)
+ return 0;
+
+ /* also, FUNC_ID matching needs to be activated by userspace
+ * after it has re-checked that there is no possible module
+ * with a prod_id/manf_id/card_id match.
+ */
+ mutex_lock(&dev->socket->ops_mutex);
+ ret = dev->allow_func_id_match;
+ mutex_unlock(&dev->socket->ops_mutex);
+
+ if (!ret) {
+ dev_dbg(&dev->dev,
+ "skipping FUNC_ID match until userspace ACK\n");
+ return 0;
+ }
+ }
+
+ if (did->match_flags & PCMCIA_DEV_ID_MATCH_FAKE_CIS) {
+ dev_dbg(&dev->dev, "device needs a fake CIS\n");
+ if (!dev->socket->fake_cis)
+ if (pcmcia_load_firmware(dev, did->cisfile))
+ return 0;
+ }
+
+ if (did->match_flags & PCMCIA_DEV_ID_MATCH_ANONYMOUS) {
+ int i;
+ for (i = 0; i < 4; i++)
+ if (dev->prod_id[i])
+ return 0;
+ if (dev->has_manf_id || dev->has_card_id || dev->has_func_id)
+ return 0;
+ }
+
+ return 1;
+}
+
+
+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);
+ const struct pcmcia_device_id *did = p_drv->id_table;
+ struct pcmcia_dynid *dynid;
+
+ /* match dynamic devices first */
+ mutex_lock(&p_drv->dynids.lock);
+ list_for_each_entry(dynid, &p_drv->dynids.list, node) {
+ dev_dbg(dev, "trying to match to %s\n", drv->name);
+ if (pcmcia_devmatch(p_dev, &dynid->id)) {
+ dev_dbg(dev, "matched to %s\n", drv->name);
+ mutex_unlock(&p_drv->dynids.lock);
+ return 1;
+ }
+ }
+ mutex_unlock(&p_drv->dynids.lock);
+
+ while (did && did->match_flags) {
+ dev_dbg(dev, "trying to match to %s\n", drv->name);
+ if (pcmcia_devmatch(p_dev, did)) {
+ dev_dbg(dev, "matched to %s\n", drv->name);
+ return 1;
+ }
+ did++;
+ }
+
+ return 0;
+}
+
+static int pcmcia_bus_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct pcmcia_device *p_dev;
+ int i;
+ u32 hash[4] = { 0, 0, 0, 0};
+
+ if (!dev)
+ return -ENODEV;
+
+ p_dev = to_pcmcia_dev(dev);
+
+ /* calculate hashes */
+ for (i = 0; i < 4; i++) {
+ if (!p_dev->prod_id[i])
+ continue;
+ hash[i] = crc32(0, p_dev->prod_id[i], strlen(p_dev->prod_id[i]));
+ }
+
+ if (add_uevent_var(env, "SOCKET_NO=%u", p_dev->socket->sock))
+ return -ENOMEM;
+
+ if (add_uevent_var(env, "DEVICE_NO=%02X", p_dev->device_no))
+ return -ENOMEM;
+
+ if (add_uevent_var(env, "MODALIAS=pcmcia:m%04Xc%04Xf%02Xfn%02Xpfn%02X"
+ "pa%08Xpb%08Xpc%08Xpd%08X",
+ p_dev->has_manf_id ? p_dev->manf_id : 0,
+ p_dev->has_card_id ? p_dev->card_id : 0,
+ p_dev->has_func_id ? p_dev->func_id : 0,
+ p_dev->func,
+ p_dev->device_no,
+ hash[0],
+ hash[1],
+ hash[2],
+ hash[3]))
+ return -ENOMEM;
+
+ return 0;
+}
+
+/************************ runtime PM support ***************************/
+
+static int pcmcia_dev_suspend(struct device *dev);
+static int pcmcia_dev_resume(struct device *dev);
+
+static int runtime_suspend(struct device *dev)
+{
+ int rc;
+
+ device_lock(dev);
+ rc = pcmcia_dev_suspend(dev);
+ device_unlock(dev);
+ return rc;
+}
+
+static int runtime_resume(struct device *dev)
+{
+ int rc;
+
+ device_lock(dev);
+ rc = pcmcia_dev_resume(dev);
+ device_unlock(dev);
+ return rc;
+}
+
+/************************ per-device sysfs output ***************************/
+
+#define pcmcia_device_attr(field, test, format) \
+static ssize_t field##_show (struct device *dev, struct device_attribute *attr, char *buf) \
+{ \
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev); \
+ return p_dev->test ? sprintf(buf, format, p_dev->field) : -ENODEV; \
+} \
+static DEVICE_ATTR_RO(field);
+
+#define pcmcia_device_stringattr(name, field) \
+static ssize_t name##_show (struct device *dev, struct device_attribute *attr, char *buf) \
+{ \
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev); \
+ return p_dev->field ? sprintf(buf, "%s\n", p_dev->field) : -ENODEV; \
+} \
+static DEVICE_ATTR_RO(name);
+
+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 ssize_t function_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+ return p_dev->socket ? sprintf(buf, "0x%02x\n", p_dev->func) : -ENODEV;
+}
+static DEVICE_ATTR_RO(function);
+
+static ssize_t resources_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+ char *str = buf;
+ int i;
+
+ for (i = 0; i < PCMCIA_NUM_RESOURCES; i++)
+ str += sprintf(str, "%pr\n", p_dev->resource[i]);
+
+ return str - buf;
+}
+static DEVICE_ATTR_RO(resources);
+
+static ssize_t pm_state_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+
+ if (p_dev->suspended)
+ return sprintf(buf, "off\n");
+ else
+ return sprintf(buf, "on\n");
+}
+
+static ssize_t pm_state_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+ int ret = 0;
+
+ if (!count)
+ return -EINVAL;
+
+ if ((!p_dev->suspended) && !strncmp(buf, "off", 3))
+ ret = runtime_suspend(dev);
+ else if (p_dev->suspended && !strncmp(buf, "on", 2))
+ ret = runtime_resume(dev);
+
+ return ret ? ret : count;
+}
+static DEVICE_ATTR_RW(pm_state);
+
+static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+ int i;
+ u32 hash[4] = { 0, 0, 0, 0};
+
+ /* calculate hashes */
+ for (i = 0; i < 4; i++) {
+ if (!p_dev->prod_id[i])
+ continue;
+ hash[i] = crc32(0, p_dev->prod_id[i],
+ strlen(p_dev->prod_id[i]));
+ }
+ return sprintf(buf, "pcmcia:m%04Xc%04Xf%02Xfn%02Xpfn%02X"
+ "pa%08Xpb%08Xpc%08Xpd%08X\n",
+ p_dev->has_manf_id ? p_dev->manf_id : 0,
+ p_dev->has_card_id ? p_dev->card_id : 0,
+ p_dev->has_func_id ? p_dev->func_id : 0,
+ p_dev->func, p_dev->device_no,
+ hash[0], hash[1], hash[2], hash[3]);
+}
+static DEVICE_ATTR_RO(modalias);
+
+static ssize_t allow_func_id_match_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+
+ if (!count)
+ return -EINVAL;
+
+ mutex_lock(&p_dev->socket->ops_mutex);
+ p_dev->allow_func_id_match = 1;
+ mutex_unlock(&p_dev->socket->ops_mutex);
+ pcmcia_parse_uevents(p_dev->socket, PCMCIA_UEVENT_REQUERY);
+
+ return count;
+}
+static DEVICE_ATTR_WO(allow_func_id_match);
+
+static struct attribute *pcmcia_dev_attrs[] = {
+ &dev_attr_resources.attr,
+ &dev_attr_pm_state.attr,
+ &dev_attr_function.attr,
+ &dev_attr_func_id.attr,
+ &dev_attr_manf_id.attr,
+ &dev_attr_card_id.attr,
+ &dev_attr_prod_id1.attr,
+ &dev_attr_prod_id2.attr,
+ &dev_attr_prod_id3.attr,
+ &dev_attr_prod_id4.attr,
+ &dev_attr_modalias.attr,
+ &dev_attr_allow_func_id_match.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(pcmcia_dev);
+
+/* PM support, also needed for reset */
+
+static int pcmcia_dev_suspend(struct device *dev)
+{
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+ struct pcmcia_driver *p_drv = NULL;
+ int ret = 0;
+
+ mutex_lock(&p_dev->socket->ops_mutex);
+ if (p_dev->suspended) {
+ mutex_unlock(&p_dev->socket->ops_mutex);
+ return 0;
+ }
+ p_dev->suspended = 1;
+ mutex_unlock(&p_dev->socket->ops_mutex);
+
+ dev_dbg(dev, "suspending\n");
+
+ if (dev->driver)
+ p_drv = to_pcmcia_drv(dev->driver);
+
+ if (!p_drv)
+ goto out;
+
+ if (p_drv->suspend) {
+ ret = p_drv->suspend(p_dev);
+ if (ret) {
+ dev_err(dev,
+ "pcmcia: device %s (driver %s) did not want to go to sleep (%d)\n",
+ p_dev->devname, p_drv->name, ret);
+ mutex_lock(&p_dev->socket->ops_mutex);
+ p_dev->suspended = 0;
+ mutex_unlock(&p_dev->socket->ops_mutex);
+ goto out;
+ }
+ }
+
+ if (p_dev->device_no == p_dev->func) {
+ dev_dbg(dev, "releasing configuration\n");
+ pcmcia_release_configuration(p_dev);
+ }
+
+ out:
+ return ret;
+}
+
+
+static int pcmcia_dev_resume(struct device *dev)
+{
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+ struct pcmcia_driver *p_drv = NULL;
+ int ret = 0;
+
+ mutex_lock(&p_dev->socket->ops_mutex);
+ if (!p_dev->suspended) {
+ mutex_unlock(&p_dev->socket->ops_mutex);
+ return 0;
+ }
+ p_dev->suspended = 0;
+ mutex_unlock(&p_dev->socket->ops_mutex);
+
+ dev_dbg(dev, "resuming\n");
+
+ if (dev->driver)
+ p_drv = to_pcmcia_drv(dev->driver);
+
+ if (!p_drv)
+ goto out;
+
+ if (p_dev->device_no == p_dev->func) {
+ dev_dbg(dev, "requesting configuration\n");
+ ret = pcmcia_enable_device(p_dev);
+ if (ret)
+ goto out;
+ }
+
+ if (p_drv->resume)
+ ret = p_drv->resume(p_dev);
+
+ out:
+ return ret;
+}
+
+
+static int pcmcia_bus_suspend_callback(struct device *dev, void *_data)
+{
+ struct pcmcia_socket *skt = _data;
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+
+ if (p_dev->socket != skt || p_dev->suspended)
+ return 0;
+
+ return runtime_suspend(dev);
+}
+
+static int pcmcia_bus_resume_callback(struct device *dev, void *_data)
+{
+ struct pcmcia_socket *skt = _data;
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+
+ if (p_dev->socket != skt || !p_dev->suspended)
+ return 0;
+
+ runtime_resume(dev);
+
+ return 0;
+}
+
+static int pcmcia_bus_resume(struct pcmcia_socket *skt)
+{
+ dev_dbg(&skt->dev, "resuming socket %d\n", skt->sock);
+ bus_for_each_dev(&pcmcia_bus_type, NULL, skt, pcmcia_bus_resume_callback);
+ return 0;
+}
+
+static int pcmcia_bus_suspend(struct pcmcia_socket *skt)
+{
+ dev_dbg(&skt->dev, "suspending socket %d\n", skt->sock);
+ if (bus_for_each_dev(&pcmcia_bus_type, NULL, skt,
+ pcmcia_bus_suspend_callback)) {
+ pcmcia_bus_resume(skt);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int pcmcia_bus_remove(struct pcmcia_socket *skt)
+{
+ atomic_set(&skt->present, 0);
+ pcmcia_card_remove(skt, NULL);
+
+ mutex_lock(&skt->ops_mutex);
+ destroy_cis_cache(skt);
+ pcmcia_cleanup_irq(skt);
+ mutex_unlock(&skt->ops_mutex);
+
+ return 0;
+}
+
+static int pcmcia_bus_add(struct pcmcia_socket *skt)
+{
+ atomic_set(&skt->present, 1);
+
+ mutex_lock(&skt->ops_mutex);
+ skt->pcmcia_pfc = 0;
+ destroy_cis_cache(skt); /* to be on the safe side... */
+ mutex_unlock(&skt->ops_mutex);
+
+ pcmcia_card_add(skt);
+
+ return 0;
+}
+
+static int pcmcia_bus_early_resume(struct pcmcia_socket *skt)
+{
+ if (!verify_cis_cache(skt))
+ return 0;
+
+ dev_dbg(&skt->dev, "cis mismatch - different card\n");
+
+ /* first, remove the card */
+ pcmcia_bus_remove(skt);
+
+ mutex_lock(&skt->ops_mutex);
+ destroy_cis_cache(skt);
+ kfree(skt->fake_cis);
+ skt->fake_cis = NULL;
+ skt->functions = 0;
+ mutex_unlock(&skt->ops_mutex);
+
+ /* now, add the new card */
+ pcmcia_bus_add(skt);
+ return 0;
+}
+
+
+/*
+ * NOTE: This is racy. There's no guarantee the card will still be
+ * physically present, even if the call to this function returns
+ * non-NULL. Furthermore, the device driver most likely is unbound
+ * almost immediately, so the timeframe where pcmcia_dev_present
+ * returns NULL is probably really really small.
+ */
+struct pcmcia_device *pcmcia_dev_present(struct pcmcia_device *_p_dev)
+{
+ struct pcmcia_device *p_dev;
+ struct pcmcia_device *ret = NULL;
+
+ p_dev = pcmcia_get_dev(_p_dev);
+ if (!p_dev)
+ return NULL;
+
+ if (atomic_read(&p_dev->socket->present) != 0)
+ ret = p_dev;
+
+ pcmcia_put_dev(p_dev);
+ return ret;
+}
+EXPORT_SYMBOL(pcmcia_dev_present);
+
+
+static struct pcmcia_callback pcmcia_bus_callback = {
+ .owner = THIS_MODULE,
+ .add = pcmcia_bus_add,
+ .remove = pcmcia_bus_remove,
+ .requery = pcmcia_requery,
+ .validate = pccard_validate_cis,
+ .suspend = pcmcia_bus_suspend,
+ .early_resume = pcmcia_bus_early_resume,
+ .resume = pcmcia_bus_resume,
+};
+
+static int pcmcia_bus_add_socket(struct device *dev,
+ struct class_interface *class_intf)
+{
+ struct pcmcia_socket *socket = dev_get_drvdata(dev);
+ int ret;
+
+ socket = pcmcia_get_socket(socket);
+ if (!socket) {
+ dev_err(dev, "PCMCIA obtaining reference to socket failed\n");
+ return -ENODEV;
+ }
+
+ ret = sysfs_create_bin_file(&dev->kobj, &pccard_cis_attr);
+ if (ret) {
+ dev_err(dev, "PCMCIA registration failed\n");
+ pcmcia_put_socket(socket);
+ return ret;
+ }
+
+ INIT_LIST_HEAD(&socket->devices_list);
+ socket->pcmcia_pfc = 0;
+ socket->device_count = 0;
+ atomic_set(&socket->present, 0);
+
+ ret = pccard_register_pcmcia(socket, &pcmcia_bus_callback);
+ if (ret) {
+ dev_err(dev, "PCMCIA registration failed\n");
+ pcmcia_put_socket(socket);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void pcmcia_bus_remove_socket(struct device *dev,
+ struct class_interface *class_intf)
+{
+ struct pcmcia_socket *socket = dev_get_drvdata(dev);
+
+ if (!socket)
+ return;
+
+ pccard_register_pcmcia(socket, NULL);
+
+ /* unregister any unbound devices */
+ mutex_lock(&socket->skt_mutex);
+ pcmcia_card_remove(socket, NULL);
+ release_cis_mem(socket);
+ mutex_unlock(&socket->skt_mutex);
+
+ sysfs_remove_bin_file(&dev->kobj, &pccard_cis_attr);
+
+ pcmcia_put_socket(socket);
+
+ return;
+}
+
+
+/* the pcmcia_bus_interface is used to handle pcmcia socket devices */
+static struct class_interface pcmcia_bus_interface __refdata = {
+ .class = &pcmcia_socket_class,
+ .add_dev = &pcmcia_bus_add_socket,
+ .remove_dev = &pcmcia_bus_remove_socket,
+};
+
+static const struct dev_pm_ops pcmcia_bus_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(pcmcia_dev_suspend, pcmcia_dev_resume)
+};
+
+struct bus_type pcmcia_bus_type = {
+ .name = "pcmcia",
+ .uevent = pcmcia_bus_uevent,
+ .match = pcmcia_bus_match,
+ .dev_groups = pcmcia_dev_groups,
+ .probe = pcmcia_device_probe,
+ .remove = pcmcia_device_remove,
+ .pm = &pcmcia_bus_pm_ops,
+};
+
+
+static int __init init_pcmcia_bus(void)
+{
+ int ret;
+
+ ret = bus_register(&pcmcia_bus_type);
+ if (ret < 0) {
+ printk(KERN_WARNING "pcmcia: bus_register error: %d\n", ret);
+ return ret;
+ }
+ ret = class_interface_register(&pcmcia_bus_interface);
+ if (ret < 0) {
+ printk(KERN_WARNING
+ "pcmcia: class_interface_register error: %d\n", ret);
+ bus_unregister(&pcmcia_bus_type);
+ return ret;
+ }
+
+ 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);
+
+ bus_unregister(&pcmcia_bus_type);
+}
+module_exit(exit_pcmcia_bus);
+
+
+MODULE_ALIAS("ds");
diff --git a/drivers/pcmcia/electra_cf.c b/drivers/pcmcia/electra_cf.c
new file mode 100644
index 000000000..35158cfd9
--- /dev/null
+++ b/drivers/pcmcia/electra_cf.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 PA Semi, Inc
+ *
+ * Maintained by: Olof Johansson <olof@lixom.net>
+ *
+ * Based on drivers/pcmcia/omap_cf.c
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+
+#include <pcmcia/ss.h>
+
+static const char driver_name[] = "electra-cf";
+
+struct electra_cf_socket {
+ struct pcmcia_socket socket;
+
+ struct timer_list timer;
+ unsigned present:1;
+ unsigned active:1;
+
+ struct platform_device *ofdev;
+ unsigned long mem_phys;
+ void __iomem *mem_base;
+ unsigned long mem_size;
+ void __iomem *io_virt;
+ unsigned int io_base;
+ unsigned int io_size;
+ u_int irq;
+ struct resource iomem;
+ void __iomem *gpio_base;
+ int gpio_detect;
+ int gpio_vsense;
+ int gpio_3v;
+ int gpio_5v;
+};
+
+#define POLL_INTERVAL (2 * HZ)
+
+
+static int electra_cf_present(struct electra_cf_socket *cf)
+{
+ unsigned int gpio;
+
+ gpio = in_le32(cf->gpio_base+0x40);
+ return !(gpio & (1 << cf->gpio_detect));
+}
+
+static int electra_cf_ss_init(struct pcmcia_socket *s)
+{
+ return 0;
+}
+
+/* the timer is primarily to kick this socket's pccardd */
+static void electra_cf_timer(struct timer_list *t)
+{
+ struct electra_cf_socket *cf = from_timer(cf, t, timer);
+ int present = electra_cf_present(cf);
+
+ if (present != cf->present) {
+ cf->present = present;
+ pcmcia_parse_events(&cf->socket, SS_DETECT);
+ }
+
+ if (cf->active)
+ mod_timer(&cf->timer, jiffies + POLL_INTERVAL);
+}
+
+static irqreturn_t electra_cf_irq(int irq, void *_cf)
+{
+ struct electra_cf_socket *cf = _cf;
+
+ electra_cf_timer(&cf->timer);
+ return IRQ_HANDLED;
+}
+
+static int electra_cf_get_status(struct pcmcia_socket *s, u_int *sp)
+{
+ struct electra_cf_socket *cf;
+
+ if (!sp)
+ return -EINVAL;
+
+ cf = container_of(s, struct electra_cf_socket, socket);
+
+ /* NOTE CF is always 3VCARD */
+ if (electra_cf_present(cf)) {
+ *sp = SS_READY | SS_DETECT | SS_POWERON | SS_3VCARD;
+
+ s->pci_irq = cf->irq;
+ } else
+ *sp = 0;
+ return 0;
+}
+
+static int electra_cf_set_socket(struct pcmcia_socket *sock,
+ struct socket_state_t *s)
+{
+ unsigned int gpio;
+ unsigned int vcc;
+ struct electra_cf_socket *cf;
+
+ cf = container_of(sock, struct electra_cf_socket, socket);
+
+ /* "reset" means no power in our case */
+ vcc = (s->flags & SS_RESET) ? 0 : s->Vcc;
+
+ switch (vcc) {
+ case 0:
+ gpio = 0;
+ break;
+ case 33:
+ gpio = (1 << cf->gpio_3v);
+ break;
+ case 5:
+ gpio = (1 << cf->gpio_5v);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ gpio |= 1 << (cf->gpio_3v + 16); /* enwr */
+ gpio |= 1 << (cf->gpio_5v + 16); /* enwr */
+ out_le32(cf->gpio_base+0x90, gpio);
+
+ pr_debug("%s: Vcc %d, io_irq %d, flags %04x csc %04x\n",
+ driver_name, s->Vcc, s->io_irq, s->flags, s->csc_mask);
+
+ return 0;
+}
+
+static int electra_cf_set_io_map(struct pcmcia_socket *s,
+ struct pccard_io_map *io)
+{
+ return 0;
+}
+
+static int electra_cf_set_mem_map(struct pcmcia_socket *s,
+ struct pccard_mem_map *map)
+{
+ struct electra_cf_socket *cf;
+
+ if (map->card_start)
+ return -EINVAL;
+ cf = container_of(s, struct electra_cf_socket, socket);
+ map->static_start = cf->mem_phys;
+ map->flags &= MAP_ACTIVE|MAP_ATTRIB;
+ if (!(map->flags & MAP_ATTRIB))
+ map->static_start += 0x800;
+ return 0;
+}
+
+static struct pccard_operations electra_cf_ops = {
+ .init = electra_cf_ss_init,
+ .get_status = electra_cf_get_status,
+ .set_socket = electra_cf_set_socket,
+ .set_io_map = electra_cf_set_io_map,
+ .set_mem_map = electra_cf_set_mem_map,
+};
+
+static int electra_cf_probe(struct platform_device *ofdev)
+{
+ struct device *device = &ofdev->dev;
+ struct device_node *np = ofdev->dev.of_node;
+ struct electra_cf_socket *cf;
+ struct resource mem, io;
+ int status = -ENOMEM;
+ const unsigned int *prop;
+ int err;
+
+ err = of_address_to_resource(np, 0, &mem);
+ if (err)
+ return -EINVAL;
+
+ err = of_address_to_resource(np, 1, &io);
+ if (err)
+ return -EINVAL;
+
+ cf = kzalloc(sizeof(*cf), GFP_KERNEL);
+ if (!cf)
+ return -ENOMEM;
+
+ timer_setup(&cf->timer, electra_cf_timer, 0);
+ cf->irq = 0;
+
+ cf->ofdev = ofdev;
+ cf->mem_phys = mem.start;
+ cf->mem_size = PAGE_ALIGN(resource_size(&mem));
+ cf->mem_base = ioremap(cf->mem_phys, cf->mem_size);
+ if (!cf->mem_base)
+ goto out_free_cf;
+ cf->io_size = PAGE_ALIGN(resource_size(&io));
+ cf->io_virt = ioremap_phb(io.start, cf->io_size);
+ if (!cf->io_virt)
+ goto out_unmap_mem;
+
+ cf->gpio_base = ioremap(0xfc103000, 0x1000);
+ if (!cf->gpio_base)
+ goto out_unmap_virt;
+ dev_set_drvdata(device, cf);
+
+ cf->io_base = (unsigned long)cf->io_virt - VMALLOC_END;
+ cf->iomem.start = (unsigned long)cf->mem_base;
+ cf->iomem.end = (unsigned long)cf->mem_base + (mem.end - mem.start);
+ cf->iomem.flags = IORESOURCE_MEM;
+
+ cf->irq = irq_of_parse_and_map(np, 0);
+
+ status = request_irq(cf->irq, electra_cf_irq, IRQF_SHARED,
+ driver_name, cf);
+ if (status < 0) {
+ dev_err(device, "request_irq failed\n");
+ goto fail1;
+ }
+
+ cf->socket.pci_irq = cf->irq;
+
+ prop = of_get_property(np, "card-detect-gpio", NULL);
+ if (!prop)
+ goto fail1;
+ cf->gpio_detect = *prop;
+
+ prop = of_get_property(np, "card-vsense-gpio", NULL);
+ if (!prop)
+ goto fail1;
+ cf->gpio_vsense = *prop;
+
+ prop = of_get_property(np, "card-3v-gpio", NULL);
+ if (!prop)
+ goto fail1;
+ cf->gpio_3v = *prop;
+
+ prop = of_get_property(np, "card-5v-gpio", NULL);
+ if (!prop)
+ goto fail1;
+ cf->gpio_5v = *prop;
+
+ cf->socket.io_offset = cf->io_base;
+
+ /* reserve chip-select regions */
+ if (!request_mem_region(cf->mem_phys, cf->mem_size, driver_name)) {
+ status = -ENXIO;
+ dev_err(device, "Can't claim memory region\n");
+ goto fail1;
+ }
+
+ if (!request_region(cf->io_base, cf->io_size, driver_name)) {
+ status = -ENXIO;
+ dev_err(device, "Can't claim I/O region\n");
+ goto fail2;
+ }
+
+ cf->socket.owner = THIS_MODULE;
+ cf->socket.dev.parent = &ofdev->dev;
+ cf->socket.ops = &electra_cf_ops;
+ cf->socket.resource_ops = &pccard_static_ops;
+ cf->socket.features = SS_CAP_PCCARD | SS_CAP_STATIC_MAP |
+ SS_CAP_MEM_ALIGN;
+ cf->socket.map_size = 0x800;
+
+ status = pcmcia_register_socket(&cf->socket);
+ if (status < 0) {
+ dev_err(device, "pcmcia_register_socket failed\n");
+ goto fail3;
+ }
+
+ dev_info(device, "at mem 0x%lx io 0x%llx irq %d\n",
+ cf->mem_phys, io.start, cf->irq);
+
+ cf->active = 1;
+ electra_cf_timer(&cf->timer);
+ return 0;
+
+fail3:
+ release_region(cf->io_base, cf->io_size);
+fail2:
+ release_mem_region(cf->mem_phys, cf->mem_size);
+fail1:
+ if (cf->irq)
+ free_irq(cf->irq, cf);
+
+ iounmap(cf->gpio_base);
+out_unmap_virt:
+ device_init_wakeup(&ofdev->dev, 0);
+ iounmap(cf->io_virt);
+out_unmap_mem:
+ iounmap(cf->mem_base);
+out_free_cf:
+ kfree(cf);
+ return status;
+
+}
+
+static int electra_cf_remove(struct platform_device *ofdev)
+{
+ struct device *device = &ofdev->dev;
+ struct electra_cf_socket *cf;
+
+ cf = dev_get_drvdata(device);
+
+ cf->active = 0;
+ pcmcia_unregister_socket(&cf->socket);
+ free_irq(cf->irq, cf);
+ del_timer_sync(&cf->timer);
+
+ iounmap(cf->io_virt);
+ iounmap(cf->mem_base);
+ iounmap(cf->gpio_base);
+ release_mem_region(cf->mem_phys, cf->mem_size);
+ release_region(cf->io_base, cf->io_size);
+
+ kfree(cf);
+
+ return 0;
+}
+
+static const struct of_device_id electra_cf_match[] = {
+ {
+ .compatible = "electra-cf",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, electra_cf_match);
+
+static struct platform_driver electra_cf_driver = {
+ .driver = {
+ .name = driver_name,
+ .of_match_table = electra_cf_match,
+ },
+ .probe = electra_cf_probe,
+ .remove = electra_cf_remove,
+};
+
+module_platform_driver(electra_cf_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Olof Johansson <olof@lixom.net>");
+MODULE_DESCRIPTION("PA Semi Electra CF driver");
diff --git a/drivers/pcmcia/i82092.c b/drivers/pcmcia/i82092.c
new file mode 100644
index 000000000..192c9049d
--- /dev/null
+++ b/drivers/pcmcia/i82092.c
@@ -0,0 +1,678 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 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
+ */
+
+#include <linux/kernel.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/ss.h>
+
+#include <linux/io.h>
+
+#include "i82092aa.h"
+#include "i82365.h"
+
+MODULE_LICENSE("GPL");
+
+/* PCI core routines */
+static const struct pci_device_id i82092aa_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82092AA_0) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, i82092aa_pci_ids);
+
+static struct pci_driver i82092aa_pci_driver = {
+ .name = "i82092aa",
+ .id_table = i82092aa_pci_ids,
+ .probe = i82092aa_pci_probe,
+ .remove = i82092aa_pci_remove,
+};
+
+
+/* the pccard structure and its functions */
+static struct pccard_operations i82092aa_operations = {
+ .init = i82092aa_init,
+ .get_status = i82092aa_get_status,
+ .set_socket = i82092aa_set_socket,
+ .set_io_map = i82092aa_set_io_map,
+ .set_mem_map = i82092aa_set_mem_map,
+};
+
+/* The card can do up to 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
+ */
+ unsigned int 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 i82092aa_pci_probe(struct pci_dev *dev,
+ const struct pci_device_id *id)
+{
+ unsigned char configbyte;
+ int i, ret;
+
+ ret = pci_enable_device(dev);
+ if (ret)
+ return ret;
+
+ /* PCI Configuration Control */
+ pci_read_config_byte(dev, 0x40, &configbyte);
+
+ switch (configbyte&6) {
+ case 0:
+ socket_count = 2;
+ break;
+ case 2:
+ socket_count = 1;
+ break;
+ case 4:
+ case 6:
+ socket_count = 4;
+ break;
+
+ default:
+ dev_err(&dev->dev,
+ "Oops, you did something we didn't think of.\n");
+ ret = -EIO;
+ goto err_out_disable;
+ }
+ dev_info(&dev->dev, "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].dev = dev;
+ 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.cb_dev = dev;
+ sockets[i].socket.owner = THIS_MODULE;
+
+ sockets[i].number = i;
+
+ if (card_present(i)) {
+ sockets[i].card_state = 3;
+ dev_dbg(&dev->dev, "slot %i is occupied\n", i);
+ } else {
+ dev_dbg(&dev->dev, "slot %i is vacant\n", i);
+ }
+ }
+
+ /* Now, specifiy that all interrupts are to be done as PCI interrupts
+ * bitmask, one bit per event, 1 = PCI interrupt, 0 = ISA interrupt
+ */
+ configbyte = 0xFF;
+
+ /* PCI Interrupt Routing Register */
+ pci_write_config_byte(dev, 0x50, configbyte);
+
+ /* Register the interrupt handler */
+ dev_dbg(&dev->dev, "Requesting interrupt %i\n", dev->irq);
+ ret = request_irq(dev->irq, i82092aa_interrupt, IRQF_SHARED,
+ "i82092aa", i82092aa_interrupt);
+ if (ret) {
+ dev_err(&dev->dev, "Failed to register IRQ %d, aborting\n",
+ dev->irq);
+ goto err_out_free_res;
+ }
+
+ for (i = 0; i < socket_count; i++) {
+ sockets[i].socket.dev.parent = &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;
+ }
+
+ 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 i82092aa_pci_remove(struct pci_dev *dev)
+{
+ int i;
+
+ free_irq(dev->irq, i82092aa_interrupt);
+
+ for (i = 0; i < socket_count; i++)
+ pcmcia_unregister_socket(&sockets[i].socket);
+}
+
+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;
+}
+
+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)
+{
+ int i;
+ int loopcount = 0;
+ int handled = 0;
+
+ unsigned int events, active = 0;
+
+ while (1) {
+ loopcount++;
+ if (loopcount > 20) {
+ pr_err("i82092aa: infinite eventloop in interrupt\n");
+ break;
+ }
+
+ active = 0;
+
+ for (i = 0; i < socket_count; i++) {
+ int csc;
+
+ /* Inactive socket, should not happen */
+ if (sockets[i].card_state == 0)
+ continue;
+
+ /* card status change register */
+ csc = indirect_read(i, I365_CSC);
+
+ if (csc == 0) /* no events on this socket */
+ continue;
+ handled = 1;
+ events = 0;
+
+ if (csc & I365_CSC_DETECT) {
+ events |= SS_DETECT;
+ dev_info(&sockets[i].dev->dev,
+ "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" */
+ if (csc & I365_CSC_STSCHG)
+ events |= SS_STSCHG;
+ } else {
+ /* Check for battery/ready events */
+ if (csc & I365_CSC_BVD1)
+ events |= SS_BATDEAD;
+ if (csc & I365_CSC_BVD2)
+ events |= SS_BATWARN;
+ if (csc & I365_CSC_READY)
+ events |= SS_READY;
+ }
+
+ if (events)
+ pcmcia_parse_events(&sockets[i].socket, events);
+ active |= events;
+ }
+
+ if (active == 0) /* no more events to handle */
+ break;
+ }
+ return IRQ_RETVAL(handled);
+}
+
+
+
+/* socket functions */
+
+static int card_present(int socketno)
+{
+ unsigned int val;
+
+ 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)
+ return 1;
+
+ return 0;
+}
+
+static void set_bridge_state(int sock)
+{
+ indirect_write(sock, I365_GBLCTL, 0x00);
+ indirect_write(sock, I365_GENCTL, 0x00);
+
+ indirect_setbit(sock, I365_INTCTL, 0x08);
+}
+
+
+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, };
+
+ 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);
+ }
+
+ 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;
+
+ /* Interface Status Register */
+ status = indirect_read(sock, 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(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 */
+
+ return 0;
+}
+
+
+static int i82092aa_set_socket(struct pcmcia_socket *socket,
+ socket_state_t *state)
+{
+ struct socket_info *sock_info = container_of(socket, struct socket_info,
+ socket);
+ unsigned int sock = sock_info->number;
+ unsigned char reg;
+
+ /* First, set the global controller options */
+
+ set_bridge_state(sock);
+
+ /* Values for the IGENC register */
+
+ reg = 0;
+
+ /* The reset bit has "inverse" logic */
+ if (!(state->flags & SS_RESET))
+ reg = reg | I365_PC_RESET;
+ if (state->flags & SS_IOCARD)
+ reg = reg | I365_PC_IOCARD;
+
+ /* IGENC, Interrupt and General Control Register */
+ indirect_write(sock, I365_INTCTL, reg);
+
+ /* Power registers */
+
+ reg = I365_PWR_NORESET; /* default: disable resetdrv on resume */
+
+ if (state->flags & SS_PWR_AUTO) {
+ dev_info(&sock_info->dev->dev, "Auto power\n");
+ reg |= I365_PWR_AUTO; /* automatic power mngmnt */
+ }
+ if (state->flags & SS_OUTPUT_ENA) {
+ dev_info(&sock_info->dev->dev, "Power Enabled\n");
+ reg |= I365_PWR_OUT; /* enable power */
+ }
+
+ switch (state->Vcc) {
+ case 0:
+ break;
+ case 50:
+ dev_info(&sock_info->dev->dev,
+ "setting voltage to Vcc to 5V on socket %i\n",
+ sock);
+ reg |= I365_VCC_5V;
+ break;
+ default:
+ dev_err(&sock_info->dev->dev,
+ "%s called with invalid VCC power value: %i",
+ __func__, state->Vcc);
+ return -EINVAL;
+ }
+
+ switch (state->Vpp) {
+ case 0:
+ dev_info(&sock_info->dev->dev,
+ "not setting Vpp on socket %i\n", sock);
+ break;
+ case 50:
+ dev_info(&sock_info->dev->dev,
+ "setting Vpp to 5.0 for socket %i\n", sock);
+ reg |= I365_VPP1_5V | I365_VPP2_5V;
+ break;
+ case 120:
+ dev_info(&sock_info->dev->dev, "setting Vpp to 12.0\n");
+ reg |= I365_VPP1_12V | I365_VPP2_12V;
+ break;
+ default:
+ dev_err(&sock_info->dev->dev,
+ "%s called with invalid VPP power value: %i",
+ __func__, state->Vcc);
+ 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);
+
+ return 0;
+}
+
+static int i82092aa_set_io_map(struct pcmcia_socket *socket,
+ struct pccard_io_map *io)
+{
+ struct socket_info *sock_info = container_of(socket, struct socket_info,
+ socket);
+ unsigned int sock = sock_info->number;
+ unsigned char map, ioctl;
+
+ map = io->map;
+
+ /* Check error conditions */
+ if (map > 1)
+ return -EINVAL;
+
+ if ((io->start > 0xffff) || (io->stop > 0xffff)
+ || (io->stop < io->start))
+ 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));
+
+ /* 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));
+
+ 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;
+
+ pcibios_resource_to_bus(sock_info->dev->bus, &region, mem->res);
+
+ map = mem->map;
+ if (map > 4)
+ return -EINVAL;
+
+ if ((mem->card_start > 0x3ffffff) || (region.start > region.end) ||
+ (mem->speed > 1000)) {
+ dev_err(&sock_info->dev->dev,
+ "invalid mem map for socket %i: %llx to %llx with a start of %x\n",
+ sock,
+ (unsigned long long)region.start,
+ (unsigned long long)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));
+
+ /* 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)
+ i |= I365_MEM_REG;
+ 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));
+
+ return 0;
+}
+
+static int i82092aa_module_init(void)
+{
+ return pci_register_driver(&i82092aa_pci_driver);
+}
+
+static void i82092aa_module_exit(void)
+{
+ pci_unregister_driver(&i82092aa_pci_driver);
+ if (sockets[0].io_base > 0)
+ release_region(sockets[0].io_base, 2);
+}
+
+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 000000000..0f851acab
--- /dev/null
+++ b/drivers/pcmcia/i82092aa.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _INCLUDE_GUARD_i82092aa_H_
+#define _INCLUDE_GUARD_i82092aa_H_
+
+#include <linux/interrupt.h>
+
+/* 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);
+
+
+
+
+static int i82092aa_get_status(struct pcmcia_socket *socket, u_int *value);
+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 000000000..891ccea2c
--- /dev/null
+++ b/drivers/pcmcia/i82365.c
@@ -0,0 +1,1346 @@
+/*======================================================================
+
+ 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/types.h>
+#include <linux/fcntl.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/timer.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/bitops.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+
+#include <pcmcia/ss.h>
+
+#include <linux/isapnp.h>
+
+/* ISA-bus controllers */
+#include "i82365.h"
+#include "cirrus.h"
+#include "vg468.h"
+#include "ricoh.h"
+
+
+static irqreturn_t i365_count_irq(int, void *);
+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 unsigned 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_hw(i365_base, ulong, ioport, 0444);
+module_param(ignore, int, 0444);
+module_param(extra_sockets, int, 0444);
+module_param_hw(irq_mask, int, other, 0444);
+module_param_hw_array(irq_list, int, irq, &irq_list_count, 0444);
+module_param_hw(cs_irq, int, irq, 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);
+
+/*====================================================================*/
+
+struct cirrus_state {
+ u_char misc1, misc2;
+ u_char timer[6];
+};
+
+struct vg46x_state {
+ u_char ctl, ema;
+};
+
+struct i82365_socket {
+ u_short type, flags;
+ struct pcmcia_socket socket;
+ unsigned int number;
+ unsigned int ioaddr;
+ u_short psock;
+ u_char cs_irq, intr;
+ union {
+ struct cirrus_state cirrus;
+ struct vg46x_state 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! */
+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,
+};
+
+/* 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
+
+struct pcic {
+ char *name;
+ u_short flags;
+};
+
+static struct pcic 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 ARRAY_SIZE(pcic)
+
+/*====================================================================*/
+
+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);
+ {
+ unsigned int 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);
+ {
+ unsigned int 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;
+ struct cirrus_state *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;
+ struct cirrus_state *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];
+ struct cirrus_state *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)
+{
+ struct vg46x_state *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)
+{
+ struct vg46x_state *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)
+{
+ struct vg46x_state *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)
+{
+ i365_get(irq_sock, I365_CSC);
+ irq_hits++;
+ pr_debug("i82365: -> hit on irq %d\n", irq);
+ return IRQ_HANDLED;
+}
+
+static u_int __init test_irq(u_short sock, int irq)
+{
+ pr_debug("i82365: testing ISA irq %d\n", irq);
+ if (request_irq(irq, i365_count_irq, IRQF_PROBE_SHARED, "scan",
+ i365_count_irq) != 0)
+ return 1;
+ irq_hits = 0; irq_sock = sock;
+ msleep(10);
+ if (irq_hits) {
+ free_irq(irq, i365_count_irq);
+ pr_debug("i82365: 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);
+ pr_debug("i82365: 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, IRQF_PROBE_SHARED) == 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(unsigned int 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;
+ unsigned int 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)) &&
+ ((start & 0xfeef) != 0x02e8)) {
+ if (!request_region(start, stop-start+1, "i82365"))
+ return 1;
+ release_region(start, stop-start+1);
+ }
+
+ return 0;
+}
+
+/*====================================================================*/
+
+static void __init add_socket(unsigned int 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 (base == 0) printk("\n");
+ printk(KERN_INFO " %s", pcic[type].name);
+ printk(" ISA-to-PCMCIA at port %#x 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, IRQF_PROBE_SHARED) == 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;
+ unsigned int 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 (!request_region(i365_base, 2, "i82365")) {
+ 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)
+{
+ int i, j, csc;
+ u_int events, active;
+ u_long flags = 0;
+ int handled = 0;
+
+ pr_debug("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);
+ pr_debug("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");
+
+ pr_debug("pcic_interrupt done\n");
+ return IRQ_RETVAL(handled);
+} /* pcic_interrupt */
+
+static void pcic_interrupt_wrapper(struct timer_list *unused)
+{
+ pcic_interrupt(0, 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;
+ }
+ }
+
+ pr_debug("GetStatus(%d) = %#4.4x\n", sock, *value);
+ return 0;
+} /* i365_get_status */
+
+/*====================================================================*/
+
+static int i365_set_socket(u_short sock, socket_state_t *state)
+{
+ struct i82365_socket *t = &socket[sock];
+ u_char reg;
+
+ pr_debug("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;
+
+ pr_debug("SetIOMap(%d, %d, %#2.2x, %d ns, "
+ "%#llx-%#llx)\n", sock, io->map, io->flags, io->speed,
+ (unsigned long long)io->start, (unsigned long long)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;
+
+ pr_debug("SetMemMap(%d, %d, %#2.2x, %d ns, %#llx-%#llx, "
+ "%#x)\n", sock, mem->map, mem->flags, mem->speed,
+ (unsigned long long)mem->res->start,
+ (unsigned long long)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_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,
+ .set_socket = pcic_set_socket,
+ .set_io_map = pcic_set_io_map,
+ .set_mem_map = pcic_set_mem_map,
+};
+
+/*====================================================================*/
+
+static struct platform_driver i82365_driver = {
+ .driver = {
+ .name = "i82365",
+ },
+};
+
+static struct platform_device *i82365_device;
+
+static int __init init_i82365(void)
+{
+ int i, ret;
+
+ ret = platform_driver_register(&i82365_driver);
+ if (ret)
+ goto err_out;
+
+ i82365_device = platform_device_alloc("i82365", 0);
+ if (i82365_device) {
+ ret = platform_device_add(i82365_device);
+ if (ret)
+ platform_device_put(i82365_device);
+ } else
+ ret = -ENOMEM;
+
+ if (ret)
+ goto err_driver_unregister;
+
+ printk(KERN_INFO "Intel ISA PCIC probe: ");
+ sockets = 0;
+
+ isa_probe();
+
+ if (sockets == 0) {
+ printk("not found.\n");
+ ret = -ENODEV;
+ goto err_dev_unregister;
+ }
+
+ /* Set up interrupt handler(s) */
+ if (grab_irq != 0)
+ ret = request_irq(cs_irq, pcic_interrupt, 0, "i82365", pcic_interrupt);
+
+ if (ret)
+ goto err_socket_release;
+
+ /* register sockets with the pcmcia core */
+ for (i = 0; i < sockets; i++) {
+ socket[i].socket.dev.parent = &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;
+ }
+
+ /* Finally, schedule a polling interrupt */
+ if (poll_interval != 0) {
+ timer_setup(&poll_timer, pcic_interrupt_wrapper, 0);
+ poll_timer.expires = jiffies + poll_interval;
+ add_timer(&poll_timer);
+ }
+
+ return 0;
+err_socket_release:
+ for (i = 0; i < sockets; i++) {
+ /* Turn off all interrupt sources! */
+ i365_set(i, I365_CSCINT, 0);
+ release_region(socket[i].ioaddr, 2);
+ }
+err_dev_unregister:
+ platform_device_unregister(i82365_device);
+ release_region(i365_base, 2);
+#ifdef CONFIG_PNP
+ if (i82365_pnpdev)
+ pnp_disable_dev(i82365_pnpdev);
+#endif
+err_driver_unregister:
+ platform_driver_unregister(&i82365_driver);
+err_out:
+ return ret;
+} /* 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);
+ }
+ release_region(i365_base, 2);
+#ifdef CONFIG_PNP
+ if (i82365_pnpdev)
+ pnp_disable_dev(i82365_pnpdev);
+#endif
+ platform_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 000000000..3f84d7a2d
--- /dev/null
+++ b/drivers/pcmcia/i82365.h
@@ -0,0 +1,136 @@
+/*
+ * 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 /* Vpp1 = 5.0v */
+#define I365_VPP1_12V 0x02 /* Vpp1 = 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
+#define I365_CSC_IRQ_MASK 0xF0
+
+/* 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/max1600.c b/drivers/pcmcia/max1600.c
new file mode 100644
index 000000000..379875a5e
--- /dev/null
+++ b/drivers/pcmcia/max1600.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MAX1600 PCMCIA power switch library
+ *
+ * Copyright (C) 2016 Russell King
+ */
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/slab.h>
+#include "max1600.h"
+
+static const char *max1600_gpio_name[2][MAX1600_GPIO_MAX] = {
+ { "a0vcc", "a1vcc", "a0vpp", "a1vpp" },
+ { "b0vcc", "b1vcc", "b0vpp", "b1vpp" },
+};
+
+int max1600_init(struct device *dev, struct max1600 **ptr,
+ unsigned int channel, unsigned int code)
+{
+ struct max1600 *m;
+ int chan;
+ int i;
+
+ switch (channel) {
+ case MAX1600_CHAN_A:
+ chan = 0;
+ break;
+ case MAX1600_CHAN_B:
+ chan = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (code != MAX1600_CODE_LOW && code != MAX1600_CODE_HIGH)
+ return -EINVAL;
+
+ m = devm_kzalloc(dev, sizeof(*m), GFP_KERNEL);
+ if (!m)
+ return -ENOMEM;
+
+ m->dev = dev;
+ m->code = code;
+
+ for (i = 0; i < MAX1600_GPIO_MAX; i++) {
+ const char *name;
+
+ name = max1600_gpio_name[chan][i];
+ if (i != MAX1600_GPIO_0VPP) {
+ m->gpio[i] = devm_gpiod_get(dev, name, GPIOD_OUT_LOW);
+ } else {
+ m->gpio[i] = devm_gpiod_get_optional(dev, name,
+ GPIOD_OUT_LOW);
+ if (!m->gpio[i])
+ break;
+ }
+ if (IS_ERR(m->gpio[i]))
+ return PTR_ERR(m->gpio[i]);
+ }
+
+ *ptr = m;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(max1600_init);
+
+int max1600_configure(struct max1600 *m, unsigned int vcc, unsigned int vpp)
+{
+ DECLARE_BITMAP(values, MAX1600_GPIO_MAX) = { 0, };
+ int n = MAX1600_GPIO_0VPP;
+
+ if (m->gpio[MAX1600_GPIO_0VPP]) {
+ if (vpp == 0) {
+ __assign_bit(MAX1600_GPIO_0VPP, values, 0);
+ __assign_bit(MAX1600_GPIO_1VPP, values, 0);
+ } else if (vpp == 120) {
+ __assign_bit(MAX1600_GPIO_0VPP, values, 0);
+ __assign_bit(MAX1600_GPIO_1VPP, values, 1);
+ } else if (vpp == vcc) {
+ __assign_bit(MAX1600_GPIO_0VPP, values, 1);
+ __assign_bit(MAX1600_GPIO_1VPP, values, 0);
+ } else {
+ dev_err(m->dev, "unrecognised Vpp %u.%uV\n",
+ vpp / 10, vpp % 10);
+ return -EINVAL;
+ }
+ n = MAX1600_GPIO_MAX;
+ } else if (vpp != vcc && vpp != 0) {
+ dev_err(m->dev, "no VPP control\n");
+ return -EINVAL;
+ }
+
+ if (vcc == 0) {
+ __assign_bit(MAX1600_GPIO_0VCC, values, 0);
+ __assign_bit(MAX1600_GPIO_1VCC, values, 0);
+ } else if (vcc == 33) { /* VY */
+ __assign_bit(MAX1600_GPIO_0VCC, values, 1);
+ __assign_bit(MAX1600_GPIO_1VCC, values, 0);
+ } else if (vcc == 50) { /* VX */
+ __assign_bit(MAX1600_GPIO_0VCC, values, 0);
+ __assign_bit(MAX1600_GPIO_1VCC, values, 1);
+ } else {
+ dev_err(m->dev, "unrecognised Vcc %u.%uV\n",
+ vcc / 10, vcc % 10);
+ return -EINVAL;
+ }
+
+ if (m->code == MAX1600_CODE_HIGH) {
+ /*
+ * Cirrus mode appears to be the same as Intel mode,
+ * except the VCC pins are inverted.
+ */
+ __change_bit(MAX1600_GPIO_0VCC, values);
+ __change_bit(MAX1600_GPIO_1VCC, values);
+ }
+
+ return gpiod_set_array_value_cansleep(n, m->gpio, NULL, values);
+}
+EXPORT_SYMBOL_GPL(max1600_configure);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/pcmcia/max1600.h b/drivers/pcmcia/max1600.h
new file mode 100644
index 000000000..00bf1a094
--- /dev/null
+++ b/drivers/pcmcia/max1600.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef MAX1600_H
+#define MAX1600_H
+
+struct gpio_desc;
+
+enum {
+ MAX1600_GPIO_0VCC = 0,
+ MAX1600_GPIO_1VCC,
+ MAX1600_GPIO_0VPP,
+ MAX1600_GPIO_1VPP,
+ MAX1600_GPIO_MAX,
+
+ MAX1600_CHAN_A,
+ MAX1600_CHAN_B,
+
+ MAX1600_CODE_LOW,
+ MAX1600_CODE_HIGH,
+};
+
+struct max1600 {
+ struct gpio_desc *gpio[MAX1600_GPIO_MAX];
+ struct device *dev;
+ unsigned int code;
+};
+
+int max1600_init(struct device *dev, struct max1600 **ptr,
+ unsigned int channel, unsigned int code);
+
+int max1600_configure(struct max1600 *, unsigned int vcc, unsigned int vpp);
+
+#endif
diff --git a/drivers/pcmcia/o2micro.h b/drivers/pcmcia/o2micro.h
new file mode 100644
index 000000000..5096e92c7
--- /dev/null
+++ b/drivers/pcmcia/o2micro.h
@@ -0,0 +1,183 @@
+/*
+ * 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
+
+/* 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
+
+#define O2_RESERVED1 0x94
+#define O2_RESERVED2 0xD4
+#define O2_RES_READ_PREFETCH 0x02
+#define O2_RES_WRITE_BURST 0x08
+
+static int o2micro_override(struct yenta_socket *socket)
+{
+ /*
+ * 'reserved' register at 0x94/D4. allows setting read prefetch and write
+ * bursting. read prefetching 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;
+ bool use_speedup;
+
+ if (PCI_FUNC(socket->dev->devfn) == 0) {
+ a = config_readb(socket, O2_RESERVED1);
+ b = config_readb(socket, O2_RESERVED2);
+ dev_dbg(&socket->dev->dev, "O2: 0x94/0xD4: %02x/%02x\n", a, b);
+
+ switch (socket->dev->device) {
+ /*
+ * older bridges have problems with both read prefetch and write
+ * bursting depending on the combination of the chipset, bridge
+ * and the cardbus card. so disable them to be on the safe side.
+ */
+ case PCI_DEVICE_ID_O2_6729:
+ case PCI_DEVICE_ID_O2_6730:
+ case PCI_DEVICE_ID_O2_6812:
+ case PCI_DEVICE_ID_O2_6832:
+ case PCI_DEVICE_ID_O2_6836:
+ case PCI_DEVICE_ID_O2_6933:
+ use_speedup = false;
+ break;
+ default:
+ use_speedup = true;
+ break;
+ }
+
+ /* the user may override our decision */
+ if (strcasecmp(o2_speedup, "on") == 0)
+ use_speedup = true;
+ else if (strcasecmp(o2_speedup, "off") == 0)
+ use_speedup = false;
+ else if (strcasecmp(o2_speedup, "default") != 0)
+ dev_warn(&socket->dev->dev,
+ "O2: Unknown parameter, using 'default'");
+
+ if (use_speedup) {
+ dev_info(&socket->dev->dev,
+ "O2: enabling read prefetch/write burst. If you experience problems or performance issues, use the yenta_socket parameter 'o2_speedup=off'\n");
+ config_writeb(socket, O2_RESERVED1,
+ a | O2_RES_READ_PREFETCH | O2_RES_WRITE_BURST);
+ config_writeb(socket, O2_RESERVED2,
+ b | O2_RES_READ_PREFETCH | O2_RES_WRITE_BURST);
+ } else {
+ dev_info(&socket->dev->dev,
+ "O2: disabling read prefetch/write burst. If you experience problems or performance issues, use the yenta_socket parameter 'o2_speedup=on'\n");
+ config_writeb(socket, O2_RESERVED1,
+ a & ~(O2_RES_READ_PREFETCH | O2_RES_WRITE_BURST));
+ config_writeb(socket, O2_RESERVED2,
+ b & ~(O2_RES_READ_PREFETCH | O2_RES_WRITE_BURST));
+ }
+ }
+
+ 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/omap_cf.c b/drivers/pcmcia/omap_cf.c
new file mode 100644
index 000000000..d3ef55349
--- /dev/null
+++ b/drivers/pcmcia/omap_cf.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * omap_cf.c -- OMAP 16xx CompactFlash controller driver
+ *
+ * Copyright (c) 2005 David Brownell
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+
+#include <pcmcia/ss.h>
+
+#include <mach/hardware.h>
+#include <asm/io.h>
+#include <linux/sizes.h>
+
+#include <mach/mux.h>
+#include <mach/tc.h>
+
+
+/* NOTE: don't expect this to support many I/O cards. The 16xx chips have
+ * hard-wired timings to support Compact Flash memory cards; they won't work
+ * with various other devices (like WLAN adapters) without some external
+ * logic to help out.
+ *
+ * NOTE: CF controller docs disagree with address space docs as to where
+ * CF_BASE really lives; this is a doc erratum.
+ */
+#define CF_BASE 0xfffe2800
+
+/* status; read after IRQ */
+#define CF_STATUS (CF_BASE + 0x00)
+# define CF_STATUS_BAD_READ (1 << 2)
+# define CF_STATUS_BAD_WRITE (1 << 1)
+# define CF_STATUS_CARD_DETECT (1 << 0)
+
+/* which chipselect (CS0..CS3) is used for CF (active low) */
+#define CF_CFG (CF_BASE + 0x02)
+
+/* card reset */
+#define CF_CONTROL (CF_BASE + 0x04)
+# define CF_CONTROL_RESET (1 << 0)
+
+#define omap_cf_present() (!(omap_readw(CF_STATUS) & CF_STATUS_CARD_DETECT))
+
+/*--------------------------------------------------------------------------*/
+
+static const char driver_name[] = "omap_cf";
+
+struct omap_cf_socket {
+ struct pcmcia_socket socket;
+
+ struct timer_list timer;
+ unsigned present:1;
+ unsigned active:1;
+
+ struct platform_device *pdev;
+ unsigned long phys_cf;
+ u_int irq;
+ struct resource iomem;
+};
+
+#define POLL_INTERVAL (2 * HZ)
+
+/*--------------------------------------------------------------------------*/
+
+static int omap_cf_ss_init(struct pcmcia_socket *s)
+{
+ return 0;
+}
+
+/* the timer is primarily to kick this socket's pccardd */
+static void omap_cf_timer(struct timer_list *t)
+{
+ struct omap_cf_socket *cf = from_timer(cf, t, timer);
+ unsigned present = omap_cf_present();
+
+ if (present != cf->present) {
+ cf->present = present;
+ pr_debug("%s: card %s\n", driver_name,
+ present ? "present" : "gone");
+ pcmcia_parse_events(&cf->socket, SS_DETECT);
+ }
+
+ if (cf->active)
+ mod_timer(&cf->timer, jiffies + POLL_INTERVAL);
+}
+
+/* This irq handler prevents "irqNNN: nobody cared" messages as drivers
+ * claim the card's IRQ. It may also detect some card insertions, but
+ * not removals; it can't always eliminate timer irqs.
+ */
+static irqreturn_t omap_cf_irq(int irq, void *_cf)
+{
+ struct omap_cf_socket *cf = (struct omap_cf_socket *)_cf;
+
+ omap_cf_timer(&cf->timer);
+ return IRQ_HANDLED;
+}
+
+static int omap_cf_get_status(struct pcmcia_socket *s, u_int *sp)
+{
+ if (!sp)
+ return -EINVAL;
+
+ /* NOTE CF is always 3VCARD */
+ if (omap_cf_present()) {
+ struct omap_cf_socket *cf;
+
+ *sp = SS_READY | SS_DETECT | SS_POWERON | SS_3VCARD;
+ cf = container_of(s, struct omap_cf_socket, socket);
+ s->pcmcia_irq = 0;
+ s->pci_irq = cf->irq;
+ } else
+ *sp = 0;
+ return 0;
+}
+
+static int
+omap_cf_set_socket(struct pcmcia_socket *sock, struct socket_state_t *s)
+{
+ u16 control;
+
+ /* REVISIT some non-OSK boards may support power switching */
+ switch (s->Vcc) {
+ case 0:
+ case 33:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ control = omap_readw(CF_CONTROL);
+ if (s->flags & SS_RESET)
+ omap_writew(CF_CONTROL_RESET, CF_CONTROL);
+ else
+ omap_writew(0, CF_CONTROL);
+
+ pr_debug("%s: Vcc %d, io_irq %d, flags %04x csc %04x\n",
+ driver_name, s->Vcc, s->io_irq, s->flags, s->csc_mask);
+
+ return 0;
+}
+
+static int omap_cf_ss_suspend(struct pcmcia_socket *s)
+{
+ pr_debug("%s: %s\n", driver_name, __func__);
+ return omap_cf_set_socket(s, &dead_socket);
+}
+
+/* regions are 2K each: mem, attrib, io (and reserved-for-ide) */
+
+static int
+omap_cf_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io)
+{
+ struct omap_cf_socket *cf;
+
+ cf = container_of(s, struct omap_cf_socket, socket);
+ io->flags &= MAP_ACTIVE|MAP_ATTRIB|MAP_16BIT;
+ io->start = cf->phys_cf + SZ_4K;
+ io->stop = io->start + SZ_2K - 1;
+ return 0;
+}
+
+static int
+omap_cf_set_mem_map(struct pcmcia_socket *s, struct pccard_mem_map *map)
+{
+ struct omap_cf_socket *cf;
+
+ if (map->card_start)
+ return -EINVAL;
+ cf = container_of(s, struct omap_cf_socket, socket);
+ map->static_start = cf->phys_cf;
+ map->flags &= MAP_ACTIVE|MAP_ATTRIB|MAP_16BIT;
+ if (map->flags & MAP_ATTRIB)
+ map->static_start += SZ_2K;
+ return 0;
+}
+
+static struct pccard_operations omap_cf_ops = {
+ .init = omap_cf_ss_init,
+ .suspend = omap_cf_ss_suspend,
+ .get_status = omap_cf_get_status,
+ .set_socket = omap_cf_set_socket,
+ .set_io_map = omap_cf_set_io_map,
+ .set_mem_map = omap_cf_set_mem_map,
+};
+
+/*--------------------------------------------------------------------------*/
+
+/*
+ * NOTE: right now the only board-specific platform_data is
+ * "what chipselect is used". Boards could want more.
+ */
+
+static int __init omap_cf_probe(struct platform_device *pdev)
+{
+ unsigned seg;
+ struct omap_cf_socket *cf;
+ int irq;
+ int status;
+
+ seg = (int) pdev->dev.platform_data;
+ if (seg == 0 || seg > 3)
+ return -ENODEV;
+
+ /* either CFLASH.IREQ (INT_1610_CF) or some GPIO */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return -EINVAL;
+
+ cf = kzalloc(sizeof *cf, GFP_KERNEL);
+ if (!cf)
+ return -ENOMEM;
+ timer_setup(&cf->timer, omap_cf_timer, 0);
+
+ cf->pdev = pdev;
+ platform_set_drvdata(pdev, cf);
+
+ /* this primarily just shuts up irq handling noise */
+ status = request_irq(irq, omap_cf_irq, IRQF_SHARED,
+ driver_name, cf);
+ if (status < 0)
+ goto fail0;
+ cf->irq = irq;
+ cf->socket.pci_irq = irq;
+
+ switch (seg) {
+ /* NOTE: CS0 could be configured too ... */
+ case 1:
+ cf->phys_cf = OMAP_CS1_PHYS;
+ break;
+ case 2:
+ cf->phys_cf = OMAP_CS2_PHYS;
+ break;
+ case 3:
+ cf->phys_cf = omap_cs3_phys();
+ break;
+ default:
+ goto fail1;
+ }
+ cf->iomem.start = cf->phys_cf;
+ cf->iomem.end = cf->iomem.end + SZ_8K - 1;
+ cf->iomem.flags = IORESOURCE_MEM;
+
+ /* pcmcia layer only remaps "real" memory */
+ cf->socket.io_offset = (unsigned long)
+ ioremap(cf->phys_cf + SZ_4K, SZ_2K);
+ if (!cf->socket.io_offset)
+ goto fail1;
+
+ if (!request_mem_region(cf->phys_cf, SZ_8K, driver_name))
+ goto fail1;
+
+ /* NOTE: CF conflicts with MMC1 */
+ omap_cfg_reg(W11_1610_CF_CD1);
+ omap_cfg_reg(P11_1610_CF_CD2);
+ omap_cfg_reg(R11_1610_CF_IOIS16);
+ omap_cfg_reg(V10_1610_CF_IREQ);
+ omap_cfg_reg(W10_1610_CF_RESET);
+
+ omap_writew(~(1 << seg), CF_CFG);
+
+ pr_info("%s: cs%d on irq %d\n", driver_name, seg, irq);
+
+ /* NOTE: better EMIFS setup might support more cards; but the
+ * TRM only shows how to affect regular flash signals, not their
+ * CF/PCMCIA variants...
+ */
+ pr_debug("%s: cs%d, previous ccs %08x acs %08x\n", driver_name,
+ seg, omap_readl(EMIFS_CCS(seg)), omap_readl(EMIFS_ACS(seg)));
+ omap_writel(0x0004a1b3, EMIFS_CCS(seg)); /* synch mode 4 etc */
+ omap_writel(0x00000000, EMIFS_ACS(seg)); /* OE hold/setup */
+
+ /* CF uses armxor_ck, which is "always" available */
+
+ pr_debug("%s: sts %04x cfg %04x control %04x %s\n", driver_name,
+ omap_readw(CF_STATUS), omap_readw(CF_CFG),
+ omap_readw(CF_CONTROL),
+ omap_cf_present() ? "present" : "(not present)");
+
+ cf->socket.owner = THIS_MODULE;
+ cf->socket.dev.parent = &pdev->dev;
+ cf->socket.ops = &omap_cf_ops;
+ cf->socket.resource_ops = &pccard_static_ops;
+ cf->socket.features = SS_CAP_PCCARD | SS_CAP_STATIC_MAP
+ | SS_CAP_MEM_ALIGN;
+ cf->socket.map_size = SZ_2K;
+ cf->socket.io[0].res = &cf->iomem;
+
+ status = pcmcia_register_socket(&cf->socket);
+ if (status < 0)
+ goto fail2;
+
+ cf->active = 1;
+ mod_timer(&cf->timer, jiffies + POLL_INTERVAL);
+ return 0;
+
+fail2:
+ release_mem_region(cf->phys_cf, SZ_8K);
+fail1:
+ if (cf->socket.io_offset)
+ iounmap((void __iomem *) cf->socket.io_offset);
+ free_irq(irq, cf);
+fail0:
+ kfree(cf);
+ return status;
+}
+
+static int __exit omap_cf_remove(struct platform_device *pdev)
+{
+ struct omap_cf_socket *cf = platform_get_drvdata(pdev);
+
+ cf->active = 0;
+ pcmcia_unregister_socket(&cf->socket);
+ del_timer_sync(&cf->timer);
+ iounmap((void __iomem *) cf->socket.io_offset);
+ release_mem_region(cf->phys_cf, SZ_8K);
+ free_irq(cf->irq, cf);
+ kfree(cf);
+ return 0;
+}
+
+static struct platform_driver omap_cf_driver = {
+ .driver = {
+ .name = driver_name,
+ },
+ .remove = __exit_p(omap_cf_remove),
+};
+
+static int __init omap_cf_init(void)
+{
+ if (cpu_is_omap16xx())
+ return platform_driver_probe(&omap_cf_driver, omap_cf_probe);
+ return -ENODEV;
+}
+
+static void __exit omap_cf_exit(void)
+{
+ if (cpu_is_omap16xx())
+ platform_driver_unregister(&omap_cf_driver);
+}
+
+module_init(omap_cf_init);
+module_exit(omap_cf_exit);
+
+MODULE_DESCRIPTION("OMAP CF Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:omap_cf");
diff --git a/drivers/pcmcia/pcmcia_cis.c b/drivers/pcmcia/pcmcia_cis.c
new file mode 100644
index 000000000..e4c4daf92
--- /dev/null
+++ b/drivers/pcmcia/pcmcia_cis.c
@@ -0,0 +1,434 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PCMCIA high-level CIS access 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) 1999 David A. Hinds
+ * Copyright (C) 2004-2010 Dominik Brodowski
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+
+#include <pcmcia/cisreg.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/ss.h>
+#include <pcmcia/ds.h>
+#include "cs_internal.h"
+
+
+/**
+ * pccard_read_tuple() - internal CIS tuple access
+ * @s: the struct pcmcia_socket where the card is inserted
+ * @function: the device function we loop for
+ * @code: which CIS code shall we look for?
+ * @parse: buffer where the tuple shall be parsed (or NULL, if no parse)
+ *
+ * pccard_read_tuple() reads out one tuple and attempts to parse it
+ */
+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) {
+ dev_warn(&s->dev, "no memory to read tuple\n");
+ return -ENOMEM;
+ }
+ tuple.DesiredTuple = code;
+ tuple.Attributes = 0;
+ if (function == BIND_FN_ALL)
+ tuple.Attributes = TUPLE_RETURN_COMMON;
+ ret = pccard_get_first_tuple(s, function, &tuple);
+ if (ret != 0)
+ goto done;
+ tuple.TupleData = buf;
+ tuple.TupleOffset = 0;
+ tuple.TupleDataMax = 255;
+ ret = pccard_get_tuple_data(s, &tuple);
+ if (ret != 0)
+ goto done;
+ ret = pcmcia_parse_tuple(&tuple, parse);
+done:
+ kfree(buf);
+ return ret;
+}
+
+
+/**
+ * pccard_loop_tuple() - loop over tuples in the CIS
+ * @s: the struct pcmcia_socket where the card is inserted
+ * @function: the device function we loop for
+ * @code: which CIS code shall we look for?
+ * @parse: buffer where the tuple shall be parsed (or NULL, if no parse)
+ * @priv_data: private data to be passed to the loop_tuple function.
+ * @loop_tuple: function to call for each CIS entry of type @function. IT
+ * gets passed the raw tuple, the paresed tuple (if @parse is
+ * set) and @priv_data.
+ *
+ * pccard_loop_tuple() loops over all CIS entries of type @function, and
+ * calls the @loop_tuple function for each entry. If the call to @loop_tuple
+ * returns 0, the loop exits. Returns 0 on success or errorcode otherwise.
+ */
+static int pccard_loop_tuple(struct pcmcia_socket *s, unsigned int function,
+ cisdata_t code, cisparse_t *parse, void *priv_data,
+ int (*loop_tuple) (tuple_t *tuple,
+ cisparse_t *parse,
+ void *priv_data))
+{
+ tuple_t tuple;
+ cisdata_t *buf;
+ int ret;
+
+ buf = kzalloc(256, GFP_KERNEL);
+ if (buf == NULL) {
+ dev_warn(&s->dev, "no memory to read tuple\n");
+ return -ENOMEM;
+ }
+
+ tuple.TupleData = buf;
+ tuple.TupleDataMax = 255;
+ tuple.TupleOffset = 0;
+ tuple.DesiredTuple = code;
+ tuple.Attributes = 0;
+
+ ret = pccard_get_first_tuple(s, function, &tuple);
+ while (!ret) {
+ if (pccard_get_tuple_data(s, &tuple))
+ goto next_entry;
+
+ if (parse)
+ if (pcmcia_parse_tuple(&tuple, parse))
+ goto next_entry;
+
+ ret = loop_tuple(&tuple, parse, priv_data);
+ if (!ret)
+ break;
+
+next_entry:
+ ret = pccard_get_next_tuple(s, function, &tuple);
+ }
+
+ kfree(buf);
+ return ret;
+}
+
+
+/**
+ * pcmcia_io_cfg_data_width() - convert cfgtable to data path width parameter
+ */
+static int pcmcia_io_cfg_data_width(unsigned int flags)
+{
+ if (!(flags & CISTPL_IO_8BIT))
+ return IO_DATA_PATH_WIDTH_16;
+ if (!(flags & CISTPL_IO_16BIT))
+ return IO_DATA_PATH_WIDTH_8;
+ return IO_DATA_PATH_WIDTH_AUTO;
+}
+
+
+struct pcmcia_cfg_mem {
+ struct pcmcia_device *p_dev;
+ int (*conf_check) (struct pcmcia_device *p_dev, void *priv_data);
+ void *priv_data;
+ cisparse_t parse;
+ cistpl_cftable_entry_t dflt;
+};
+
+/**
+ * pcmcia_do_loop_config() - internal helper for pcmcia_loop_config()
+ *
+ * pcmcia_do_loop_config() is the internal callback for the call from
+ * pcmcia_loop_config() to pccard_loop_tuple(). Data is transferred
+ * by a struct pcmcia_cfg_mem.
+ */
+static int pcmcia_do_loop_config(tuple_t *tuple, cisparse_t *parse, void *priv)
+{
+ struct pcmcia_cfg_mem *cfg_mem = priv;
+ struct pcmcia_device *p_dev = cfg_mem->p_dev;
+ cistpl_cftable_entry_t *cfg = &parse->cftable_entry;
+ cistpl_cftable_entry_t *dflt = &cfg_mem->dflt;
+ unsigned int flags = p_dev->config_flags;
+ unsigned int vcc = p_dev->socket->socket.Vcc;
+
+ dev_dbg(&p_dev->dev, "testing configuration %x, autoconf %x\n",
+ cfg->index, flags);
+
+ /* default values */
+ cfg_mem->p_dev->config_index = cfg->index;
+ if (cfg->flags & CISTPL_CFTABLE_DEFAULT)
+ cfg_mem->dflt = *cfg;
+
+ /* check for matching Vcc? */
+ if (flags & CONF_AUTO_CHECK_VCC) {
+ if (cfg->vcc.present & (1 << CISTPL_POWER_VNOM)) {
+ if (vcc != cfg->vcc.param[CISTPL_POWER_VNOM] / 10000)
+ return -ENODEV;
+ } else if (dflt->vcc.present & (1 << CISTPL_POWER_VNOM)) {
+ if (vcc != dflt->vcc.param[CISTPL_POWER_VNOM] / 10000)
+ return -ENODEV;
+ }
+ }
+
+ /* set Vpp? */
+ if (flags & CONF_AUTO_SET_VPP) {
+ if (cfg->vpp1.present & (1 << CISTPL_POWER_VNOM))
+ p_dev->vpp = cfg->vpp1.param[CISTPL_POWER_VNOM] / 10000;
+ else if (dflt->vpp1.present & (1 << CISTPL_POWER_VNOM))
+ p_dev->vpp =
+ dflt->vpp1.param[CISTPL_POWER_VNOM] / 10000;
+ }
+
+ /* enable audio? */
+ if ((flags & CONF_AUTO_AUDIO) && (cfg->flags & CISTPL_CFTABLE_AUDIO))
+ p_dev->config_flags |= CONF_ENABLE_SPKR;
+
+
+ /* IO window settings? */
+ if (flags & CONF_AUTO_SET_IO) {
+ cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt->io;
+ int i = 0;
+
+ p_dev->resource[0]->start = p_dev->resource[0]->end = 0;
+ p_dev->resource[1]->start = p_dev->resource[1]->end = 0;
+ if (io->nwin == 0)
+ return -ENODEV;
+
+ p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH;
+ p_dev->resource[0]->flags |=
+ pcmcia_io_cfg_data_width(io->flags);
+ if (io->nwin > 1) {
+ /* For multifunction cards, by convention, we
+ * configure the network function with window 0,
+ * and serial with window 1 */
+ i = (io->win[1].len > io->win[0].len);
+ p_dev->resource[1]->flags = p_dev->resource[0]->flags;
+ p_dev->resource[1]->start = io->win[1-i].base;
+ p_dev->resource[1]->end = io->win[1-i].len;
+ }
+ p_dev->resource[0]->start = io->win[i].base;
+ p_dev->resource[0]->end = io->win[i].len;
+ p_dev->io_lines = io->flags & CISTPL_IO_LINES_MASK;
+ }
+
+ /* MEM window settings? */
+ if (flags & CONF_AUTO_SET_IOMEM) {
+ /* so far, we only set one memory window */
+ cistpl_mem_t *mem = (cfg->mem.nwin) ? &cfg->mem : &dflt->mem;
+
+ p_dev->resource[2]->start = p_dev->resource[2]->end = 0;
+ if (mem->nwin == 0)
+ return -ENODEV;
+
+ p_dev->resource[2]->start = mem->win[0].host_addr;
+ p_dev->resource[2]->end = mem->win[0].len;
+ if (p_dev->resource[2]->end < 0x1000)
+ p_dev->resource[2]->end = 0x1000;
+ p_dev->card_addr = mem->win[0].card_addr;
+ }
+
+ dev_dbg(&p_dev->dev,
+ "checking configuration %x: %pr %pr %pr (%d lines)\n",
+ p_dev->config_index, p_dev->resource[0], p_dev->resource[1],
+ p_dev->resource[2], p_dev->io_lines);
+
+ return cfg_mem->conf_check(p_dev, cfg_mem->priv_data);
+}
+
+/**
+ * pcmcia_loop_config() - loop over configuration options
+ * @p_dev: the struct pcmcia_device which we need to loop for.
+ * @conf_check: function to call for each configuration option.
+ * It gets passed the struct pcmcia_device and private data
+ * being passed to pcmcia_loop_config()
+ * @priv_data: private data to be passed to the conf_check function.
+ *
+ * pcmcia_loop_config() loops over all configuration options, and calls
+ * the driver-specific conf_check() for each one, checking whether
+ * it is a valid one. Returns 0 on success or errorcode otherwise.
+ */
+int pcmcia_loop_config(struct pcmcia_device *p_dev,
+ int (*conf_check) (struct pcmcia_device *p_dev,
+ void *priv_data),
+ void *priv_data)
+{
+ struct pcmcia_cfg_mem *cfg_mem;
+ int ret;
+
+ cfg_mem = kzalloc(sizeof(struct pcmcia_cfg_mem), GFP_KERNEL);
+ if (cfg_mem == NULL)
+ return -ENOMEM;
+
+ cfg_mem->p_dev = p_dev;
+ cfg_mem->conf_check = conf_check;
+ cfg_mem->priv_data = priv_data;
+
+ ret = pccard_loop_tuple(p_dev->socket, p_dev->func,
+ CISTPL_CFTABLE_ENTRY, &cfg_mem->parse,
+ cfg_mem, pcmcia_do_loop_config);
+
+ kfree(cfg_mem);
+ return ret;
+}
+EXPORT_SYMBOL(pcmcia_loop_config);
+
+
+struct pcmcia_loop_mem {
+ struct pcmcia_device *p_dev;
+ void *priv_data;
+ int (*loop_tuple) (struct pcmcia_device *p_dev,
+ tuple_t *tuple,
+ void *priv_data);
+};
+
+/**
+ * pcmcia_do_loop_tuple() - internal helper for pcmcia_loop_config()
+ *
+ * pcmcia_do_loop_tuple() is the internal callback for the call from
+ * pcmcia_loop_tuple() to pccard_loop_tuple(). Data is transferred
+ * by a struct pcmcia_cfg_mem.
+ */
+static int pcmcia_do_loop_tuple(tuple_t *tuple, cisparse_t *parse, void *priv)
+{
+ struct pcmcia_loop_mem *loop = priv;
+
+ return loop->loop_tuple(loop->p_dev, tuple, loop->priv_data);
+};
+
+/**
+ * pcmcia_loop_tuple() - loop over tuples in the CIS
+ * @p_dev: the struct pcmcia_device which we need to loop for.
+ * @code: which CIS code shall we look for?
+ * @priv_data: private data to be passed to the loop_tuple function.
+ * @loop_tuple: function to call for each CIS entry of type @function. IT
+ * gets passed the raw tuple and @priv_data.
+ *
+ * pcmcia_loop_tuple() loops over all CIS entries of type @function, and
+ * calls the @loop_tuple function for each entry. If the call to @loop_tuple
+ * returns 0, the loop exits. Returns 0 on success or errorcode otherwise.
+ */
+int pcmcia_loop_tuple(struct pcmcia_device *p_dev, cisdata_t code,
+ int (*loop_tuple) (struct pcmcia_device *p_dev,
+ tuple_t *tuple,
+ void *priv_data),
+ void *priv_data)
+{
+ struct pcmcia_loop_mem loop = {
+ .p_dev = p_dev,
+ .loop_tuple = loop_tuple,
+ .priv_data = priv_data};
+
+ return pccard_loop_tuple(p_dev->socket, p_dev->func, code, NULL,
+ &loop, pcmcia_do_loop_tuple);
+}
+EXPORT_SYMBOL(pcmcia_loop_tuple);
+
+
+struct pcmcia_loop_get {
+ size_t len;
+ cisdata_t **buf;
+};
+
+/**
+ * pcmcia_do_get_tuple() - internal helper for pcmcia_get_tuple()
+ *
+ * pcmcia_do_get_tuple() is the internal callback for the call from
+ * pcmcia_get_tuple() to pcmcia_loop_tuple(). As we're only interested in
+ * the first tuple, return 0 unconditionally. Create a memory buffer large
+ * enough to hold the content of the tuple, and fill it with the tuple data.
+ * The caller is responsible to free the buffer.
+ */
+static int pcmcia_do_get_tuple(struct pcmcia_device *p_dev, tuple_t *tuple,
+ void *priv)
+{
+ struct pcmcia_loop_get *get = priv;
+
+ *get->buf = kzalloc(tuple->TupleDataLen, GFP_KERNEL);
+ if (*get->buf) {
+ get->len = tuple->TupleDataLen;
+ memcpy(*get->buf, tuple->TupleData, tuple->TupleDataLen);
+ } else
+ dev_dbg(&p_dev->dev, "do_get_tuple: out of memory\n");
+ return 0;
+}
+
+/**
+ * pcmcia_get_tuple() - get first tuple from CIS
+ * @p_dev: the struct pcmcia_device which we need to loop for.
+ * @code: which CIS code shall we look for?
+ * @buf: pointer to store the buffer to.
+ *
+ * pcmcia_get_tuple() gets the content of the first CIS entry of type @code.
+ * It returns the buffer length (or zero). The caller is responsible to free
+ * the buffer passed in @buf.
+ */
+size_t pcmcia_get_tuple(struct pcmcia_device *p_dev, cisdata_t code,
+ unsigned char **buf)
+{
+ struct pcmcia_loop_get get = {
+ .len = 0,
+ .buf = buf,
+ };
+
+ *get.buf = NULL;
+ pcmcia_loop_tuple(p_dev, code, pcmcia_do_get_tuple, &get);
+
+ return get.len;
+}
+EXPORT_SYMBOL(pcmcia_get_tuple);
+
+
+/**
+ * pcmcia_do_get_mac() - internal helper for pcmcia_get_mac_from_cis()
+ *
+ * pcmcia_do_get_mac() is the internal callback for the call from
+ * pcmcia_get_mac_from_cis() to pcmcia_loop_tuple(). We check whether the
+ * tuple contains a proper LAN_NODE_ID of length 6, and copy the data
+ * to struct net_device->dev_addr[i].
+ */
+static int pcmcia_do_get_mac(struct pcmcia_device *p_dev, tuple_t *tuple,
+ void *priv)
+{
+ struct net_device *dev = priv;
+ int i;
+
+ if (tuple->TupleData[0] != CISTPL_FUNCE_LAN_NODE_ID)
+ return -EINVAL;
+ if (tuple->TupleDataLen < ETH_ALEN + 2) {
+ dev_warn(&p_dev->dev, "Invalid CIS tuple length for "
+ "LAN_NODE_ID\n");
+ return -EINVAL;
+ }
+
+ if (tuple->TupleData[1] != ETH_ALEN) {
+ dev_warn(&p_dev->dev, "Invalid header for LAN_NODE_ID\n");
+ return -EINVAL;
+ }
+ for (i = 0; i < 6; i++)
+ dev->dev_addr[i] = tuple->TupleData[i+2];
+ return 0;
+}
+
+/**
+ * pcmcia_get_mac_from_cis() - read out MAC address from CISTPL_FUNCE
+ * @p_dev: the struct pcmcia_device for which we want the address.
+ * @dev: a properly prepared struct net_device to store the info to.
+ *
+ * pcmcia_get_mac_from_cis() reads out the hardware MAC address from
+ * CISTPL_FUNCE and stores it into struct net_device *dev->dev_addr which
+ * must be set up properly by the driver (see examples!).
+ */
+int pcmcia_get_mac_from_cis(struct pcmcia_device *p_dev, struct net_device *dev)
+{
+ return pcmcia_loop_tuple(p_dev, CISTPL_FUNCE, pcmcia_do_get_mac, dev);
+}
+EXPORT_SYMBOL(pcmcia_get_mac_from_cis);
+
diff --git a/drivers/pcmcia/pcmcia_resource.c b/drivers/pcmcia/pcmcia_resource.c
new file mode 100644
index 000000000..e3a6b6c8a
--- /dev/null
+++ b/drivers/pcmcia/pcmcia_resource.c
@@ -0,0 +1,957 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PCMCIA 16-bit resource management 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) 1999 David A. Hinds
+ * Copyright (C) 2004-2010 Dominik Brodowski
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/device.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+
+#include <asm/irq.h>
+
+#include <pcmcia/ss.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/cisreg.h>
+#include <pcmcia/ds.h>
+
+#include "cs_internal.h"
+
+
+/* Access speed for IO windows */
+static int io_speed;
+module_param(io_speed, int, 0444);
+
+
+int pcmcia_validate_mem(struct pcmcia_socket *s)
+{
+ if (s->resource_ops->validate_mem)
+ return s->resource_ops->validate_mem(s);
+ /* if there is no callback, we can assume that everything is OK */
+ return 0;
+}
+
+struct resource *pcmcia_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;
+}
+
+
+/**
+ * release_io_space() - release IO ports allocated with alloc_io_space()
+ * @s: pcmcia socket
+ * @res: resource to release
+ *
+ */
+static void release_io_space(struct pcmcia_socket *s, struct resource *res)
+{
+ resource_size_t num = resource_size(res);
+ int i;
+
+ dev_dbg(&s->dev, "release_io_space for %pR\n", res);
+
+ for (i = 0; i < MAX_IO_WIN; i++) {
+ if (!s->io[i].res)
+ continue;
+ if ((s->io[i].res->start <= res->start) &&
+ (s->io[i].res->end >= res->end)) {
+ s->io[i].InUse -= num;
+ if (res->parent)
+ release_resource(res);
+ res->start = res->end = 0;
+ res->flags = IORESOURCE_IO;
+ /* Free the window if no one else is using it */
+ if (s->io[i].InUse == 0) {
+ release_resource(s->io[i].res);
+ kfree(s->io[i].res);
+ s->io[i].res = NULL;
+ }
+ }
+ }
+}
+
+
+/**
+ * alloc_io_space() - allocate IO ports for use by a PCMCIA device
+ * @s: pcmcia socket
+ * @res: resource to allocate (begin: begin, end: size)
+ * @lines: number of IO lines decoded by the PCMCIA card
+ *
+ * Special stuff for managing IO windows, because they are scarce
+ */
+static int alloc_io_space(struct pcmcia_socket *s, struct resource *res,
+ unsigned int lines)
+{
+ unsigned int align;
+ unsigned int base = res->start;
+ unsigned int num = res->end;
+ int ret;
+
+ res->flags |= IORESOURCE_IO;
+
+ dev_dbg(&s->dev, "alloc_io_space request for %pR, %d lines\n",
+ res, lines);
+
+ align = base ? (lines ? 1<<lines : 0) : 1;
+ if (align && (align < num)) {
+ if (base) {
+ dev_dbg(&s->dev, "odd IO request\n");
+ align = 0;
+ } else
+ while (align && (align < num))
+ align <<= 1;
+ }
+ if (base & ~(align-1)) {
+ dev_dbg(&s->dev, "odd IO request\n");
+ align = 0;
+ }
+
+ ret = s->resource_ops->find_io(s, res->flags, &base, num, align,
+ &res->parent);
+ if (ret) {
+ dev_dbg(&s->dev, "alloc_io_space request failed (%d)\n", ret);
+ return -EINVAL;
+ }
+
+ res->start = base;
+ res->end = res->start + num - 1;
+
+ if (res->parent) {
+ ret = request_resource(res->parent, res);
+ if (ret) {
+ dev_warn(&s->dev,
+ "request_resource %pR failed: %d\n", res, ret);
+ res->parent = NULL;
+ release_io_space(s, res);
+ }
+ }
+ dev_dbg(&s->dev, "alloc_io_space request result %d: %pR\n", ret, res);
+ return ret;
+}
+
+
+/**
+ * pcmcia_access_config() - read or write card configuration registers
+ *
+ * pcmcia_access_config() reads and writes configuration registers in
+ * attribute memory. Memory window 0 is reserved for this and the tuple
+ * reading services. Drivers must use pcmcia_read_config_byte() or
+ * pcmcia_write_config_byte().
+ */
+static int pcmcia_access_config(struct pcmcia_device *p_dev,
+ off_t where, u8 *val,
+ int (*accessf) (struct pcmcia_socket *s,
+ int attr, unsigned int addr,
+ unsigned int len, void *ptr))
+{
+ struct pcmcia_socket *s;
+ config_t *c;
+ int addr;
+ int ret = 0;
+
+ s = p_dev->socket;
+
+ mutex_lock(&s->ops_mutex);
+ c = p_dev->function_config;
+
+ if (!(c->state & CONFIG_LOCKED)) {
+ dev_dbg(&p_dev->dev, "Configuration isn't locked\n");
+ mutex_unlock(&s->ops_mutex);
+ return -EACCES;
+ }
+
+ addr = (p_dev->config_base + where) >> 1;
+
+ ret = accessf(s, 1, addr, 1, val);
+
+ mutex_unlock(&s->ops_mutex);
+
+ return ret;
+}
+
+
+/**
+ * pcmcia_read_config_byte() - read a byte from a card configuration register
+ *
+ * pcmcia_read_config_byte() reads a byte from a configuration register in
+ * attribute memory.
+ */
+int pcmcia_read_config_byte(struct pcmcia_device *p_dev, off_t where, u8 *val)
+{
+ return pcmcia_access_config(p_dev, where, val, pcmcia_read_cis_mem);
+}
+EXPORT_SYMBOL(pcmcia_read_config_byte);
+
+
+/**
+ * pcmcia_write_config_byte() - write a byte to a card configuration register
+ *
+ * pcmcia_write_config_byte() writes a byte to a configuration register in
+ * attribute memory.
+ */
+int pcmcia_write_config_byte(struct pcmcia_device *p_dev, off_t where, u8 val)
+{
+ return pcmcia_access_config(p_dev, where, &val, pcmcia_write_cis_mem);
+}
+EXPORT_SYMBOL(pcmcia_write_config_byte);
+
+
+/**
+ * pcmcia_map_mem_page() - modify iomem window to point to a different offset
+ * @p_dev: pcmcia device
+ * @res: iomem resource already enabled by pcmcia_request_window()
+ * @offset: card_offset to map
+ *
+ * pcmcia_map_mem_page() modifies what can be read and written by accessing
+ * an iomem range previously enabled by pcmcia_request_window(), by setting
+ * the card_offset value to @offset.
+ */
+int pcmcia_map_mem_page(struct pcmcia_device *p_dev, struct resource *res,
+ unsigned int offset)
+{
+ struct pcmcia_socket *s = p_dev->socket;
+ unsigned int w;
+ int ret;
+
+ w = ((res->flags & IORESOURCE_BITS & WIN_FLAGS_REQ) >> 2) - 1;
+ if (w >= MAX_WIN)
+ return -EINVAL;
+
+ mutex_lock(&s->ops_mutex);
+ s->win[w].card_start = offset;
+ ret = s->ops->set_mem_map(s, &s->win[w]);
+ if (ret)
+ dev_warn(&p_dev->dev, "failed to set_mem_map\n");
+ mutex_unlock(&s->ops_mutex);
+ return ret;
+}
+EXPORT_SYMBOL(pcmcia_map_mem_page);
+
+
+/**
+ * pcmcia_fixup_iowidth() - reduce io width to 8bit
+ * @p_dev: pcmcia device
+ *
+ * pcmcia_fixup_iowidth() allows a PCMCIA device driver to reduce the
+ * IO width to 8bit after having called pcmcia_enable_device()
+ * previously.
+ */
+int pcmcia_fixup_iowidth(struct pcmcia_device *p_dev)
+{
+ struct pcmcia_socket *s = p_dev->socket;
+ pccard_io_map io_off = { 0, 0, 0, 0, 1 };
+ pccard_io_map io_on;
+ int i, ret = 0;
+
+ mutex_lock(&s->ops_mutex);
+
+ dev_dbg(&p_dev->dev, "fixup iowidth to 8bit\n");
+
+ if (!(s->state & SOCKET_PRESENT) ||
+ !(p_dev->function_config->state & CONFIG_LOCKED)) {
+ dev_dbg(&p_dev->dev, "No card? Config not locked?\n");
+ ret = -EACCES;
+ goto unlock;
+ }
+
+ io_on.speed = io_speed;
+ for (i = 0; i < MAX_IO_WIN; i++) {
+ if (!s->io[i].res)
+ continue;
+ io_off.map = i;
+ io_on.map = i;
+
+ io_on.flags = MAP_ACTIVE | IO_DATA_PATH_WIDTH_8;
+ io_on.start = s->io[i].res->start;
+ io_on.stop = s->io[i].res->end;
+
+ s->ops->set_io_map(s, &io_off);
+ msleep(40);
+ s->ops->set_io_map(s, &io_on);
+ }
+unlock:
+ mutex_unlock(&s->ops_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(pcmcia_fixup_iowidth);
+
+
+/**
+ * pcmcia_fixup_vpp() - set Vpp to a new voltage level
+ * @p_dev: pcmcia device
+ * @new_vpp: new Vpp voltage
+ *
+ * pcmcia_fixup_vpp() allows a PCMCIA device driver to set Vpp to
+ * a new voltage level between calls to pcmcia_enable_device()
+ * and pcmcia_disable_device().
+ */
+int pcmcia_fixup_vpp(struct pcmcia_device *p_dev, unsigned char new_vpp)
+{
+ struct pcmcia_socket *s = p_dev->socket;
+ int ret = 0;
+
+ mutex_lock(&s->ops_mutex);
+
+ dev_dbg(&p_dev->dev, "fixup Vpp to %d\n", new_vpp);
+
+ if (!(s->state & SOCKET_PRESENT) ||
+ !(p_dev->function_config->state & CONFIG_LOCKED)) {
+ dev_dbg(&p_dev->dev, "No card? Config not locked?\n");
+ ret = -EACCES;
+ goto unlock;
+ }
+
+ s->socket.Vpp = new_vpp;
+ if (s->ops->set_socket(s, &s->socket)) {
+ dev_warn(&p_dev->dev, "Unable to set VPP\n");
+ ret = -EIO;
+ goto unlock;
+ }
+ p_dev->vpp = new_vpp;
+
+unlock:
+ mutex_unlock(&s->ops_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL(pcmcia_fixup_vpp);
+
+
+/**
+ * pcmcia_release_configuration() - physically disable a PCMCIA device
+ * @p_dev: pcmcia device
+ *
+ * pcmcia_release_configuration() is the 1:1 counterpart to
+ * pcmcia_enable_device(): If a PCMCIA device is no longer used by any
+ * driver, the Vpp voltage is set to 0, IRQs will no longer be generated,
+ * and I/O ranges will be disabled. As pcmcia_release_io() and
+ * pcmcia_release_window() still need to be called, device drivers are
+ * expected to call pcmcia_disable_device() instead.
+ */
+int pcmcia_release_configuration(struct pcmcia_device *p_dev)
+{
+ pccard_io_map io = { 0, 0, 0, 0, 1 };
+ struct pcmcia_socket *s = p_dev->socket;
+ config_t *c;
+ int i;
+
+ mutex_lock(&s->ops_mutex);
+ c = p_dev->function_config;
+ if (p_dev->_locked) {
+ p_dev->_locked = 0;
+ 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_LOCKED) {
+ c->state &= ~CONFIG_LOCKED;
+ if (c->state & CONFIG_IO_REQ)
+ for (i = 0; i < MAX_IO_WIN; i++) {
+ if (!s->io[i].res)
+ continue;
+ s->io[i].Config--;
+ if (s->io[i].Config != 0)
+ continue;
+ io.map = i;
+ s->ops->set_io_map(s, &io);
+ }
+ }
+ mutex_unlock(&s->ops_mutex);
+
+ return 0;
+}
+
+
+/**
+ * pcmcia_release_io() - release I/O allocated by a PCMCIA device
+ * @p_dev: pcmcia device
+ *
+ * pcmcia_release_io() releases the I/O ranges allocated by a PCMCIA
+ * device. 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.
+ */
+static int pcmcia_release_io(struct pcmcia_device *p_dev)
+{
+ struct pcmcia_socket *s = p_dev->socket;
+ int ret = -EINVAL;
+ config_t *c;
+
+ mutex_lock(&s->ops_mutex);
+ if (!p_dev->_io)
+ goto out;
+
+ c = p_dev->function_config;
+
+ release_io_space(s, &c->io[0]);
+
+ if (c->io[1].end)
+ release_io_space(s, &c->io[1]);
+
+ p_dev->_io = 0;
+ c->state &= ~CONFIG_IO_REQ;
+
+out:
+ mutex_unlock(&s->ops_mutex);
+
+ return ret;
+} /* pcmcia_release_io */
+
+
+/**
+ * pcmcia_release_window() - release reserved iomem for PCMCIA devices
+ * @p_dev: pcmcia device
+ * @res: iomem resource to release
+ *
+ * pcmcia_release_window() releases &struct resource *res which was
+ * previously reserved by calling pcmcia_request_window().
+ */
+int pcmcia_release_window(struct pcmcia_device *p_dev, struct resource *res)
+{
+ struct pcmcia_socket *s = p_dev->socket;
+ pccard_mem_map *win;
+ unsigned int w;
+
+ dev_dbg(&p_dev->dev, "releasing window %pR\n", res);
+
+ w = ((res->flags & IORESOURCE_BITS & WIN_FLAGS_REQ) >> 2) - 1;
+ if (w >= MAX_WIN)
+ return -EINVAL;
+
+ mutex_lock(&s->ops_mutex);
+ win = &s->win[w];
+
+ if (!(p_dev->_win & CLIENT_WIN_REQ(w))) {
+ dev_dbg(&p_dev->dev, "not releasing unknown window\n");
+ mutex_unlock(&s->ops_mutex);
+ return -EINVAL;
+ }
+
+ /* Shut down memory window */
+ win->flags &= ~MAP_ACTIVE;
+ s->ops->set_mem_map(s, win);
+ s->state &= ~SOCKET_WIN_REQ(w);
+
+ /* Release system memory */
+ if (win->res) {
+ release_resource(res);
+ release_resource(win->res);
+ kfree(win->res);
+ win->res = NULL;
+ }
+ res->start = res->end = 0;
+ res->flags = IORESOURCE_MEM;
+ p_dev->_win &= ~CLIENT_WIN_REQ(w);
+ mutex_unlock(&s->ops_mutex);
+
+ return 0;
+} /* pcmcia_release_window */
+EXPORT_SYMBOL(pcmcia_release_window);
+
+
+/**
+ * pcmcia_enable_device() - set up and activate a PCMCIA device
+ * @p_dev: the associated PCMCIA device
+ *
+ * pcmcia_enable_device() physically enables a PCMCIA device. It parses
+ * the flags passed to in @flags and stored in @p_dev->flags and sets up
+ * the Vpp voltage, enables the speaker line, I/O ports and store proper
+ * values to configuration registers.
+ */
+int pcmcia_enable_device(struct pcmcia_device *p_dev)
+{
+ int i;
+ unsigned int base;
+ struct pcmcia_socket *s = p_dev->socket;
+ config_t *c;
+ pccard_io_map iomap;
+ unsigned char status = 0;
+ unsigned char ext_status = 0;
+ unsigned char option = 0;
+ unsigned int flags = p_dev->config_flags;
+
+ if (!(s->state & SOCKET_PRESENT))
+ return -ENODEV;
+
+ mutex_lock(&s->ops_mutex);
+ c = p_dev->function_config;
+ if (c->state & CONFIG_LOCKED) {
+ mutex_unlock(&s->ops_mutex);
+ dev_dbg(&p_dev->dev, "Configuration is locked\n");
+ return -EACCES;
+ }
+
+ /* Do power control. We don't allow changes in Vcc. */
+ s->socket.Vpp = p_dev->vpp;
+ if (s->ops->set_socket(s, &s->socket)) {
+ mutex_unlock(&s->ops_mutex);
+ dev_warn(&p_dev->dev, "Unable to set socket state\n");
+ return -EINVAL;
+ }
+
+ /* Pick memory or I/O card, DMA mode, interrupt */
+ if (p_dev->_io || flags & CONF_ENABLE_IRQ)
+ flags |= CONF_ENABLE_IOCARD;
+ if (flags & CONF_ENABLE_IOCARD)
+ s->socket.flags |= SS_IOCARD;
+ if (flags & CONF_ENABLE_ZVCARD)
+ s->socket.flags |= SS_ZVCARD | SS_IOCARD;
+ if (flags & CONF_ENABLE_SPKR) {
+ s->socket.flags |= SS_SPKR_ENA;
+ status = CCSR_AUDIO_ENA;
+ if (!(p_dev->config_regs & PRESENT_STATUS))
+ dev_warn(&p_dev->dev, "speaker requested, but "
+ "PRESENT_STATUS not set!\n");
+ }
+ if (flags & CONF_ENABLE_IRQ)
+ s->socket.io_irq = s->pcmcia_irq;
+ else
+ s->socket.io_irq = 0;
+ if (flags & CONF_ENABLE_ESR) {
+ p_dev->config_regs |= PRESENT_EXT_STATUS;
+ ext_status = ESR_REQ_ATTN_ENA;
+ }
+ s->ops->set_socket(s, &s->socket);
+ s->lock_count++;
+
+ dev_dbg(&p_dev->dev,
+ "enable_device: V %d, flags %x, base %x, regs %x, idx %x\n",
+ p_dev->vpp, flags, p_dev->config_base, p_dev->config_regs,
+ p_dev->config_index);
+
+ /* Set up CIS configuration registers */
+ base = p_dev->config_base;
+ if (p_dev->config_regs & PRESENT_COPY) {
+ u16 tmp = 0;
+ dev_dbg(&p_dev->dev, "clearing CISREG_SCR\n");
+ pcmcia_write_cis_mem(s, 1, (base + CISREG_SCR)>>1, 1, &tmp);
+ }
+ if (p_dev->config_regs & PRESENT_PIN_REPLACE) {
+ u16 tmp = 0;
+ dev_dbg(&p_dev->dev, "clearing CISREG_PRR\n");
+ pcmcia_write_cis_mem(s, 1, (base + CISREG_PRR)>>1, 1, &tmp);
+ }
+ if (p_dev->config_regs & PRESENT_OPTION) {
+ if (s->functions == 1) {
+ option = p_dev->config_index & COR_CONFIG_MASK;
+ } else {
+ option = p_dev->config_index & COR_MFC_CONFIG_MASK;
+ option |= COR_FUNC_ENA|COR_IREQ_ENA;
+ if (p_dev->config_regs & PRESENT_IOBASE_0)
+ option |= COR_ADDR_DECODE;
+ }
+ if ((flags & CONF_ENABLE_IRQ) &&
+ !(flags & CONF_ENABLE_PULSE_IRQ))
+ option |= COR_LEVEL_REQ;
+ pcmcia_write_cis_mem(s, 1, (base + CISREG_COR)>>1, 1, &option);
+ msleep(40);
+ }
+ if (p_dev->config_regs & PRESENT_STATUS)
+ pcmcia_write_cis_mem(s, 1, (base + CISREG_CCSR)>>1, 1, &status);
+
+ if (p_dev->config_regs & PRESENT_EXT_STATUS)
+ pcmcia_write_cis_mem(s, 1, (base + CISREG_ESR)>>1, 1,
+ &ext_status);
+
+ if (p_dev->config_regs & PRESENT_IOBASE_0) {
+ u8 b = c->io[0].start & 0xff;
+ pcmcia_write_cis_mem(s, 1, (base + CISREG_IOBASE_0)>>1, 1, &b);
+ b = (c->io[0].start >> 8) & 0xff;
+ pcmcia_write_cis_mem(s, 1, (base + CISREG_IOBASE_1)>>1, 1, &b);
+ }
+ if (p_dev->config_regs & PRESENT_IOSIZE) {
+ u8 b = resource_size(&c->io[0]) + resource_size(&c->io[1]) - 1;
+ pcmcia_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].res) {
+ iomap.map = i;
+ iomap.flags = MAP_ACTIVE;
+ switch (s->io[i].res->flags & 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].res->start;
+ iomap.stop = s->io[i].res->end;
+ s->ops->set_io_map(s, &iomap);
+ s->io[i].Config++;
+ }
+ }
+
+ c->state |= CONFIG_LOCKED;
+ p_dev->_locked = 1;
+ mutex_unlock(&s->ops_mutex);
+ return 0;
+} /* pcmcia_enable_device */
+EXPORT_SYMBOL(pcmcia_enable_device);
+
+
+/**
+ * pcmcia_request_io() - attempt to reserve port ranges for PCMCIA devices
+ * @p_dev: the associated PCMCIA device
+ *
+ * pcmcia_request_io() attempts to reserve the IO port ranges specified in
+ * &struct pcmcia_device @p_dev->resource[0] and @p_dev->resource[1]. The
+ * "start" value is the requested start of the IO port resource; "end"
+ * reflects the number of ports requested. The number of IO lines requested
+ * is specified in &struct pcmcia_device @p_dev->io_lines.
+ */
+int pcmcia_request_io(struct pcmcia_device *p_dev)
+{
+ struct pcmcia_socket *s = p_dev->socket;
+ config_t *c = p_dev->function_config;
+ int ret = -EINVAL;
+
+ mutex_lock(&s->ops_mutex);
+ dev_dbg(&p_dev->dev, "pcmcia_request_io: %pR , %pR",
+ &c->io[0], &c->io[1]);
+
+ if (!(s->state & SOCKET_PRESENT)) {
+ dev_dbg(&p_dev->dev, "pcmcia_request_io: No card present\n");
+ goto out;
+ }
+
+ if (c->state & CONFIG_LOCKED) {
+ dev_dbg(&p_dev->dev, "Configuration is locked\n");
+ goto out;
+ }
+ if (c->state & CONFIG_IO_REQ) {
+ dev_dbg(&p_dev->dev, "IO already configured\n");
+ goto out;
+ }
+
+ ret = alloc_io_space(s, &c->io[0], p_dev->io_lines);
+ if (ret)
+ goto out;
+
+ if (c->io[1].end) {
+ ret = alloc_io_space(s, &c->io[1], p_dev->io_lines);
+ if (ret) {
+ struct resource tmp = c->io[0];
+ /* release the previously allocated resource */
+ release_io_space(s, &c->io[0]);
+ /* but preserve the settings, for they worked... */
+ c->io[0].end = resource_size(&tmp);
+ c->io[0].start = tmp.start;
+ c->io[0].flags = tmp.flags;
+ goto out;
+ }
+ } else
+ c->io[1].start = 0;
+
+ c->state |= CONFIG_IO_REQ;
+ p_dev->_io = 1;
+
+ dev_dbg(&p_dev->dev, "pcmcia_request_io succeeded: %pR , %pR",
+ &c->io[0], &c->io[1]);
+out:
+ mutex_unlock(&s->ops_mutex);
+
+ return ret;
+} /* pcmcia_request_io */
+EXPORT_SYMBOL(pcmcia_request_io);
+
+
+/**
+ * pcmcia_request_irq() - attempt to request a IRQ for a PCMCIA device
+ * @p_dev: the associated PCMCIA device
+ * @handler: IRQ handler to register
+ *
+ * pcmcia_request_irq() is a wrapper around request_irq() which allows
+ * the PCMCIA core to clean up the registration in pcmcia_disable_device().
+ * Drivers are free to use request_irq() directly, but then they need to
+ * call free_irq() themselfves, too. Also, only %IRQF_SHARED capable IRQ
+ * handlers are allowed.
+ */
+int __must_check pcmcia_request_irq(struct pcmcia_device *p_dev,
+ irq_handler_t handler)
+{
+ int ret;
+
+ if (!p_dev->irq)
+ return -EINVAL;
+
+ ret = request_irq(p_dev->irq, handler, IRQF_SHARED,
+ p_dev->devname, p_dev->priv);
+ if (!ret)
+ p_dev->_irq = 1;
+
+ return ret;
+}
+EXPORT_SYMBOL(pcmcia_request_irq);
+
+
+#ifdef CONFIG_PCMCIA_PROBE
+
+/* mask of IRQs already reserved by other cards, we should avoid using them */
+static u8 pcmcia_used_irq[32];
+
+static irqreturn_t test_action(int cpl, void *dev_id)
+{
+ return IRQ_NONE;
+}
+
+/**
+ * pcmcia_setup_isa_irq() - determine whether an ISA IRQ can be used
+ * @p_dev - the associated PCMCIA device
+ *
+ * locking note: must be called with ops_mutex locked.
+ */
+static int pcmcia_setup_isa_irq(struct pcmcia_device *p_dev, int type)
+{
+ struct pcmcia_socket *s = p_dev->socket;
+ unsigned int try, irq;
+ u32 mask = s->irq_mask;
+ int ret = -ENODEV;
+
+ for (try = 0; try < 64; try++) {
+ irq = try % 32;
+
+ if (irq > NR_IRQS)
+ continue;
+
+ /* marked as available by driver, not blocked by userspace? */
+ if (!((mask >> irq) & 1))
+ continue;
+
+ /* avoid an IRQ which is already used by another PCMCIA card */
+ if ((try < 32) && pcmcia_used_irq[irq])
+ continue;
+
+ /* register the correct driver, if possible, to 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, test_action, type, p_dev->devname,
+ p_dev);
+ if (!ret) {
+ free_irq(irq, p_dev);
+ p_dev->irq = s->pcmcia_irq = irq;
+ pcmcia_used_irq[irq]++;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+void pcmcia_cleanup_irq(struct pcmcia_socket *s)
+{
+ pcmcia_used_irq[s->pcmcia_irq]--;
+ s->pcmcia_irq = 0;
+}
+
+#else /* CONFIG_PCMCIA_PROBE */
+
+static int pcmcia_setup_isa_irq(struct pcmcia_device *p_dev, int type)
+{
+ return -EINVAL;
+}
+
+void pcmcia_cleanup_irq(struct pcmcia_socket *s)
+{
+ s->pcmcia_irq = 0;
+ return;
+}
+
+#endif /* CONFIG_PCMCIA_PROBE */
+
+
+/**
+ * pcmcia_setup_irq() - determine IRQ to be used for device
+ * @p_dev - the associated PCMCIA device
+ *
+ * locking note: must be called with ops_mutex locked.
+ */
+int pcmcia_setup_irq(struct pcmcia_device *p_dev)
+{
+ struct pcmcia_socket *s = p_dev->socket;
+
+ if (p_dev->irq)
+ return 0;
+
+ /* already assigned? */
+ if (s->pcmcia_irq) {
+ p_dev->irq = s->pcmcia_irq;
+ return 0;
+ }
+
+ /* prefer an exclusive ISA irq */
+ if (!pcmcia_setup_isa_irq(p_dev, 0))
+ return 0;
+
+ /* but accept a shared ISA irq */
+ if (!pcmcia_setup_isa_irq(p_dev, IRQF_SHARED))
+ return 0;
+
+ /* but use the PCI irq otherwise */
+ if (s->pci_irq) {
+ p_dev->irq = s->pcmcia_irq = s->pci_irq;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+
+/**
+ * pcmcia_request_window() - attempt to reserve iomem for PCMCIA devices
+ * @p_dev: the associated PCMCIA device
+ * @res: &struct resource pointing to p_dev->resource[2..5]
+ * @speed: access speed
+ *
+ * pcmcia_request_window() attepts to reserve an iomem ranges specified in
+ * &struct resource @res pointing to one of the entries in
+ * &struct pcmcia_device @p_dev->resource[2..5]. The "start" value is the
+ * requested start of the IO mem resource; "end" reflects the size
+ * requested.
+ */
+int pcmcia_request_window(struct pcmcia_device *p_dev, struct resource *res,
+ unsigned int speed)
+{
+ struct pcmcia_socket *s = p_dev->socket;
+ pccard_mem_map *win;
+ u_long align;
+ int w;
+
+ dev_dbg(&p_dev->dev, "request_window %pR %d\n", res, speed);
+
+ if (!(s->state & SOCKET_PRESENT)) {
+ dev_dbg(&p_dev->dev, "No card present\n");
+ return -ENODEV;
+ }
+
+ /* Window size defaults to smallest available */
+ if (res->end == 0)
+ res->end = s->map_size;
+ align = (s->features & SS_CAP_MEM_ALIGN) ? res->end : s->map_size;
+ if (res->end & (s->map_size-1)) {
+ dev_dbg(&p_dev->dev, "invalid map size\n");
+ return -EINVAL;
+ }
+ if ((res->start && (s->features & SS_CAP_STATIC_MAP)) ||
+ (res->start & (align-1))) {
+ dev_dbg(&p_dev->dev, "invalid base address\n");
+ return -EINVAL;
+ }
+ if (res->start)
+ align = 0;
+
+ /* Allocate system memory window */
+ mutex_lock(&s->ops_mutex);
+ for (w = 0; w < MAX_WIN; w++)
+ if (!(s->state & SOCKET_WIN_REQ(w)))
+ break;
+ if (w == MAX_WIN) {
+ dev_dbg(&p_dev->dev, "all windows are used already\n");
+ mutex_unlock(&s->ops_mutex);
+ return -EINVAL;
+ }
+
+ win = &s->win[w];
+
+ if (!(s->features & SS_CAP_STATIC_MAP)) {
+ win->res = pcmcia_find_mem_region(res->start, res->end, align,
+ 0, s);
+ if (!win->res) {
+ dev_dbg(&p_dev->dev, "allocating mem region failed\n");
+ mutex_unlock(&s->ops_mutex);
+ return -EINVAL;
+ }
+ }
+ p_dev->_win |= CLIENT_WIN_REQ(w);
+
+ /* Configure the socket controller */
+ win->map = w+1;
+ win->flags = res->flags & WIN_FLAGS_MAP;
+ win->speed = speed;
+ win->card_start = 0;
+
+ if (s->ops->set_mem_map(s, win) != 0) {
+ dev_dbg(&p_dev->dev, "failed to set memory mapping\n");
+ mutex_unlock(&s->ops_mutex);
+ return -EIO;
+ }
+ s->state |= SOCKET_WIN_REQ(w);
+
+ /* Return window handle */
+ if (s->features & SS_CAP_STATIC_MAP)
+ res->start = win->static_start;
+ else
+ res->start = win->res->start;
+
+ /* convert to new-style resources */
+ res->end += res->start - 1;
+ res->flags &= ~WIN_FLAGS_REQ;
+ res->flags |= (win->map << 2) | IORESOURCE_MEM;
+ res->parent = win->res;
+ if (win->res)
+ request_resource(&iomem_resource, res);
+
+ dev_dbg(&p_dev->dev, "request_window results in %pR\n", res);
+
+ mutex_unlock(&s->ops_mutex);
+
+ return 0;
+} /* pcmcia_request_window */
+EXPORT_SYMBOL(pcmcia_request_window);
+
+
+/**
+ * pcmcia_disable_device() - disable and clean up a PCMCIA device
+ * @p_dev: the associated PCMCIA device
+ *
+ * pcmcia_disable_device() is the driver-callable counterpart to
+ * pcmcia_enable_device(): If a PCMCIA device is no longer used,
+ * drivers are expected to clean up and disable the device by calling
+ * this function. Any I/O ranges (iomem and ioports) will be released,
+ * the Vpp voltage will be set to 0, and IRQs will no longer be
+ * generated -- at least if there is no other card function (of
+ * multifunction devices) being used.
+ */
+void pcmcia_disable_device(struct pcmcia_device *p_dev)
+{
+ int i;
+
+ dev_dbg(&p_dev->dev, "disabling device\n");
+
+ for (i = 0; i < MAX_WIN; i++) {
+ struct resource *res = p_dev->resource[MAX_IO_WIN + i];
+ if (res->flags & WIN_FLAGS_REQ)
+ pcmcia_release_window(p_dev, res);
+ }
+
+ pcmcia_release_configuration(p_dev);
+ pcmcia_release_io(p_dev);
+ if (p_dev->_irq) {
+ free_irq(p_dev->irq, p_dev->priv);
+ p_dev->_irq = 0;
+ }
+}
+EXPORT_SYMBOL(pcmcia_disable_device);
diff --git a/drivers/pcmcia/pd6729.c b/drivers/pcmcia/pd6729.c
new file mode 100644
index 000000000..f0af9985c
--- /dev/null
+++ b/drivers/pcmcia/pd6729.c
@@ -0,0 +1,777 @@
+/*
+ * 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/module.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/io.h>
+
+#include <pcmcia/ss.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.
+ */
+
+static int irq_mode = 1; /* 0 = ISA interrupt, 1 = PCI interrupt */
+
+module_param(irq_mode, int, 0444);
+MODULE_PARM_DESC(irq_mode,
+ "interrupt delivery mode. 0 = ISA, 1 = PCI. default is 1");
+
+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 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;
+ dev_vdbg(&socket[i].socket.dev,
+ "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(struct timer_list *t)
+{
+ struct pd6729_socket *socket = from_timer(socket, t, poll_timer);
+
+ pd6729_interrupt(0, (void *)socket);
+ 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_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) {
+ dev_dbg(&sock->dev, "Auto power\n");
+ reg |= I365_PWR_AUTO; /* automatic power mngmnt */
+ }
+ if (state->flags & SS_OUTPUT_ENA) {
+ dev_dbg(&sock->dev, "Power Enabled\n");
+ reg |= I365_PWR_OUT; /* enable power */
+ }
+
+ switch (state->Vcc) {
+ case 0:
+ break;
+ case 33:
+ dev_dbg(&sock->dev,
+ "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:
+ dev_dbg(&sock->dev,
+ "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:
+ dev_dbg(&sock->dev,
+ "pd6729_set_socket called with invalid VCC power "
+ "value: %i\n", state->Vcc);
+ return -EINVAL;
+ }
+
+ switch (state->Vpp) {
+ case 0:
+ dev_dbg(&sock->dev, "not setting Vpp on socket %i\n",
+ socket->number);
+ break;
+ case 33:
+ case 50:
+ dev_dbg(&sock->dev, "setting Vpp to Vcc for socket %i\n",
+ socket->number);
+ reg |= I365_VPP1_5V;
+ break;
+ case 120:
+ dev_dbg(&sock->dev, "setting Vpp to 12.0\n");
+ reg |= I365_VPP1_12V;
+ break;
+ default:
+ dev_dbg(&sock->dev, "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) {
+ dev_dbg(&sock->dev, "pd6729_set_io_map with invalid map\n");
+ 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));
+
+ /* dev_dbg(&sock->dev, "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) {
+ dev_warn(&sock->dev, "invalid map requested\n");
+ return -EINVAL;
+ }
+
+ if ((mem->res->start > mem->res->end) || (mem->speed > 1000)) {
+ dev_warn(&sock->dev, "invalid invalid address / speed\n");
+ 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) {
+ /* dev_dbg(&sock->dev, "requesting attribute memory for "
+ "socket %i\n", socket->number);*/
+ i |= I365_MEM_REG;
+ } else {
+ /* dev_dbg(&sock->dev, "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,
+ .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)
+{
+ pr_devel("-> hit on irq %d\n", irq);
+ return IRQ_HANDLED;
+}
+
+static int pd6729_check_irq(int irq)
+{
+ int ret;
+
+ ret = request_irq(irq, pd6729_test, IRQF_PROBE_SHARED, "x",
+ pd6729_test);
+ if (ret)
+ return -1;
+
+ free_irq(irq, pd6729_test);
+ return 0;
+}
+
+static u_int 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;
+ }
+
+ 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))
+ 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!");
+ else
+ printk(" polling status changes.\n");
+
+ return mask;
+}
+
+static int 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 = kcalloc(MAX_SOCKETS, sizeof(struct pd6729_socket),
+ GFP_KERNEL);
+ if (!socket) {
+ dev_warn(&dev->dev, "failed to kzalloc socket.\n");
+ return -ENOMEM;
+ }
+
+ ret = pci_enable_device(dev);
+ if (ret) {
+ dev_warn(&dev->dev, "failed to enable pci_device.\n");
+ goto err_out_free_mem;
+ }
+
+ if (!pci_resource_start(dev, 0)) {
+ dev_warn(&dev->dev, "refusing to load the driver as the "
+ "io_base is NULL.\n");
+ ret = -ENOMEM;
+ goto err_out_disable;
+ }
+
+ dev_info(&dev->dev, "Cirrus PD6729 PCI to PCMCIA Bridge at 0x%llx "
+ "on irq %d\n",
+ (unsigned long long)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)) {
+ dev_dbg(&dev->dev, "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) {
+ dev_warn(&dev->dev, "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) {
+ dev_warn(&dev->dev, "no ISA interrupt is available.\n");
+ ret = -ENODEV;
+ 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_PAGE_REGS | 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.cb_dev = dev;
+ 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.parent = &dev->dev;
+ socket[i].socket.driver_data = &socket[i];
+ }
+
+ pci_set_drvdata(dev, socket);
+ if (irq_mode == 1) {
+ /* Register the interrupt handler */
+ ret = request_irq(dev->irq, pd6729_interrupt, IRQF_SHARED,
+ "pd6729", socket);
+ if (ret) {
+ dev_err(&dev->dev, "Failed to register irq %d\n",
+ dev->irq);
+ goto err_out_free_res;
+ }
+ } else {
+ /* poll Card status change */
+ timer_setup(&socket->poll_timer, pd6729_interrupt_wrapper, 0);
+ mod_timer(&socket->poll_timer, jiffies + HZ);
+ }
+
+ for (i = 0; i < MAX_SOCKETS; i++) {
+ ret = pcmcia_register_socket(&socket[i].socket);
+ if (ret) {
+ dev_warn(&dev->dev, "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 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 const struct pci_device_id pd6729_pci_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_CIRRUS, PCI_DEVICE_ID_CIRRUS_6729) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, pd6729_pci_ids);
+
+static struct pci_driver pd6729_pci_driver = {
+ .name = "pd6729",
+ .id_table = pd6729_pci_ids,
+ .probe = pd6729_pci_probe,
+ .remove = pd6729_pci_remove,
+};
+
+module_pci_driver(pd6729_pci_driver);
diff --git a/drivers/pcmcia/pd6729.h b/drivers/pcmcia/pd6729.h
new file mode 100644
index 000000000..605cc2ccf
--- /dev/null
+++ b/drivers/pcmcia/pd6729.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _INCLUDE_GUARD_PD6729_H_
+#define _INCLUDE_GUARD_PD6729_H_
+
+/* 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_balloon3.c b/drivers/pcmcia/pxa2xx_balloon3.c
new file mode 100644
index 000000000..5fe1da7a5
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_balloon3.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/pxa2xx_balloon3.c
+ *
+ * Balloon3 PCMCIA specific routines.
+ *
+ * Author: Nick Bane
+ * Created: June, 2006
+ * Copyright: Toby Churchill Ltd
+ * Derived from pxa2xx_mainstone.c, by Nico Pitre
+ *
+ * Various modification by Marek Vasut <marek.vasut@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+
+#include <mach/balloon3.h>
+
+#include <asm/mach-types.h>
+
+#include "soc_common.h"
+
+static int balloon3_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ uint16_t ver;
+
+ ver = __raw_readw(BALLOON3_FPGA_VER);
+ if (ver < 0x4f08)
+ pr_warn("The FPGA code, version 0x%04x, is too old. "
+ "PCMCIA/CF support might be broken in this version!",
+ ver);
+
+ skt->socket.pci_irq = BALLOON3_BP_CF_NRDY_IRQ;
+ skt->stat[SOC_STAT_CD].gpio = BALLOON3_GPIO_S0_CD;
+ skt->stat[SOC_STAT_CD].name = "PCMCIA0 CD";
+ skt->stat[SOC_STAT_BVD1].irq = BALLOON3_BP_NSTSCHG_IRQ;
+ skt->stat[SOC_STAT_BVD1].name = "PCMCIA0 STSCHG";
+
+ return 0;
+}
+
+static unsigned long balloon3_pcmcia_status[2] = {
+ BALLOON3_CF_nSTSCHG_BVD1,
+ BALLOON3_CF_nSTSCHG_BVD1
+};
+
+static void balloon3_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ uint16_t status;
+ int flip;
+
+ /* This actually reads the STATUS register */
+ status = __raw_readw(BALLOON3_CF_STATUS_REG);
+ flip = (status ^ balloon3_pcmcia_status[skt->nr])
+ & BALLOON3_CF_nSTSCHG_BVD1;
+ /*
+ * Workaround for STSCHG which can't be deasserted:
+ * We therefore disable/enable corresponding IRQs
+ * as needed to avoid IRQ locks.
+ */
+ if (flip) {
+ balloon3_pcmcia_status[skt->nr] = status;
+ if (status & BALLOON3_CF_nSTSCHG_BVD1)
+ enable_irq(BALLOON3_BP_NSTSCHG_IRQ);
+ else
+ disable_irq(BALLOON3_BP_NSTSCHG_IRQ);
+ }
+
+ state->ready = !!(status & BALLOON3_CF_nIRQ);
+ state->bvd1 = !!(status & BALLOON3_CF_nSTSCHG_BVD1);
+ state->bvd2 = 0; /* not available */
+ state->vs_3v = 1; /* Always true its a CF card */
+ state->vs_Xv = 0; /* not available */
+}
+
+static int balloon3_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ __raw_writew(BALLOON3_CF_RESET, BALLOON3_CF_CONTROL_REG +
+ ((state->flags & SS_RESET) ?
+ BALLOON3_FPGA_SETnCLR : 0));
+ return 0;
+}
+
+static struct pcmcia_low_level balloon3_pcmcia_ops = {
+ .owner = THIS_MODULE,
+ .hw_init = balloon3_pcmcia_hw_init,
+ .socket_state = balloon3_pcmcia_socket_state,
+ .configure_socket = balloon3_pcmcia_configure_socket,
+ .first = 0,
+ .nr = 1,
+};
+
+static struct platform_device *balloon3_pcmcia_device;
+
+static int __init balloon3_pcmcia_init(void)
+{
+ int ret;
+
+ if (!machine_is_balloon3())
+ return -ENODEV;
+
+ balloon3_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+ if (!balloon3_pcmcia_device)
+ return -ENOMEM;
+
+ ret = platform_device_add_data(balloon3_pcmcia_device,
+ &balloon3_pcmcia_ops, sizeof(balloon3_pcmcia_ops));
+
+ if (!ret)
+ ret = platform_device_add(balloon3_pcmcia_device);
+
+ if (ret)
+ platform_device_put(balloon3_pcmcia_device);
+
+ return ret;
+}
+
+static void __exit balloon3_pcmcia_exit(void)
+{
+ platform_device_unregister(balloon3_pcmcia_device);
+}
+
+module_init(balloon3_pcmcia_init);
+module_exit(balloon3_pcmcia_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nick Bane <nick@cecomputing.co.uk>");
+MODULE_ALIAS("platform:pxa2xx-pcmcia");
+MODULE_DESCRIPTION("Balloon3 board CF/PCMCIA driver");
diff --git a/drivers/pcmcia/pxa2xx_base.c b/drivers/pcmcia/pxa2xx_base.c
new file mode 100644
index 000000000..d6d2f75f8
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_base.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*======================================================================
+
+ Device driver for the PCMCIA control functionality of PXA2xx
+ microprocessors.
+
+
+ (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/slab.h>
+#include <linux/init.h>
+#include <linux/cpufreq.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+
+#include <mach/hardware.h>
+#include <mach/smemc.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <mach/pxa2xx-regs.h>
+#include <asm/mach-types.h>
+
+#include <pcmcia/ss.h>
+#include <pcmcia/cistpl.h>
+
+#include "soc_common.h"
+#include "pxa2xx_base.h"
+
+/*
+ * Personal Computer Memory Card International Association (PCMCIA) sockets
+ */
+
+#define PCMCIAPrtSp 0x04000000 /* PCMCIA Partition Space [byte] */
+#define PCMCIASp (4*PCMCIAPrtSp) /* PCMCIA Space [byte] */
+#define PCMCIAIOSp PCMCIAPrtSp /* PCMCIA I/O Space [byte] */
+#define PCMCIAAttrSp PCMCIAPrtSp /* PCMCIA Attribute Space [byte] */
+#define PCMCIAMemSp PCMCIAPrtSp /* PCMCIA Memory Space [byte] */
+
+#define PCMCIA0Sp PCMCIASp /* PCMCIA 0 Space [byte] */
+#define PCMCIA0IOSp PCMCIAIOSp /* PCMCIA 0 I/O Space [byte] */
+#define PCMCIA0AttrSp PCMCIAAttrSp /* PCMCIA 0 Attribute Space [byte] */
+#define PCMCIA0MemSp PCMCIAMemSp /* PCMCIA 0 Memory Space [byte] */
+
+#define PCMCIA1Sp PCMCIASp /* PCMCIA 1 Space [byte] */
+#define PCMCIA1IOSp PCMCIAIOSp /* PCMCIA 1 I/O Space [byte] */
+#define PCMCIA1AttrSp PCMCIAAttrSp /* PCMCIA 1 Attribute Space [byte] */
+#define PCMCIA1MemSp PCMCIAMemSp /* PCMCIA 1 Memory Space [byte] */
+
+#define _PCMCIA(Nb) /* PCMCIA [0..1] */ \
+ (0x20000000 + (Nb) * PCMCIASp)
+#define _PCMCIAIO(Nb) _PCMCIA(Nb) /* PCMCIA I/O [0..1] */
+#define _PCMCIAAttr(Nb) /* PCMCIA Attribute [0..1] */ \
+ (_PCMCIA(Nb) + 2 * PCMCIAPrtSp)
+#define _PCMCIAMem(Nb) /* PCMCIA Memory [0..1] */ \
+ (_PCMCIA(Nb) + 3 * PCMCIAPrtSp)
+
+#define _PCMCIA0 _PCMCIA(0) /* PCMCIA 0 */
+#define _PCMCIA0IO _PCMCIAIO(0) /* PCMCIA 0 I/O */
+#define _PCMCIA0Attr _PCMCIAAttr(0) /* PCMCIA 0 Attribute */
+#define _PCMCIA0Mem _PCMCIAMem(0) /* PCMCIA 0 Memory */
+
+#define _PCMCIA1 _PCMCIA(1) /* PCMCIA 1 */
+#define _PCMCIA1IO _PCMCIAIO(1) /* PCMCIA 1 I/O */
+#define _PCMCIA1Attr _PCMCIAAttr(1) /* PCMCIA 1 Attribute */
+#define _PCMCIA1Mem _PCMCIAMem(1) /* PCMCIA 1 Memory */
+
+
+#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 )
+{
+ uint32_t val;
+
+ val = ((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);
+
+ __raw_writel(val, MCMEM(sock));
+
+ return 0;
+}
+
+static int pxa2xx_pcmcia_set_mcio( int sock, int speed, int clock )
+{
+ uint32_t val;
+
+ val = ((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);
+
+ __raw_writel(val, MCIO(sock));
+
+ return 0;
+}
+
+static int pxa2xx_pcmcia_set_mcatt( int sock, int speed, int clock )
+{
+ uint32_t val;
+
+ val = ((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);
+
+ __raw_writel(val, MCATT(sock));
+
+ 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 long clk = clk_get_rate(skt->clk);
+ return pxa2xx_pcmcia_set_mcxx(skt, clk / 10000);
+}
+
+#ifdef CONFIG_CPU_FREQ
+
+static int
+pxa2xx_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) {
+ 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_timing(skt);
+ }
+ 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_timing(skt);
+ }
+ break;
+ }
+ return 0;
+}
+#endif
+
+void pxa2xx_configure_sockets(struct device *dev, struct pcmcia_low_level *ops)
+{
+ /*
+ * We have at least one socket, so set MECR:CIT
+ * (Card Is There)
+ */
+ uint32_t mecr = MECR_CIT;
+
+ /* Set MECR:NOS (Number Of Sockets) */
+ if ((ops->first + ops->nr) > 1 ||
+ machine_is_viper() || machine_is_arcom_zeus())
+ mecr |= MECR_NOS;
+
+ __raw_writel(mecr, MECR);
+}
+EXPORT_SYMBOL(pxa2xx_configure_sockets);
+
+static const char *skt_names[] = {
+ "PCMCIA socket 0",
+ "PCMCIA socket 1",
+};
+
+#define SKT_DEV_INFO_SIZE(n) \
+ (sizeof(struct skt_dev_info) + (n)*sizeof(struct soc_pcmcia_socket))
+
+int pxa2xx_drv_pcmcia_add_one(struct soc_pcmcia_socket *skt)
+{
+ 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;
+
+ 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;
+
+ 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;
+
+ 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;
+
+ return soc_pcmcia_add_one(skt);
+}
+EXPORT_SYMBOL(pxa2xx_drv_pcmcia_add_one);
+
+void pxa2xx_drv_pcmcia_ops(struct pcmcia_low_level *ops)
+{
+ /* 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
+}
+EXPORT_SYMBOL(pxa2xx_drv_pcmcia_ops);
+
+static int pxa2xx_drv_pcmcia_probe(struct platform_device *dev)
+{
+ int i, ret = 0;
+ struct pcmcia_low_level *ops;
+ struct skt_dev_info *sinfo;
+ struct soc_pcmcia_socket *skt;
+ struct clk *clk;
+
+ ops = (struct pcmcia_low_level *)dev->dev.platform_data;
+ if (!ops) {
+ ret = -ENODEV;
+ goto err0;
+ }
+
+ if (cpu_is_pxa320() && ops->nr > 1) {
+ dev_err(&dev->dev, "pxa320 supports only one pcmcia slot");
+ ret = -EINVAL;
+ goto err0;
+ }
+
+ clk = devm_clk_get(&dev->dev, NULL);
+ if (IS_ERR(clk))
+ return -ENODEV;
+
+ pxa2xx_drv_pcmcia_ops(ops);
+
+ sinfo = devm_kzalloc(&dev->dev, SKT_DEV_INFO_SIZE(ops->nr),
+ GFP_KERNEL);
+ if (!sinfo)
+ return -ENOMEM;
+
+ sinfo->nskt = ops->nr;
+
+ /* Initialize processor specific parameters */
+ for (i = 0; i < ops->nr; i++) {
+ skt = &sinfo->skt[i];
+
+ skt->nr = ops->first + i;
+ skt->clk = clk;
+ soc_pcmcia_init_one(skt, ops, &dev->dev);
+
+ ret = pxa2xx_drv_pcmcia_add_one(skt);
+ if (ret)
+ goto err1;
+ }
+
+ pxa2xx_configure_sockets(&dev->dev, ops);
+ dev_set_drvdata(&dev->dev, sinfo);
+
+ return 0;
+
+err1:
+ while (--i >= 0)
+ soc_pcmcia_remove_one(&sinfo->skt[i]);
+
+err0:
+ return ret;
+}
+
+static int pxa2xx_drv_pcmcia_remove(struct platform_device *dev)
+{
+ struct skt_dev_info *sinfo = platform_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < sinfo->nskt; i++)
+ soc_pcmcia_remove_one(&sinfo->skt[i]);
+
+ return 0;
+}
+
+static int pxa2xx_drv_pcmcia_resume(struct device *dev)
+{
+ struct pcmcia_low_level *ops = (struct pcmcia_low_level *)dev->platform_data;
+
+ pxa2xx_configure_sockets(dev, ops);
+ return 0;
+}
+
+static const struct dev_pm_ops pxa2xx_drv_pcmcia_pm_ops = {
+ .resume = pxa2xx_drv_pcmcia_resume,
+};
+
+static struct platform_driver pxa2xx_pcmcia_driver = {
+ .probe = pxa2xx_drv_pcmcia_probe,
+ .remove = pxa2xx_drv_pcmcia_remove,
+ .driver = {
+ .name = "pxa2xx-pcmcia",
+ .pm = &pxa2xx_drv_pcmcia_pm_ops,
+ },
+};
+
+static int __init pxa2xx_pcmcia_init(void)
+{
+ return platform_driver_register(&pxa2xx_pcmcia_driver);
+}
+
+static void __exit pxa2xx_pcmcia_exit(void)
+{
+ platform_driver_unregister(&pxa2xx_pcmcia_driver);
+}
+
+fs_initcall(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");
+MODULE_ALIAS("platform:pxa2xx-pcmcia");
diff --git a/drivers/pcmcia/pxa2xx_base.h b/drivers/pcmcia/pxa2xx_base.h
new file mode 100644
index 000000000..e58c7a415
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_base.h
@@ -0,0 +1,4 @@
+int pxa2xx_drv_pcmcia_add_one(struct soc_pcmcia_socket *skt);
+void pxa2xx_drv_pcmcia_ops(struct pcmcia_low_level *ops);
+void pxa2xx_configure_sockets(struct device *dev, struct pcmcia_low_level *ops);
+
diff --git a/drivers/pcmcia/pxa2xx_cm_x255.c b/drivers/pcmcia/pxa2xx_cm_x255.c
new file mode 100644
index 000000000..c0b6b846f
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_cm_x255.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/pxa/pxa_cm_x255.c
+ *
+ * Compulab Ltd., 2003, 2007, 2008
+ * Mike Rapoport <mike@compulab.co.il>
+ */
+
+#include <linux/platform_device.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/export.h>
+
+#include "soc_common.h"
+
+#define GPIO_PCMCIA_SKTSEL (54)
+#define GPIO_PCMCIA_S0_CD_VALID (16)
+#define GPIO_PCMCIA_S1_CD_VALID (17)
+#define GPIO_PCMCIA_S0_RDYINT (6)
+#define GPIO_PCMCIA_S1_RDYINT (8)
+#define GPIO_PCMCIA_RESET (9)
+
+static int cmx255_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ int ret = gpio_request(GPIO_PCMCIA_RESET, "PCCard reset");
+ if (ret)
+ return ret;
+ gpio_direction_output(GPIO_PCMCIA_RESET, 0);
+
+ if (skt->nr == 0) {
+ skt->stat[SOC_STAT_CD].gpio = GPIO_PCMCIA_S0_CD_VALID;
+ skt->stat[SOC_STAT_CD].name = "PCMCIA0 CD";
+ skt->stat[SOC_STAT_RDY].gpio = GPIO_PCMCIA_S0_RDYINT;
+ skt->stat[SOC_STAT_RDY].name = "PCMCIA0 RDY";
+ } else {
+ skt->stat[SOC_STAT_CD].gpio = GPIO_PCMCIA_S1_CD_VALID;
+ skt->stat[SOC_STAT_CD].name = "PCMCIA1 CD";
+ skt->stat[SOC_STAT_RDY].gpio = GPIO_PCMCIA_S1_RDYINT;
+ skt->stat[SOC_STAT_RDY].name = "PCMCIA1 RDY";
+ }
+
+ return 0;
+}
+
+static void cmx255_pcmcia_shutdown(struct soc_pcmcia_socket *skt)
+{
+ gpio_free(GPIO_PCMCIA_RESET);
+}
+
+
+static void cmx255_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ state->vs_3v = 0;
+ state->vs_Xv = 0;
+}
+
+
+static int cmx255_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ switch (skt->nr) {
+ case 0:
+ if (state->flags & SS_RESET) {
+ gpio_set_value(GPIO_PCMCIA_SKTSEL, 0);
+ udelay(1);
+ gpio_set_value(GPIO_PCMCIA_RESET, 1);
+ udelay(10);
+ gpio_set_value(GPIO_PCMCIA_RESET, 0);
+ }
+ break;
+ case 1:
+ if (state->flags & SS_RESET) {
+ gpio_set_value(GPIO_PCMCIA_SKTSEL, 1);
+ udelay(1);
+ gpio_set_value(GPIO_PCMCIA_RESET, 1);
+ udelay(10);
+ gpio_set_value(GPIO_PCMCIA_RESET, 0);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static struct pcmcia_low_level cmx255_pcmcia_ops __initdata = {
+ .owner = THIS_MODULE,
+ .hw_init = cmx255_pcmcia_hw_init,
+ .hw_shutdown = cmx255_pcmcia_shutdown,
+ .socket_state = cmx255_pcmcia_socket_state,
+ .configure_socket = cmx255_pcmcia_configure_socket,
+ .nr = 1,
+};
+
+static struct platform_device *cmx255_pcmcia_device;
+
+int __init cmx255_pcmcia_init(void)
+{
+ int ret;
+
+ cmx255_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+
+ if (!cmx255_pcmcia_device)
+ return -ENOMEM;
+
+ ret = platform_device_add_data(cmx255_pcmcia_device, &cmx255_pcmcia_ops,
+ sizeof(cmx255_pcmcia_ops));
+
+ if (ret == 0) {
+ printk(KERN_INFO "Registering cm-x255 PCMCIA interface.\n");
+ ret = platform_device_add(cmx255_pcmcia_device);
+ }
+
+ if (ret)
+ platform_device_put(cmx255_pcmcia_device);
+
+ return ret;
+}
+
+void __exit cmx255_pcmcia_exit(void)
+{
+ platform_device_unregister(cmx255_pcmcia_device);
+}
diff --git a/drivers/pcmcia/pxa2xx_cm_x270.c b/drivers/pcmcia/pxa2xx_cm_x270.c
new file mode 100644
index 000000000..36e35da5f
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_cm_x270.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/pxa/pxa_cm_x270.c
+ *
+ * Compulab Ltd., 2003, 2007, 2008
+ * Mike Rapoport <mike@compulab.co.il>
+ */
+
+#include <linux/platform_device.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/export.h>
+
+#include "soc_common.h"
+
+#define GPIO_PCMCIA_S0_CD_VALID (84)
+#define GPIO_PCMCIA_S0_RDYINT (82)
+#define GPIO_PCMCIA_RESET (53)
+
+static int cmx270_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ int ret = gpio_request(GPIO_PCMCIA_RESET, "PCCard reset");
+ if (ret)
+ return ret;
+ gpio_direction_output(GPIO_PCMCIA_RESET, 0);
+
+ skt->stat[SOC_STAT_CD].gpio = GPIO_PCMCIA_S0_CD_VALID;
+ skt->stat[SOC_STAT_CD].name = "PCMCIA0 CD";
+ skt->stat[SOC_STAT_RDY].gpio = GPIO_PCMCIA_S0_RDYINT;
+ skt->stat[SOC_STAT_RDY].name = "PCMCIA0 RDY";
+
+ return ret;
+}
+
+static void cmx270_pcmcia_shutdown(struct soc_pcmcia_socket *skt)
+{
+ gpio_free(GPIO_PCMCIA_RESET);
+}
+
+
+static void cmx270_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ state->vs_3v = 0;
+ state->vs_Xv = 0;
+}
+
+
+static int cmx270_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ switch (skt->nr) {
+ case 0:
+ if (state->flags & SS_RESET) {
+ gpio_set_value(GPIO_PCMCIA_RESET, 1);
+ udelay(10);
+ gpio_set_value(GPIO_PCMCIA_RESET, 0);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static struct pcmcia_low_level cmx270_pcmcia_ops __initdata = {
+ .owner = THIS_MODULE,
+ .hw_init = cmx270_pcmcia_hw_init,
+ .hw_shutdown = cmx270_pcmcia_shutdown,
+ .socket_state = cmx270_pcmcia_socket_state,
+ .configure_socket = cmx270_pcmcia_configure_socket,
+ .nr = 1,
+};
+
+static struct platform_device *cmx270_pcmcia_device;
+
+int __init cmx270_pcmcia_init(void)
+{
+ int ret;
+
+ cmx270_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+
+ if (!cmx270_pcmcia_device)
+ return -ENOMEM;
+
+ ret = platform_device_add_data(cmx270_pcmcia_device, &cmx270_pcmcia_ops,
+ sizeof(cmx270_pcmcia_ops));
+
+ if (ret == 0) {
+ printk(KERN_INFO "Registering cm-x270 PCMCIA interface.\n");
+ ret = platform_device_add(cmx270_pcmcia_device);
+ }
+
+ if (ret)
+ platform_device_put(cmx270_pcmcia_device);
+
+ return ret;
+}
+
+void __exit cmx270_pcmcia_exit(void)
+{
+ platform_device_unregister(cmx270_pcmcia_device);
+}
diff --git a/drivers/pcmcia/pxa2xx_cm_x2xx.c b/drivers/pcmcia/pxa2xx_cm_x2xx.c
new file mode 100644
index 000000000..14eae2381
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_cm_x2xx.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/pxa/pxa_cm_x2xx.c
+ *
+ * Compulab Ltd., 2003, 2007, 2008
+ * Mike Rapoport <mike@compulab.co.il>
+ */
+
+#include <linux/module.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+
+int cmx255_pcmcia_init(void);
+int cmx270_pcmcia_init(void);
+void cmx255_pcmcia_exit(void);
+void cmx270_pcmcia_exit(void);
+
+static int __init cmx2xx_pcmcia_init(void)
+{
+ int ret = -ENODEV;
+
+ if (machine_is_armcore() && cpu_is_pxa25x())
+ ret = cmx255_pcmcia_init();
+ else if (machine_is_armcore() && cpu_is_pxa27x())
+ ret = cmx270_pcmcia_init();
+
+ return ret;
+}
+
+static void __exit cmx2xx_pcmcia_exit(void)
+{
+ if (machine_is_armcore() && cpu_is_pxa25x())
+ cmx255_pcmcia_exit();
+ else if (machine_is_armcore() && cpu_is_pxa27x())
+ cmx270_pcmcia_exit();
+}
+
+module_init(cmx2xx_pcmcia_init);
+module_exit(cmx2xx_pcmcia_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>");
+MODULE_DESCRIPTION("CM-x2xx PCMCIA driver");
diff --git a/drivers/pcmcia/pxa2xx_colibri.c b/drivers/pcmcia/pxa2xx_colibri.c
new file mode 100644
index 000000000..f0f725e99
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_colibri.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/pxa2xx_colibri.c
+ *
+ * Driver for Toradex Colibri PXA270 CF socket
+ *
+ * Copyright (C) 2010 Marek Vasut <marek.vasut@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+
+#include <asm/mach-types.h>
+
+#include "soc_common.h"
+
+#define COLIBRI270_RESET_GPIO 53
+#define COLIBRI270_PPEN_GPIO 107
+#define COLIBRI270_BVD1_GPIO 83
+#define COLIBRI270_BVD2_GPIO 82
+#define COLIBRI270_DETECT_GPIO 84
+#define COLIBRI270_READY_GPIO 1
+
+#define COLIBRI320_RESET_GPIO 77
+#define COLIBRI320_PPEN_GPIO 57
+#define COLIBRI320_BVD1_GPIO 53
+#define COLIBRI320_BVD2_GPIO 79
+#define COLIBRI320_DETECT_GPIO 81
+#define COLIBRI320_READY_GPIO 29
+
+enum {
+ DETECT = 0,
+ READY = 1,
+ BVD1 = 2,
+ BVD2 = 3,
+ PPEN = 4,
+ RESET = 5,
+};
+
+/* Contents of this array are configured on-the-fly in init function */
+static struct gpio colibri_pcmcia_gpios[] = {
+ { 0, GPIOF_IN, "PCMCIA Detect" },
+ { 0, GPIOF_IN, "PCMCIA Ready" },
+ { 0, GPIOF_IN, "PCMCIA BVD1" },
+ { 0, GPIOF_IN, "PCMCIA BVD2" },
+ { 0, GPIOF_INIT_LOW, "PCMCIA PPEN" },
+ { 0, GPIOF_INIT_HIGH,"PCMCIA Reset" },
+};
+
+static int colibri_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ int ret;
+
+ ret = gpio_request_array(colibri_pcmcia_gpios,
+ ARRAY_SIZE(colibri_pcmcia_gpios));
+ if (ret)
+ goto err1;
+
+ skt->socket.pci_irq = gpio_to_irq(colibri_pcmcia_gpios[READY].gpio);
+ skt->stat[SOC_STAT_CD].irq = gpio_to_irq(colibri_pcmcia_gpios[DETECT].gpio);
+ skt->stat[SOC_STAT_CD].name = "PCMCIA CD";
+
+err1:
+ return ret;
+}
+
+static void colibri_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
+{
+ gpio_free_array(colibri_pcmcia_gpios,
+ ARRAY_SIZE(colibri_pcmcia_gpios));
+}
+
+static void colibri_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+
+ state->detect = !!gpio_get_value(colibri_pcmcia_gpios[DETECT].gpio);
+ state->ready = !!gpio_get_value(colibri_pcmcia_gpios[READY].gpio);
+ state->bvd1 = !!gpio_get_value(colibri_pcmcia_gpios[BVD1].gpio);
+ state->bvd2 = !!gpio_get_value(colibri_pcmcia_gpios[BVD2].gpio);
+ state->vs_3v = 1;
+ state->vs_Xv = 0;
+}
+
+static int
+colibri_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ gpio_set_value(colibri_pcmcia_gpios[PPEN].gpio,
+ !(state->Vcc == 33 && state->Vpp < 50));
+ gpio_set_value(colibri_pcmcia_gpios[RESET].gpio,
+ state->flags & SS_RESET);
+ return 0;
+}
+
+static struct pcmcia_low_level colibri_pcmcia_ops = {
+ .owner = THIS_MODULE,
+
+ .first = 0,
+ .nr = 1,
+
+ .hw_init = colibri_pcmcia_hw_init,
+ .hw_shutdown = colibri_pcmcia_hw_shutdown,
+
+ .socket_state = colibri_pcmcia_socket_state,
+ .configure_socket = colibri_pcmcia_configure_socket,
+};
+
+static struct platform_device *colibri_pcmcia_device;
+
+static int __init colibri_pcmcia_init(void)
+{
+ int ret;
+
+ if (!machine_is_colibri() && !machine_is_colibri320())
+ return -ENODEV;
+
+ colibri_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+ if (!colibri_pcmcia_device)
+ return -ENOMEM;
+
+ /* Colibri PXA270 */
+ if (machine_is_colibri()) {
+ colibri_pcmcia_gpios[RESET].gpio = COLIBRI270_RESET_GPIO;
+ colibri_pcmcia_gpios[PPEN].gpio = COLIBRI270_PPEN_GPIO;
+ colibri_pcmcia_gpios[BVD1].gpio = COLIBRI270_BVD1_GPIO;
+ colibri_pcmcia_gpios[BVD2].gpio = COLIBRI270_BVD2_GPIO;
+ colibri_pcmcia_gpios[DETECT].gpio = COLIBRI270_DETECT_GPIO;
+ colibri_pcmcia_gpios[READY].gpio = COLIBRI270_READY_GPIO;
+ /* Colibri PXA320 */
+ } else if (machine_is_colibri320()) {
+ colibri_pcmcia_gpios[RESET].gpio = COLIBRI320_RESET_GPIO;
+ colibri_pcmcia_gpios[PPEN].gpio = COLIBRI320_PPEN_GPIO;
+ colibri_pcmcia_gpios[BVD1].gpio = COLIBRI320_BVD1_GPIO;
+ colibri_pcmcia_gpios[BVD2].gpio = COLIBRI320_BVD2_GPIO;
+ colibri_pcmcia_gpios[DETECT].gpio = COLIBRI320_DETECT_GPIO;
+ colibri_pcmcia_gpios[READY].gpio = COLIBRI320_READY_GPIO;
+ }
+
+ ret = platform_device_add_data(colibri_pcmcia_device,
+ &colibri_pcmcia_ops, sizeof(colibri_pcmcia_ops));
+
+ if (!ret)
+ ret = platform_device_add(colibri_pcmcia_device);
+
+ if (ret)
+ platform_device_put(colibri_pcmcia_device);
+
+ return ret;
+}
+
+static void __exit colibri_pcmcia_exit(void)
+{
+ platform_device_unregister(colibri_pcmcia_device);
+}
+
+module_init(colibri_pcmcia_init);
+module_exit(colibri_pcmcia_exit);
+
+MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
+MODULE_DESCRIPTION("PCMCIA support for Toradex Colibri PXA270/PXA320");
+MODULE_ALIAS("platform:pxa2xx-pcmcia");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pcmcia/pxa2xx_e740.c b/drivers/pcmcia/pxa2xx_e740.c
new file mode 100644
index 000000000..72caa6d05
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_e740.c
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Toshiba e740 PCMCIA specific routines.
+ *
+ * (c) 2004 Ian Molton <spyro@f2s.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <mach/eseries-gpio.h>
+
+#include <asm/irq.h>
+#include <asm/mach-types.h>
+
+#include "soc_common.h"
+
+static int e740_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ if (skt->nr == 0) {
+ skt->stat[SOC_STAT_CD].gpio = GPIO_E740_PCMCIA_CD0;
+ skt->stat[SOC_STAT_CD].name = "CF card detect";
+ skt->stat[SOC_STAT_RDY].gpio = GPIO_E740_PCMCIA_RDY0;
+ skt->stat[SOC_STAT_RDY].name = "CF ready";
+ } else {
+ skt->stat[SOC_STAT_CD].gpio = GPIO_E740_PCMCIA_CD1;
+ skt->stat[SOC_STAT_CD].name = "Wifi switch";
+ skt->stat[SOC_STAT_RDY].gpio = GPIO_E740_PCMCIA_RDY1;
+ skt->stat[SOC_STAT_RDY].name = "Wifi ready";
+ }
+
+ return 0;
+}
+
+static void e740_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ state->vs_3v = 1;
+ state->vs_Xv = 0;
+}
+
+static int e740_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ if (state->flags & SS_RESET) {
+ if (skt->nr == 0)
+ gpio_set_value(GPIO_E740_PCMCIA_RST0, 1);
+ else
+ gpio_set_value(GPIO_E740_PCMCIA_RST1, 1);
+ } else {
+ if (skt->nr == 0)
+ gpio_set_value(GPIO_E740_PCMCIA_RST0, 0);
+ else
+ gpio_set_value(GPIO_E740_PCMCIA_RST1, 0);
+ }
+
+ switch (state->Vcc) {
+ case 0: /* Socket off */
+ if (skt->nr == 0)
+ gpio_set_value(GPIO_E740_PCMCIA_PWR0, 0);
+ else
+ gpio_set_value(GPIO_E740_PCMCIA_PWR1, 1);
+ break;
+ case 50:
+ case 33: /* socket on */
+ if (skt->nr == 0)
+ gpio_set_value(GPIO_E740_PCMCIA_PWR0, 1);
+ else
+ gpio_set_value(GPIO_E740_PCMCIA_PWR1, 0);
+ break;
+ default:
+ printk(KERN_ERR "e740_cs: Unsupported Vcc: %d\n", state->Vcc);
+ }
+
+ return 0;
+}
+
+static struct pcmcia_low_level e740_pcmcia_ops = {
+ .owner = THIS_MODULE,
+ .hw_init = e740_pcmcia_hw_init,
+ .socket_state = e740_pcmcia_socket_state,
+ .configure_socket = e740_pcmcia_configure_socket,
+ .nr = 2,
+};
+
+static struct platform_device *e740_pcmcia_device;
+
+static int __init e740_pcmcia_init(void)
+{
+ int ret;
+
+ if (!machine_is_e740())
+ return -ENODEV;
+
+ e740_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+ if (!e740_pcmcia_device)
+ return -ENOMEM;
+
+ ret = platform_device_add_data(e740_pcmcia_device, &e740_pcmcia_ops,
+ sizeof(e740_pcmcia_ops));
+
+ if (!ret)
+ ret = platform_device_add(e740_pcmcia_device);
+
+ if (ret)
+ platform_device_put(e740_pcmcia_device);
+
+ return ret;
+}
+
+static void __exit e740_pcmcia_exit(void)
+{
+ platform_device_unregister(e740_pcmcia_device);
+}
+
+module_init(e740_pcmcia_init);
+module_exit(e740_pcmcia_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Ian Molton <spyro@f2s.com>");
+MODULE_ALIAS("platform:pxa2xx-pcmcia");
+MODULE_DESCRIPTION("e740 PCMCIA platform support");
diff --git a/drivers/pcmcia/pxa2xx_hx4700.c b/drivers/pcmcia/pxa2xx_hx4700.c
new file mode 100644
index 000000000..87b6a1639
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_hx4700.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2012 Paul Parsons <lost.distance@yahoo.com>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+
+#include <asm/mach-types.h>
+#include <mach/hx4700.h>
+
+#include "soc_common.h"
+
+static struct gpio gpios[] = {
+ { GPIO114_HX4700_CF_RESET, GPIOF_OUT_INIT_LOW, "CF reset" },
+ { EGPIO4_CF_3V3_ON, GPIOF_OUT_INIT_LOW, "CF 3.3V enable" },
+};
+
+static int hx4700_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ int ret;
+
+ ret = gpio_request_array(gpios, ARRAY_SIZE(gpios));
+ if (ret)
+ goto out;
+
+ /*
+ * IRQ type must be set before soc_pcmcia_hw_init() calls request_irq().
+ * The asic3 default IRQ type is level trigger low level detect, exactly
+ * the the signal present on GPIOD4_CF_nCD when a CF card is inserted.
+ * If the IRQ type is not changed, the asic3 interrupt handler will loop
+ * repeatedly because it is unable to clear the level trigger interrupt.
+ */
+ irq_set_irq_type(gpio_to_irq(GPIOD4_CF_nCD), IRQ_TYPE_EDGE_BOTH);
+
+ skt->stat[SOC_STAT_CD].gpio = GPIOD4_CF_nCD;
+ skt->stat[SOC_STAT_CD].name = "PCMCIA CD";
+ skt->stat[SOC_STAT_RDY].gpio = GPIO60_HX4700_CF_RNB;
+ skt->stat[SOC_STAT_RDY].name = "PCMCIA Ready";
+
+out:
+ return ret;
+}
+
+static void hx4700_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
+{
+ gpio_free_array(gpios, ARRAY_SIZE(gpios));
+}
+
+static void hx4700_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ state->vs_3v = 1;
+ state->vs_Xv = 0;
+}
+
+static int hx4700_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ switch (state->Vcc) {
+ case 0:
+ gpio_set_value(EGPIO4_CF_3V3_ON, 0);
+ break;
+ case 33:
+ gpio_set_value(EGPIO4_CF_3V3_ON, 1);
+ break;
+ default:
+ printk(KERN_ERR "pcmcia: Unsupported Vcc: %d\n", state->Vcc);
+ return -EINVAL;
+ }
+
+ gpio_set_value(GPIO114_HX4700_CF_RESET, (state->flags & SS_RESET) != 0);
+
+ return 0;
+}
+
+static struct pcmcia_low_level hx4700_pcmcia_ops = {
+ .owner = THIS_MODULE,
+ .nr = 1,
+ .hw_init = hx4700_pcmcia_hw_init,
+ .hw_shutdown = hx4700_pcmcia_hw_shutdown,
+ .socket_state = hx4700_pcmcia_socket_state,
+ .configure_socket = hx4700_pcmcia_configure_socket,
+};
+
+static struct platform_device *hx4700_pcmcia_device;
+
+static int __init hx4700_pcmcia_init(void)
+{
+ struct platform_device *pdev;
+
+ if (!machine_is_h4700())
+ return -ENODEV;
+
+ pdev = platform_device_register_data(NULL, "pxa2xx-pcmcia", -1,
+ &hx4700_pcmcia_ops, sizeof(hx4700_pcmcia_ops));
+ if (IS_ERR(pdev))
+ return PTR_ERR(pdev);
+
+ hx4700_pcmcia_device = pdev;
+
+ return 0;
+}
+
+static void __exit hx4700_pcmcia_exit(void)
+{
+ platform_device_unregister(hx4700_pcmcia_device);
+}
+
+module_init(hx4700_pcmcia_init);
+module_exit(hx4700_pcmcia_exit);
+
+MODULE_AUTHOR("Paul Parsons <lost.distance@yahoo.com>");
+MODULE_DESCRIPTION("HP iPAQ hx4700 PCMCIA driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pcmcia/pxa2xx_mainstone.c b/drivers/pcmcia/pxa2xx_mainstone.c
new file mode 100644
index 000000000..a076e4108
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_mainstone.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/pxa2xx_mainstone.c
+ *
+ * Mainstone PCMCIA specific routines.
+ *
+ * Created: May 12, 2004
+ * Author: Nicolas Pitre
+ * Copyright: MontaVista Software Inc.
+ */
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+
+#include <pcmcia/ss.h>
+
+#include <asm/mach-types.h>
+
+#include "soc_common.h"
+#include "max1600.h"
+
+static int mst_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ struct device *dev = skt->socket.dev.parent;
+ struct max1600 *m;
+ int ret;
+
+ skt->stat[SOC_STAT_CD].name = skt->nr ? "bdetect" : "adetect";
+ skt->stat[SOC_STAT_BVD1].name = skt->nr ? "bbvd1" : "abvd1";
+ skt->stat[SOC_STAT_BVD2].name = skt->nr ? "bbvd2" : "abvd2";
+ skt->stat[SOC_STAT_RDY].name = skt->nr ? "bready" : "aready";
+ skt->stat[SOC_STAT_VS1].name = skt->nr ? "bvs1" : "avs1";
+ skt->stat[SOC_STAT_VS2].name = skt->nr ? "bvs2" : "avs2";
+
+ skt->gpio_reset = devm_gpiod_get(dev, skt->nr ? "breset" : "areset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(skt->gpio_reset))
+ return PTR_ERR(skt->gpio_reset);
+
+ ret = max1600_init(dev, &m, skt->nr ? MAX1600_CHAN_B : MAX1600_CHAN_A,
+ MAX1600_CODE_HIGH);
+ if (ret)
+ return ret;
+
+ skt->driver_data = m;
+
+ return soc_pcmcia_request_gpiods(skt);
+}
+
+static unsigned int mst_pcmcia_bvd1_status[2];
+
+static void mst_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ unsigned int flip = mst_pcmcia_bvd1_status[skt->nr] ^ state->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_bvd1_status[skt->nr] = state->bvd1;
+ if (state->bvd1)
+ enable_irq(skt->stat[SOC_STAT_BVD1].irq);
+ else
+ disable_irq(skt->stat[SOC_STAT_BVD2].irq);
+ }
+}
+
+static int mst_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ return max1600_configure(skt->driver_data, state->Vcc, state->Vpp);
+}
+
+static struct pcmcia_low_level mst_pcmcia_ops __initdata = {
+ .owner = THIS_MODULE,
+ .hw_init = mst_pcmcia_hw_init,
+ .socket_state = mst_pcmcia_socket_state,
+ .configure_socket = mst_pcmcia_configure_socket,
+ .nr = 2,
+};
+
+static struct platform_device *mst_pcmcia_device;
+
+static int __init mst_pcmcia_init(void)
+{
+ int ret;
+
+ if (!machine_is_mainstone())
+ return -ENODEV;
+
+ mst_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+ if (!mst_pcmcia_device)
+ return -ENOMEM;
+
+ ret = platform_device_add_data(mst_pcmcia_device, &mst_pcmcia_ops,
+ sizeof(mst_pcmcia_ops));
+ if (ret == 0)
+ ret = platform_device_add(mst_pcmcia_device);
+
+ if (ret)
+ platform_device_put(mst_pcmcia_device);
+
+ return ret;
+}
+
+static void __exit mst_pcmcia_exit(void)
+{
+ platform_device_unregister(mst_pcmcia_device);
+}
+
+fs_initcall(mst_pcmcia_init);
+module_exit(mst_pcmcia_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pxa2xx-pcmcia");
diff --git a/drivers/pcmcia/pxa2xx_palmld.c b/drivers/pcmcia/pxa2xx_palmld.c
new file mode 100644
index 000000000..cfff41ac9
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_palmld.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/pxa2xx_palmld.c
+ *
+ * Driver for Palm LifeDrive PCMCIA
+ *
+ * Copyright (C) 2006 Alex Osborne <ato@meshy.org>
+ * Copyright (C) 2007-2011 Marek Vasut <marek.vasut@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+
+#include <asm/mach-types.h>
+#include <mach/palmld.h>
+#include "soc_common.h"
+
+static struct gpio palmld_pcmcia_gpios[] = {
+ { GPIO_NR_PALMLD_PCMCIA_POWER, GPIOF_INIT_LOW, "PCMCIA Power" },
+ { GPIO_NR_PALMLD_PCMCIA_RESET, GPIOF_INIT_HIGH,"PCMCIA Reset" },
+};
+
+static int palmld_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ int ret;
+
+ ret = gpio_request_array(palmld_pcmcia_gpios,
+ ARRAY_SIZE(palmld_pcmcia_gpios));
+
+ skt->stat[SOC_STAT_RDY].gpio = GPIO_NR_PALMLD_PCMCIA_READY;
+ skt->stat[SOC_STAT_RDY].name = "PCMCIA Ready";
+
+ return ret;
+}
+
+static void palmld_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
+{
+ gpio_free_array(palmld_pcmcia_gpios, ARRAY_SIZE(palmld_pcmcia_gpios));
+}
+
+static void palmld_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ state->detect = 1; /* always inserted */
+ state->vs_3v = 1;
+ state->vs_Xv = 0;
+}
+
+static int palmld_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ gpio_set_value(GPIO_NR_PALMLD_PCMCIA_POWER, 1);
+ gpio_set_value(GPIO_NR_PALMLD_PCMCIA_RESET,
+ !!(state->flags & SS_RESET));
+
+ return 0;
+}
+
+static struct pcmcia_low_level palmld_pcmcia_ops = {
+ .owner = THIS_MODULE,
+
+ .first = 1,
+ .nr = 1,
+
+ .hw_init = palmld_pcmcia_hw_init,
+ .hw_shutdown = palmld_pcmcia_hw_shutdown,
+
+ .socket_state = palmld_pcmcia_socket_state,
+ .configure_socket = palmld_pcmcia_configure_socket,
+};
+
+static struct platform_device *palmld_pcmcia_device;
+
+static int __init palmld_pcmcia_init(void)
+{
+ int ret;
+
+ if (!machine_is_palmld())
+ return -ENODEV;
+
+ palmld_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+ if (!palmld_pcmcia_device)
+ return -ENOMEM;
+
+ ret = platform_device_add_data(palmld_pcmcia_device, &palmld_pcmcia_ops,
+ sizeof(palmld_pcmcia_ops));
+
+ if (!ret)
+ ret = platform_device_add(palmld_pcmcia_device);
+
+ if (ret)
+ platform_device_put(palmld_pcmcia_device);
+
+ return ret;
+}
+
+static void __exit palmld_pcmcia_exit(void)
+{
+ platform_device_unregister(palmld_pcmcia_device);
+}
+
+module_init(palmld_pcmcia_init);
+module_exit(palmld_pcmcia_exit);
+
+MODULE_AUTHOR("Alex Osborne <ato@meshy.org>,"
+ " Marek Vasut <marek.vasut@gmail.com>");
+MODULE_DESCRIPTION("PCMCIA support for Palm LifeDrive");
+MODULE_ALIAS("platform:pxa2xx-pcmcia");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pcmcia/pxa2xx_palmtc.c b/drivers/pcmcia/pxa2xx_palmtc.c
new file mode 100644
index 000000000..8fe05613e
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_palmtc.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/pxa2xx_palmtc.c
+ *
+ * Driver for Palm Tungsten|C PCMCIA
+ *
+ * Copyright (C) 2008 Alex Osborne <ato@meshy.org>
+ * Copyright (C) 2009-2011 Marek Vasut <marek.vasut@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+
+#include <asm/mach-types.h>
+#include <mach/palmtc.h>
+#include "soc_common.h"
+
+static struct gpio palmtc_pcmcia_gpios[] = {
+ { GPIO_NR_PALMTC_PCMCIA_POWER1, GPIOF_INIT_LOW, "PCMCIA Power 1" },
+ { GPIO_NR_PALMTC_PCMCIA_POWER2, GPIOF_INIT_LOW, "PCMCIA Power 2" },
+ { GPIO_NR_PALMTC_PCMCIA_POWER3, GPIOF_INIT_LOW, "PCMCIA Power 3" },
+ { GPIO_NR_PALMTC_PCMCIA_RESET, GPIOF_INIT_HIGH,"PCMCIA Reset" },
+ { GPIO_NR_PALMTC_PCMCIA_PWRREADY, GPIOF_IN, "PCMCIA Power Ready" },
+};
+
+static int palmtc_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ int ret;
+
+ ret = gpio_request_array(palmtc_pcmcia_gpios,
+ ARRAY_SIZE(palmtc_pcmcia_gpios));
+
+ skt->stat[SOC_STAT_RDY].gpio = GPIO_NR_PALMTC_PCMCIA_READY;
+ skt->stat[SOC_STAT_RDY].name = "PCMCIA Ready";
+
+ return ret;
+}
+
+static void palmtc_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
+{
+ gpio_free_array(palmtc_pcmcia_gpios, ARRAY_SIZE(palmtc_pcmcia_gpios));
+}
+
+static void palmtc_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ state->detect = 1; /* always inserted */
+ state->vs_3v = 1;
+ state->vs_Xv = 0;
+}
+
+static int palmtc_wifi_powerdown(void)
+{
+ gpio_set_value(GPIO_NR_PALMTC_PCMCIA_RESET, 1);
+ gpio_set_value(GPIO_NR_PALMTC_PCMCIA_POWER2, 0);
+ mdelay(40);
+ gpio_set_value(GPIO_NR_PALMTC_PCMCIA_POWER1, 0);
+ return 0;
+}
+
+static int palmtc_wifi_powerup(void)
+{
+ int timeout = 50;
+
+ gpio_set_value(GPIO_NR_PALMTC_PCMCIA_POWER3, 1);
+ mdelay(50);
+
+ /* Power up the card, 1.8V first, after a while 3.3V */
+ gpio_set_value(GPIO_NR_PALMTC_PCMCIA_POWER1, 1);
+ mdelay(100);
+ gpio_set_value(GPIO_NR_PALMTC_PCMCIA_POWER2, 1);
+
+ /* Wait till the card is ready */
+ while (!gpio_get_value(GPIO_NR_PALMTC_PCMCIA_PWRREADY) &&
+ timeout) {
+ mdelay(1);
+ timeout--;
+ }
+
+ /* Power down the WiFi in case of error */
+ if (!timeout) {
+ palmtc_wifi_powerdown();
+ return 1;
+ }
+
+ /* Reset the card */
+ gpio_set_value(GPIO_NR_PALMTC_PCMCIA_RESET, 1);
+ mdelay(20);
+ gpio_set_value(GPIO_NR_PALMTC_PCMCIA_RESET, 0);
+ mdelay(25);
+
+ gpio_set_value(GPIO_NR_PALMTC_PCMCIA_POWER3, 0);
+
+ return 0;
+}
+
+static int palmtc_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ int ret = 1;
+
+ if (state->Vcc == 0)
+ ret = palmtc_wifi_powerdown();
+ else if (state->Vcc == 33)
+ ret = palmtc_wifi_powerup();
+
+ return ret;
+}
+
+static struct pcmcia_low_level palmtc_pcmcia_ops = {
+ .owner = THIS_MODULE,
+
+ .first = 0,
+ .nr = 1,
+
+ .hw_init = palmtc_pcmcia_hw_init,
+ .hw_shutdown = palmtc_pcmcia_hw_shutdown,
+
+ .socket_state = palmtc_pcmcia_socket_state,
+ .configure_socket = palmtc_pcmcia_configure_socket,
+};
+
+static struct platform_device *palmtc_pcmcia_device;
+
+static int __init palmtc_pcmcia_init(void)
+{
+ int ret;
+
+ if (!machine_is_palmtc())
+ return -ENODEV;
+
+ palmtc_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+ if (!palmtc_pcmcia_device)
+ return -ENOMEM;
+
+ ret = platform_device_add_data(palmtc_pcmcia_device, &palmtc_pcmcia_ops,
+ sizeof(palmtc_pcmcia_ops));
+
+ if (!ret)
+ ret = platform_device_add(palmtc_pcmcia_device);
+
+ if (ret)
+ platform_device_put(palmtc_pcmcia_device);
+
+ return ret;
+}
+
+static void __exit palmtc_pcmcia_exit(void)
+{
+ platform_device_unregister(palmtc_pcmcia_device);
+}
+
+module_init(palmtc_pcmcia_init);
+module_exit(palmtc_pcmcia_exit);
+
+MODULE_AUTHOR("Alex Osborne <ato@meshy.org>,"
+ " Marek Vasut <marek.vasut@gmail.com>");
+MODULE_DESCRIPTION("PCMCIA support for Palm Tungsten|C");
+MODULE_ALIAS("platform:pxa2xx-pcmcia");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pcmcia/pxa2xx_palmtx.c b/drivers/pcmcia/pxa2xx_palmtx.c
new file mode 100644
index 000000000..c449ca72c
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_palmtx.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/pxa2xx_palmtx.c
+ *
+ * Driver for Palm T|X PCMCIA
+ *
+ * Copyright (C) 2007-2011 Marek Vasut <marek.vasut@gmail.com>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+
+#include <asm/mach-types.h>
+#include <mach/palmtx.h>
+#include "soc_common.h"
+
+static struct gpio palmtx_pcmcia_gpios[] = {
+ { GPIO_NR_PALMTX_PCMCIA_POWER1, GPIOF_INIT_LOW, "PCMCIA Power 1" },
+ { GPIO_NR_PALMTX_PCMCIA_POWER2, GPIOF_INIT_LOW, "PCMCIA Power 2" },
+ { GPIO_NR_PALMTX_PCMCIA_RESET, GPIOF_INIT_HIGH,"PCMCIA Reset" },
+};
+
+static int palmtx_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ int ret;
+
+ ret = gpio_request_array(palmtx_pcmcia_gpios,
+ ARRAY_SIZE(palmtx_pcmcia_gpios));
+
+ skt->stat[SOC_STAT_RDY].gpio = GPIO_NR_PALMTX_PCMCIA_READY;
+ skt->stat[SOC_STAT_RDY].name = "PCMCIA Ready";
+
+ return ret;
+}
+
+static void palmtx_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
+{
+ gpio_free_array(palmtx_pcmcia_gpios, ARRAY_SIZE(palmtx_pcmcia_gpios));
+}
+
+static void palmtx_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ state->detect = 1; /* always inserted */
+ state->vs_3v = 1;
+ state->vs_Xv = 0;
+}
+
+static int
+palmtx_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ gpio_set_value(GPIO_NR_PALMTX_PCMCIA_POWER1, 1);
+ gpio_set_value(GPIO_NR_PALMTX_PCMCIA_POWER2, 1);
+ gpio_set_value(GPIO_NR_PALMTX_PCMCIA_RESET,
+ !!(state->flags & SS_RESET));
+
+ return 0;
+}
+
+static struct pcmcia_low_level palmtx_pcmcia_ops = {
+ .owner = THIS_MODULE,
+
+ .first = 0,
+ .nr = 1,
+
+ .hw_init = palmtx_pcmcia_hw_init,
+ .hw_shutdown = palmtx_pcmcia_hw_shutdown,
+
+ .socket_state = palmtx_pcmcia_socket_state,
+ .configure_socket = palmtx_pcmcia_configure_socket,
+};
+
+static struct platform_device *palmtx_pcmcia_device;
+
+static int __init palmtx_pcmcia_init(void)
+{
+ int ret;
+
+ if (!machine_is_palmtx())
+ return -ENODEV;
+
+ palmtx_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+ if (!palmtx_pcmcia_device)
+ return -ENOMEM;
+
+ ret = platform_device_add_data(palmtx_pcmcia_device, &palmtx_pcmcia_ops,
+ sizeof(palmtx_pcmcia_ops));
+
+ if (!ret)
+ ret = platform_device_add(palmtx_pcmcia_device);
+
+ if (ret)
+ platform_device_put(palmtx_pcmcia_device);
+
+ return ret;
+}
+
+static void __exit palmtx_pcmcia_exit(void)
+{
+ platform_device_unregister(palmtx_pcmcia_device);
+}
+
+module_init(palmtx_pcmcia_init);
+module_exit(palmtx_pcmcia_exit);
+
+MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
+MODULE_DESCRIPTION("PCMCIA support for Palm T|X");
+MODULE_ALIAS("platform:pxa2xx-pcmcia");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pcmcia/pxa2xx_sharpsl.c b/drivers/pcmcia/pxa2xx_sharpsl.c
new file mode 100644
index 000000000..5fdd25a9e
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_sharpsl.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Sharp SL-C7xx Series PCMCIA routines
+ *
+ * Copyright (c) 2004-2005 Richard Purdie
+ *
+ * Based on Sharp's 2.4 kernel patches and pxa2xx_mainstone.c
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <asm/irq.h>
+#include <asm/hardware/scoop.h>
+
+#include "soc_common.h"
+
+#define NO_KEEP_VS 0x0001
+#define SCOOP_DEV platform_scoop_config->devs
+
+static void sharpsl_pcmcia_init_reset(struct soc_pcmcia_socket *skt)
+{
+ struct scoop_pcmcia_dev *scoopdev = &SCOOP_DEV[skt->nr];
+
+ reset_scoop(scoopdev->dev);
+
+ /* Shared power controls need to be handled carefully */
+ if (platform_scoop_config->power_ctrl)
+ platform_scoop_config->power_ctrl(scoopdev->dev, 0x0000, skt->nr);
+ else
+ write_scoop_reg(scoopdev->dev, SCOOP_CPR, 0x0000);
+
+ scoopdev->keep_vs = NO_KEEP_VS;
+ scoopdev->keep_rd = 0;
+}
+
+static int sharpsl_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ if (SCOOP_DEV[skt->nr].cd_irq >= 0) {
+ skt->stat[SOC_STAT_CD].irq = SCOOP_DEV[skt->nr].cd_irq;
+ skt->stat[SOC_STAT_CD].name = SCOOP_DEV[skt->nr].cd_irq_str;
+ }
+
+ skt->socket.pci_irq = SCOOP_DEV[skt->nr].irq;
+
+ return 0;
+}
+
+static void sharpsl_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ unsigned short cpr, csr;
+ struct device *scoop = SCOOP_DEV[skt->nr].dev;
+
+ cpr = read_scoop_reg(SCOOP_DEV[skt->nr].dev, SCOOP_CPR);
+
+ write_scoop_reg(scoop, SCOOP_IRM, 0x00FF);
+ write_scoop_reg(scoop, SCOOP_ISR, 0x0000);
+ write_scoop_reg(scoop, SCOOP_IRM, 0x0000);
+ csr = read_scoop_reg(scoop, SCOOP_CSR);
+ if (csr & 0x0004) {
+ /* card eject */
+ write_scoop_reg(scoop, SCOOP_CDR, 0x0000);
+ SCOOP_DEV[skt->nr].keep_vs = NO_KEEP_VS;
+ }
+ else if (!(SCOOP_DEV[skt->nr].keep_vs & NO_KEEP_VS)) {
+ /* keep vs1,vs2 */
+ write_scoop_reg(scoop, SCOOP_CDR, 0x0000);
+ csr |= SCOOP_DEV[skt->nr].keep_vs;
+ }
+ else if (cpr & 0x0003) {
+ /* power on */
+ write_scoop_reg(scoop, SCOOP_CDR, 0x0000);
+ SCOOP_DEV[skt->nr].keep_vs = (csr & 0x00C0);
+ }
+ else {
+ /* card detect */
+ if ((machine_is_spitz() || machine_is_borzoi()) && skt->nr == 1) {
+ write_scoop_reg(scoop, SCOOP_CDR, 0x0000);
+ } else {
+ write_scoop_reg(scoop, 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;
+ struct device *scoop = SCOOP_DEV[skt->nr].dev;
+
+ 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(scoop, SCOOP_MCR)) & ~0x0010;
+ ncpr = (cpr = read_scoop_reg(scoop, SCOOP_CPR)) & ~0x0083;
+ nccr = (ccr = read_scoop_reg(scoop, SCOOP_CCR)) & ~0x0080;
+ nimr = (imr = read_scoop_reg(scoop, SCOOP_IMR)) & ~0x003E;
+
+ if ((machine_is_spitz() || machine_is_borzoi() || machine_is_akita()) && skt->nr == 0) {
+ ncpr |= (state->Vcc == 33) ? 0x0002 :
+ (state->Vcc == 50) ? 0x0002 : 0;
+ } else {
+ 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)) {
+ SCOOP_DEV[skt->nr].keep_rd = 0;
+ } else if (!SCOOP_DEV[skt->nr].keep_rd) {
+ if (nccr & 0x0080)
+ SCOOP_DEV[skt->nr].keep_rd = 1;
+ else
+ nccr |= 0x0080;
+ }
+
+ if (mcr != nmcr)
+ write_scoop_reg(scoop, SCOOP_MCR, nmcr);
+ if (cpr != ncpr) {
+ if (platform_scoop_config->power_ctrl)
+ platform_scoop_config->power_ctrl(scoop, ncpr , skt->nr);
+ else
+ write_scoop_reg(scoop, SCOOP_CPR, ncpr);
+ }
+ if (ccr != nccr)
+ write_scoop_reg(scoop, SCOOP_CCR, nccr);
+ if (imr != nimr)
+ write_scoop_reg(scoop, SCOOP_IMR, nimr);
+
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+static void sharpsl_pcmcia_socket_init(struct soc_pcmcia_socket *skt)
+{
+ sharpsl_pcmcia_init_reset(skt);
+
+ /* Enable interrupt */
+ write_scoop_reg(SCOOP_DEV[skt->nr].dev, SCOOP_IMR, 0x00C0);
+ write_scoop_reg(SCOOP_DEV[skt->nr].dev, SCOOP_MCR, 0x0101);
+ SCOOP_DEV[skt->nr].keep_vs = NO_KEEP_VS;
+}
+
+static void sharpsl_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt)
+{
+ sharpsl_pcmcia_init_reset(skt);
+}
+
+static struct pcmcia_low_level sharpsl_pcmcia_ops = {
+ .owner = THIS_MODULE,
+ .hw_init = sharpsl_pcmcia_hw_init,
+ .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 = 0,
+};
+
+#ifdef CONFIG_SA1100_COLLIE
+#include "sa11xx_base.h"
+
+int pcmcia_collie_init(struct device *dev)
+{
+ int ret = -ENODEV;
+
+ if (machine_is_collie())
+ ret = sa11xx_drv_pcmcia_probe(dev, &sharpsl_pcmcia_ops, 0, 1);
+
+ return ret;
+}
+
+#else
+
+static struct platform_device *sharpsl_pcmcia_device;
+
+static int __init sharpsl_pcmcia_init(void)
+{
+ int ret;
+
+ if (!platform_scoop_config)
+ return -ENODEV;
+
+ sharpsl_pcmcia_ops.nr = platform_scoop_config->num_devs;
+ sharpsl_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+
+ if (!sharpsl_pcmcia_device)
+ return -ENOMEM;
+
+ ret = platform_device_add_data(sharpsl_pcmcia_device,
+ &sharpsl_pcmcia_ops, sizeof(sharpsl_pcmcia_ops));
+ if (ret == 0) {
+ sharpsl_pcmcia_device->dev.parent = platform_scoop_config->devs[0].dev;
+ ret = platform_device_add(sharpsl_pcmcia_device);
+ }
+
+ if (ret)
+ platform_device_put(sharpsl_pcmcia_device);
+
+ return ret;
+}
+
+static void __exit sharpsl_pcmcia_exit(void)
+{
+ platform_device_unregister(sharpsl_pcmcia_device);
+}
+
+fs_initcall(sharpsl_pcmcia_init);
+module_exit(sharpsl_pcmcia_exit);
+#endif
+
+MODULE_DESCRIPTION("Sharp SL Series PCMCIA Support");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pxa2xx-pcmcia");
diff --git a/drivers/pcmcia/pxa2xx_stargate2.c b/drivers/pcmcia/pxa2xx_stargate2.c
new file mode 100644
index 000000000..6efb7f814
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_stargate2.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/pxa2xx_stargate2.c
+ *
+ * Stargate 2 PCMCIA specific routines.
+ *
+ * Created: December 6, 2005
+ * Author: Ed C. Epp
+ * Copyright: Intel Corp 2005
+ * Jonathan Cameron <jic23@cam.ac.uk> 2009
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+
+#include <pcmcia/ss.h>
+
+#include <asm/irq.h>
+#include <asm/mach-types.h>
+
+#include "soc_common.h"
+
+#define SG2_S0_POWER_CTL 108
+#define SG2_S0_GPIO_RESET 82
+#define SG2_S0_GPIO_DETECT 53
+#define SG2_S0_GPIO_READY 81
+
+static struct gpio sg2_pcmcia_gpios[] = {
+ { SG2_S0_GPIO_RESET, GPIOF_OUT_INIT_HIGH, "PCMCIA Reset" },
+ { SG2_S0_POWER_CTL, GPIOF_OUT_INIT_HIGH, "PCMCIA Power Ctrl" },
+};
+
+static int sg2_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ skt->stat[SOC_STAT_CD].gpio = SG2_S0_GPIO_DETECT;
+ skt->stat[SOC_STAT_CD].name = "PCMCIA0 CD";
+ skt->stat[SOC_STAT_RDY].gpio = SG2_S0_GPIO_READY;
+ skt->stat[SOC_STAT_RDY].name = "PCMCIA0 RDY";
+ return 0;
+}
+
+static void sg2_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ state->bvd1 = 0; /* not available - battery detect on card */
+ state->bvd2 = 0; /* not available */
+ state->vs_3v = 1; /* not available - voltage detect for card */
+ state->vs_Xv = 0; /* not available */
+}
+
+static int sg2_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ /* Enable card power */
+ switch (state->Vcc) {
+ case 0:
+ /* sets power ctl register high */
+ gpio_set_value(SG2_S0_POWER_CTL, 1);
+ break;
+ case 33:
+ case 50:
+ /* sets power control register low (clear) */
+ gpio_set_value(SG2_S0_POWER_CTL, 0);
+ msleep(100);
+ break;
+ default:
+ pr_err("%s(): bad Vcc %u\n",
+ __func__, state->Vcc);
+ return -1;
+ }
+
+ /* reset */
+ gpio_set_value(SG2_S0_GPIO_RESET, !!(state->flags & SS_RESET));
+
+ return 0;
+}
+
+static struct pcmcia_low_level sg2_pcmcia_ops __initdata = {
+ .owner = THIS_MODULE,
+ .hw_init = sg2_pcmcia_hw_init,
+ .socket_state = sg2_pcmcia_socket_state,
+ .configure_socket = sg2_pcmcia_configure_socket,
+ .nr = 1,
+};
+
+static struct platform_device *sg2_pcmcia_device;
+
+static int __init sg2_pcmcia_init(void)
+{
+ int ret;
+
+ if (!machine_is_stargate2())
+ return -ENODEV;
+
+ sg2_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+ if (!sg2_pcmcia_device)
+ return -ENOMEM;
+
+ ret = gpio_request_array(sg2_pcmcia_gpios, ARRAY_SIZE(sg2_pcmcia_gpios));
+ if (ret)
+ goto error_put_platform_device;
+
+ ret = platform_device_add_data(sg2_pcmcia_device,
+ &sg2_pcmcia_ops,
+ sizeof(sg2_pcmcia_ops));
+ if (ret)
+ goto error_free_gpios;
+
+ ret = platform_device_add(sg2_pcmcia_device);
+ if (ret)
+ goto error_free_gpios;
+
+ return 0;
+error_free_gpios:
+ gpio_free_array(sg2_pcmcia_gpios, ARRAY_SIZE(sg2_pcmcia_gpios));
+error_put_platform_device:
+ platform_device_put(sg2_pcmcia_device);
+
+ return ret;
+}
+
+static void __exit sg2_pcmcia_exit(void)
+{
+ platform_device_unregister(sg2_pcmcia_device);
+ gpio_free_array(sg2_pcmcia_gpios, ARRAY_SIZE(sg2_pcmcia_gpios));
+}
+
+fs_initcall(sg2_pcmcia_init);
+module_exit(sg2_pcmcia_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pxa2xx-pcmcia");
diff --git a/drivers/pcmcia/pxa2xx_trizeps4.c b/drivers/pcmcia/pxa2xx_trizeps4.c
new file mode 100644
index 000000000..6db8fe880
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_trizeps4.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/pxa2xx_trizeps4.c
+ *
+ * TRIZEPS PCMCIA specific routines.
+ *
+ * Author: Jürgen Schindele
+ * Created: 20 02, 2006
+ * Copyright: Jürgen Schindele
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <asm/mach-types.h>
+#include <asm/irq.h>
+
+#include <mach/pxa2xx-regs.h>
+#include <mach/trizeps4.h>
+
+#include "soc_common.h"
+
+extern void board_pcmcia_power(int power);
+
+static int trizeps_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ /* we dont have voltage/card/ready detection
+ * so we dont need interrupts for it
+ */
+ switch (skt->nr) {
+ case 0:
+ skt->stat[SOC_STAT_CD].gpio = GPIO_PCD;
+ skt->stat[SOC_STAT_CD].name = "cs0_cd";
+ skt->stat[SOC_STAT_RDY].gpio = GPIO_PRDY;
+ skt->stat[SOC_STAT_RDY].name = "cs0_rdy";
+ break;
+ default:
+ break;
+ }
+ /* release the reset of this card */
+ pr_debug("%s: sock %d irq %d\n", __func__, skt->nr, skt->socket.pci_irq);
+
+ return 0;
+}
+
+static unsigned long trizeps_pcmcia_status[2];
+
+static void trizeps_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ unsigned short status = 0, change;
+ status = CFSR_readw();
+ change = (status ^ trizeps_pcmcia_status[skt->nr]) &
+ ConXS_CFSR_BVD_MASK;
+ if (change) {
+ trizeps_pcmcia_status[skt->nr] = status;
+ if (status & ConXS_CFSR_BVD1) {
+ /* enable_irq empty */
+ } else {
+ /* disable_irq empty */
+ }
+ }
+
+ switch (skt->nr) {
+ case 0:
+ /* just fill in fix states */
+ state->bvd1 = (status & ConXS_CFSR_BVD1) ? 1 : 0;
+ state->bvd2 = (status & ConXS_CFSR_BVD2) ? 1 : 0;
+ state->vs_3v = (status & ConXS_CFSR_VS1) ? 0 : 1;
+ state->vs_Xv = (status & ConXS_CFSR_VS2) ? 0 : 1;
+ break;
+
+#ifndef CONFIG_MACH_TRIZEPS_CONXS
+ /* on ConXS we only have one slot. Second is inactive */
+ case 1:
+ state->detect = 0;
+ state->ready = 0;
+ state->bvd1 = 0;
+ state->bvd2 = 0;
+ state->vs_3v = 0;
+ state->vs_Xv = 0;
+ break;
+
+#endif
+ }
+}
+
+static int trizeps_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ int ret = 0;
+ unsigned short power = 0;
+
+ /* we do nothing here just check a bit */
+ switch (state->Vcc) {
+ case 0: power &= 0xfc; break;
+ case 33: power |= ConXS_BCR_S0_VCC_3V3; break;
+ case 50:
+ pr_err("%s(): Vcc 5V not supported in socket\n", __func__);
+ break;
+ default:
+ pr_err("%s(): bad Vcc %u\n", __func__, state->Vcc);
+ ret = -1;
+ }
+
+ switch (state->Vpp) {
+ case 0: power &= 0xf3; break;
+ case 33: power |= ConXS_BCR_S0_VPP_3V3; break;
+ case 120:
+ pr_err("%s(): Vpp 12V not supported in socket\n", __func__);
+ break;
+ default:
+ if (state->Vpp != state->Vcc) {
+ pr_err("%s(): bad Vpp %u\n", __func__, state->Vpp);
+ ret = -1;
+ }
+ }
+
+ switch (skt->nr) {
+ case 0: /* we only have 3.3V */
+ board_pcmcia_power(power);
+ break;
+
+#ifndef CONFIG_MACH_TRIZEPS_CONXS
+ /* on ConXS we only have one slot. Second is inactive */
+ case 1:
+#endif
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void trizeps_pcmcia_socket_init(struct soc_pcmcia_socket *skt)
+{
+ /* default is on */
+ board_pcmcia_power(0x9);
+}
+
+static void trizeps_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt)
+{
+ board_pcmcia_power(0x0);
+}
+
+static struct pcmcia_low_level trizeps_pcmcia_ops = {
+ .owner = THIS_MODULE,
+ .hw_init = trizeps_pcmcia_hw_init,
+ .socket_state = trizeps_pcmcia_socket_state,
+ .configure_socket = trizeps_pcmcia_configure_socket,
+ .socket_init = trizeps_pcmcia_socket_init,
+ .socket_suspend = trizeps_pcmcia_socket_suspend,
+#ifdef CONFIG_MACH_TRIZEPS_CONXS
+ .nr = 1,
+#else
+ .nr = 2,
+#endif
+ .first = 0,
+};
+
+static struct platform_device *trizeps_pcmcia_device;
+
+static int __init trizeps_pcmcia_init(void)
+{
+ int ret;
+
+ if (!machine_is_trizeps4() && !machine_is_trizeps4wl())
+ return -ENODEV;
+
+ trizeps_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+ if (!trizeps_pcmcia_device)
+ return -ENOMEM;
+
+ ret = platform_device_add_data(trizeps_pcmcia_device,
+ &trizeps_pcmcia_ops, sizeof(trizeps_pcmcia_ops));
+
+ if (ret == 0)
+ ret = platform_device_add(trizeps_pcmcia_device);
+
+ if (ret)
+ platform_device_put(trizeps_pcmcia_device);
+
+ return ret;
+}
+
+static void __exit trizeps_pcmcia_exit(void)
+{
+ platform_device_unregister(trizeps_pcmcia_device);
+}
+
+fs_initcall(trizeps_pcmcia_init);
+module_exit(trizeps_pcmcia_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Juergen Schindele");
+MODULE_ALIAS("platform:pxa2xx-pcmcia");
diff --git a/drivers/pcmcia/pxa2xx_viper.c b/drivers/pcmcia/pxa2xx_viper.c
new file mode 100644
index 000000000..7ac6647d2
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_viper.c
@@ -0,0 +1,182 @@
+/*
+ * Viper/Zeus PCMCIA support
+ * Copyright 2004 Arcom Control Systems
+ *
+ * Maintained by Marc Zyngier <maz@misterjones.org>
+ *
+ * Based on:
+ * iPAQ h2200 PCMCIA support
+ * Copyright 2004 Koen Kooi <koen@vestingbar.nl>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+
+#include <pcmcia/ss.h>
+
+#include <asm/irq.h>
+
+#include <linux/platform_data/pcmcia-pxa2xx_viper.h>
+
+#include "soc_common.h"
+#include "pxa2xx_base.h"
+
+static struct platform_device *arcom_pcmcia_dev;
+
+static inline struct arcom_pcmcia_pdata *viper_get_pdata(void)
+{
+ return arcom_pcmcia_dev->dev.platform_data;
+}
+
+static int viper_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ struct arcom_pcmcia_pdata *pdata = viper_get_pdata();
+ unsigned long flags;
+
+ skt->stat[SOC_STAT_CD].gpio = pdata->cd_gpio;
+ skt->stat[SOC_STAT_CD].name = "PCMCIA_CD";
+ skt->stat[SOC_STAT_RDY].gpio = pdata->rdy_gpio;
+ skt->stat[SOC_STAT_RDY].name = "CF ready";
+
+ if (gpio_request(pdata->pwr_gpio, "CF power"))
+ goto err_request_pwr;
+
+ local_irq_save(flags);
+
+ if (gpio_direction_output(pdata->pwr_gpio, 0)) {
+ local_irq_restore(flags);
+ goto err_dir;
+ }
+
+ local_irq_restore(flags);
+
+ return 0;
+
+err_dir:
+ gpio_free(pdata->pwr_gpio);
+err_request_pwr:
+ dev_err(&arcom_pcmcia_dev->dev, "Failed to setup PCMCIA GPIOs\n");
+ return -1;
+}
+
+/*
+ * Release all resources.
+ */
+static void viper_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
+{
+ struct arcom_pcmcia_pdata *pdata = viper_get_pdata();
+
+ gpio_free(pdata->pwr_gpio);
+}
+
+static void viper_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ state->vs_3v = 1; /* Can only apply 3.3V */
+ state->vs_Xv = 0;
+}
+
+static int viper_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ struct arcom_pcmcia_pdata *pdata = viper_get_pdata();
+
+ /* Silently ignore Vpp, output enable, speaker enable. */
+ pdata->reset(state->flags & SS_RESET);
+
+ /* Apply socket voltage */
+ switch (state->Vcc) {
+ case 0:
+ gpio_set_value(pdata->pwr_gpio, 0);
+ break;
+ case 33:
+ gpio_set_value(pdata->pwr_gpio, 1);
+ break;
+ default:
+ dev_err(&arcom_pcmcia_dev->dev, "Unsupported Vcc:%d\n", state->Vcc);
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct pcmcia_low_level viper_pcmcia_ops = {
+ .owner = THIS_MODULE,
+ .hw_init = viper_pcmcia_hw_init,
+ .hw_shutdown = viper_pcmcia_hw_shutdown,
+ .socket_state = viper_pcmcia_socket_state,
+ .configure_socket = viper_pcmcia_configure_socket,
+ .nr = 1,
+};
+
+static struct platform_device *viper_pcmcia_device;
+
+static int viper_pcmcia_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ /* I can't imagine more than one device, but you never know... */
+ if (arcom_pcmcia_dev)
+ return -EEXIST;
+
+ if (!pdev->dev.platform_data)
+ return -EINVAL;
+
+ viper_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+ if (!viper_pcmcia_device)
+ return -ENOMEM;
+
+ arcom_pcmcia_dev = pdev;
+
+ viper_pcmcia_device->dev.parent = &pdev->dev;
+
+ ret = platform_device_add_data(viper_pcmcia_device,
+ &viper_pcmcia_ops,
+ sizeof(viper_pcmcia_ops));
+
+ if (!ret)
+ ret = platform_device_add(viper_pcmcia_device);
+
+ if (ret) {
+ platform_device_put(viper_pcmcia_device);
+ arcom_pcmcia_dev = NULL;
+ }
+
+ return ret;
+}
+
+static int viper_pcmcia_remove(struct platform_device *pdev)
+{
+ platform_device_unregister(viper_pcmcia_device);
+ arcom_pcmcia_dev = NULL;
+ return 0;
+}
+
+static struct platform_device_id viper_pcmcia_id_table[] = {
+ { .name = "viper-pcmcia", },
+ { .name = "zeus-pcmcia", },
+ { },
+};
+
+static struct platform_driver viper_pcmcia_driver = {
+ .probe = viper_pcmcia_probe,
+ .remove = viper_pcmcia_remove,
+ .driver = {
+ .name = "arcom-pcmcia",
+ },
+ .id_table = viper_pcmcia_id_table,
+};
+
+module_platform_driver(viper_pcmcia_driver);
+
+MODULE_DEVICE_TABLE(platform, viper_pcmcia_id_table);
+MODULE_LICENSE("GPL");
diff --git a/drivers/pcmcia/pxa2xx_vpac270.c b/drivers/pcmcia/pxa2xx_vpac270.c
new file mode 100644
index 000000000..3565add03
--- /dev/null
+++ b/drivers/pcmcia/pxa2xx_vpac270.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/pxa2xx_vpac270.c
+ *
+ * Driver for Voipac PXA270 PCMCIA and CF sockets
+ *
+ * Copyright (C) 2010-2011 Marek Vasut <marek.vasut@gmail.com>
+ */
+
+#include <linux/gpio.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <asm/mach-types.h>
+
+#include <mach/vpac270.h>
+
+#include "soc_common.h"
+
+static struct gpio vpac270_pcmcia_gpios[] = {
+ { GPIO107_VPAC270_PCMCIA_PPEN, GPIOF_INIT_LOW, "PCMCIA PPEN" },
+ { GPIO11_VPAC270_PCMCIA_RESET, GPIOF_INIT_LOW, "PCMCIA Reset" },
+};
+
+static struct gpio vpac270_cf_gpios[] = {
+ { GPIO16_VPAC270_CF_RESET, GPIOF_INIT_LOW, "CF Reset" },
+};
+
+static int vpac270_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ int ret;
+
+ if (skt->nr == 0) {
+ ret = gpio_request_array(vpac270_pcmcia_gpios,
+ ARRAY_SIZE(vpac270_pcmcia_gpios));
+
+ skt->stat[SOC_STAT_CD].gpio = GPIO84_VPAC270_PCMCIA_CD;
+ skt->stat[SOC_STAT_CD].name = "PCMCIA CD";
+ skt->stat[SOC_STAT_RDY].gpio = GPIO35_VPAC270_PCMCIA_RDY;
+ skt->stat[SOC_STAT_RDY].name = "PCMCIA Ready";
+ } else {
+ ret = gpio_request_array(vpac270_cf_gpios,
+ ARRAY_SIZE(vpac270_cf_gpios));
+
+ skt->stat[SOC_STAT_CD].gpio = GPIO17_VPAC270_CF_CD;
+ skt->stat[SOC_STAT_CD].name = "CF CD";
+ skt->stat[SOC_STAT_RDY].gpio = GPIO12_VPAC270_CF_RDY;
+ skt->stat[SOC_STAT_RDY].name = "CF Ready";
+ }
+
+ return ret;
+}
+
+static void vpac270_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
+{
+ if (skt->nr == 0)
+ gpio_free_array(vpac270_pcmcia_gpios,
+ ARRAY_SIZE(vpac270_pcmcia_gpios));
+ else
+ gpio_free_array(vpac270_cf_gpios,
+ ARRAY_SIZE(vpac270_cf_gpios));
+}
+
+static void vpac270_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ state->vs_3v = 1;
+ state->vs_Xv = 0;
+}
+
+static int
+vpac270_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ if (skt->nr == 0) {
+ gpio_set_value(GPIO11_VPAC270_PCMCIA_RESET,
+ (state->flags & SS_RESET));
+ gpio_set_value(GPIO107_VPAC270_PCMCIA_PPEN,
+ !(state->Vcc == 33 || state->Vcc == 50));
+ } else {
+ gpio_set_value(GPIO16_VPAC270_CF_RESET,
+ (state->flags & SS_RESET));
+ }
+
+ return 0;
+}
+
+static struct pcmcia_low_level vpac270_pcmcia_ops = {
+ .owner = THIS_MODULE,
+
+ .first = 0,
+ .nr = 2,
+
+ .hw_init = vpac270_pcmcia_hw_init,
+ .hw_shutdown = vpac270_pcmcia_hw_shutdown,
+
+ .socket_state = vpac270_pcmcia_socket_state,
+ .configure_socket = vpac270_pcmcia_configure_socket,
+};
+
+static struct platform_device *vpac270_pcmcia_device;
+
+static int __init vpac270_pcmcia_init(void)
+{
+ int ret;
+
+ if (!machine_is_vpac270())
+ return -ENODEV;
+
+ vpac270_pcmcia_device = platform_device_alloc("pxa2xx-pcmcia", -1);
+ if (!vpac270_pcmcia_device)
+ return -ENOMEM;
+
+ ret = platform_device_add_data(vpac270_pcmcia_device,
+ &vpac270_pcmcia_ops, sizeof(vpac270_pcmcia_ops));
+
+ if (!ret)
+ ret = platform_device_add(vpac270_pcmcia_device);
+
+ if (ret)
+ platform_device_put(vpac270_pcmcia_device);
+
+ return ret;
+}
+
+static void __exit vpac270_pcmcia_exit(void)
+{
+ platform_device_unregister(vpac270_pcmcia_device);
+}
+
+module_init(vpac270_pcmcia_init);
+module_exit(vpac270_pcmcia_exit);
+
+MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
+MODULE_DESCRIPTION("PCMCIA support for Voipac PXA270");
+MODULE_ALIAS("platform:pxa2xx-pcmcia");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pcmcia/ricoh.h b/drivers/pcmcia/ricoh.h
new file mode 100644
index 000000000..8ac7b138c
--- /dev/null
+++ b/drivers/pcmcia/ricoh.h
@@ -0,0 +1,241 @@
+/*
+ * 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
+
+/* Misc Control 3 Register */
+#define RL5C4XX_MISC3 0x00A2 /* 16 bit */
+#define RL5C47X_MISC3_CB_CLKRUN_DIS BIT(1)
+
+#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_set_clkrun(struct yenta_socket *socket, bool quiet)
+{
+ u16 misc3;
+
+ /*
+ * RL5C475II likely has this setting, too, however no datasheet
+ * is publicly available for this chip
+ */
+ if (socket->dev->device != PCI_DEVICE_ID_RICOH_RL5C476 &&
+ socket->dev->device != PCI_DEVICE_ID_RICOH_RL5C478)
+ return;
+
+ if (socket->dev->revision < 0x80)
+ return;
+
+ misc3 = config_readw(socket, RL5C4XX_MISC3);
+ if (misc3 & RL5C47X_MISC3_CB_CLKRUN_DIS) {
+ if (!quiet)
+ dev_dbg(&socket->dev->dev,
+ "CLKRUN feature already disabled\n");
+ } else if (disable_clkrun) {
+ if (!quiet)
+ dev_info(&socket->dev->dev,
+ "Disabling CLKRUN feature\n");
+ misc3 |= RL5C47X_MISC3_CB_CLKRUN_DIS;
+ config_writew(socket, RL5C4XX_MISC3, misc3);
+ }
+}
+
+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));
+ ricoh_set_clkrun(socket, true);
+}
+
+
+/*
+ * 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);
+ ricoh_set_clkrun(socket, false);
+
+ return 0;
+}
+
+#endif /* CONFIG_CARDBUS */
+
+#endif /* _LINUX_RICOH_H */
diff --git a/drivers/pcmcia/rsrc_iodyn.c b/drivers/pcmcia/rsrc_iodyn.c
new file mode 100644
index 000000000..b04b16496
--- /dev/null
+++ b/drivers/pcmcia/rsrc_iodyn.c
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * rsrc_iodyn.c -- Resource management routines for MEM-static sockets.
+ *
+ * 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/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <pcmcia/ss.h>
+#include <pcmcia/cistpl.h>
+#include "cs_internal.h"
+
+
+struct pcmcia_align_data {
+ unsigned long mask;
+ unsigned long offset;
+};
+
+static resource_size_t pcmcia_align(void *align_data,
+ const struct resource *res,
+ resource_size_t size, resource_size_t align)
+{
+ struct pcmcia_align_data *data = align_data;
+ resource_size_t start;
+
+ start = (res->start & ~data->mask) + data->offset;
+ if (start < res->start)
+ start += data->mask + 1;
+
+#ifdef CONFIG_X86
+ if (res->flags & IORESOURCE_IO) {
+ if (start & 0x300)
+ start = (start + 0x3ff) & ~0x3ff;
+ }
+#endif
+
+#ifdef CONFIG_M68K
+ if (res->flags & IORESOURCE_IO) {
+ if ((res->start + size - 1) >= 1024)
+ start = res->end;
+ }
+#endif
+
+ return start;
+}
+
+
+static struct resource *__iodyn_find_io_region(struct pcmcia_socket *s,
+ unsigned long base, int num,
+ unsigned long align)
+{
+ struct resource *res = pcmcia_make_resource(0, num, IORESOURCE_IO,
+ dev_name(&s->dev));
+ struct pcmcia_align_data data;
+ unsigned long min = base;
+ int ret;
+
+ data.mask = align - 1;
+ data.offset = base & data.mask;
+
+#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);
+
+ if (ret != 0) {
+ kfree(res);
+ res = NULL;
+ }
+ return res;
+}
+
+static int iodyn_find_io(struct pcmcia_socket *s, unsigned int attr,
+ unsigned int *base, unsigned int num,
+ unsigned int align, struct resource **parent)
+{
+ int i, ret = 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].res)
+ continue;
+
+ if (!*base)
+ continue;
+
+ if ((s->io[i].res->start & (align-1)) == *base)
+ return -EBUSY;
+ }
+
+ for (i = 0; i < MAX_IO_WIN; i++) {
+ struct resource *res = s->io[i].res;
+ unsigned int try;
+
+ if (res && (res->flags & IORESOURCE_BITS) !=
+ (attr & IORESOURCE_BITS))
+ continue;
+
+ if (!res) {
+ if (align == 0)
+ align = 0x10000;
+
+ res = s->io[i].res = __iodyn_find_io_region(s, *base,
+ num, align);
+ if (!res)
+ return -EINVAL;
+
+ *base = res->start;
+ s->io[i].res->flags =
+ ((res->flags & ~IORESOURCE_BITS) |
+ (attr & IORESOURCE_BITS));
+ s->io[i].InUse = num;
+ *parent = res;
+ return 0;
+ }
+
+ /* Try to extend top of window */
+ try = res->end + 1;
+ if ((*base == 0) || (*base == try)) {
+ if (adjust_resource(s->io[i].res, res->start,
+ resource_size(res) + num))
+ continue;
+ *base = try;
+ s->io[i].InUse += num;
+ *parent = res;
+ return 0;
+ }
+
+ /* Try to extend bottom of window */
+ try = res->start - num;
+ if ((*base == 0) || (*base == try)) {
+ if (adjust_resource(s->io[i].res,
+ res->start - num,
+ resource_size(res) + num))
+ continue;
+ *base = try;
+ s->io[i].InUse += num;
+ *parent = res;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+
+struct pccard_resource_ops pccard_iodyn_ops = {
+ .validate_mem = NULL,
+ .find_io = iodyn_find_io,
+ .find_mem = NULL,
+ .init = static_init,
+ .exit = NULL,
+};
+EXPORT_SYMBOL(pccard_iodyn_ops);
diff --git a/drivers/pcmcia/rsrc_mgr.c b/drivers/pcmcia/rsrc_mgr.c
new file mode 100644
index 000000000..252893216
--- /dev/null
+++ b/drivers/pcmcia/rsrc_mgr.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * rsrc_mgr.c -- Resource management routines and/or wrappers
+ *
+ * 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/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <pcmcia/ss.h>
+#include <pcmcia/cistpl.h>
+#include "cs_internal.h"
+
+int static_init(struct pcmcia_socket *s)
+{
+ /* the good thing about SS_CAP_STATIC_MAP sockets is
+ * that they don't need a resource database */
+
+ s->resource_setup_done = 1;
+
+ return 0;
+}
+
+struct resource *pcmcia_make_resource(resource_size_t start,
+ resource_size_t end,
+ unsigned long flags, const char *name)
+{
+ struct resource *res = kzalloc(sizeof(*res), GFP_KERNEL);
+
+ if (res) {
+ res->name = name;
+ res->start = start;
+ res->end = start + end - 1;
+ res->flags = flags;
+ }
+ return res;
+}
+
+static int static_find_io(struct pcmcia_socket *s, unsigned int attr,
+ unsigned int *base, unsigned int num,
+ unsigned int align, struct resource **parent)
+{
+ if (!s->io_offset)
+ return -EINVAL;
+ *base = s->io_offset | (*base & 0x0fff);
+ *parent = NULL;
+
+ return 0;
+}
+
+
+struct pccard_resource_ops pccard_static_ops = {
+ .validate_mem = NULL,
+ .find_io = static_find_io,
+ .find_mem = NULL,
+ .init = static_init,
+ .exit = NULL,
+};
+EXPORT_SYMBOL(pccard_static_ops);
+
+
+MODULE_AUTHOR("David A. Hinds, Dominik Brodowski");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("rsrc_nonstatic");
diff --git a/drivers/pcmcia/rsrc_nonstatic.c b/drivers/pcmcia/rsrc_nonstatic.c
new file mode 100644
index 000000000..6e90927e6
--- /dev/null
+++ b/drivers/pcmcia/rsrc_nonstatic.c
@@ -0,0 +1,1242 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * rsrc_nonstatic.c -- Resource management routines for !SS_CAP_STATIC_MAP sockets
+ *
+ * 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/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 <linux/io.h>
+
+#include <asm/irq.h>
+
+#include <pcmcia/ss.h>
+#include <pcmcia/cistpl.h>
+#include "cs_internal.h"
+
+/* moved to rsrc_mgr.c
+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 mem_db_valid;
+ struct resource_map io_db;
+};
+
+#define MEM_PROBE_LOW (1 << 0)
+#define MEM_PROBE_HIGH (1 << 1)
+
+/* Action field */
+#define REMOVE_MANAGED_RESOURCE 1
+#define ADD_MANAGED_RESOURCE 2
+
+/*======================================================================
+
+ Linux resource management extensions
+
+======================================================================*/
+
+static struct resource *
+claim_region(struct pcmcia_socket *s, resource_size_t base,
+ resource_size_t size, int type, char *name)
+{
+ struct resource *res, *parent;
+
+ parent = type & IORESOURCE_MEM ? &iomem_resource : &ioport_resource;
+ res = pcmcia_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 >= base)) {
+ p->num = max(num + base - p->base, p->num);
+ return 0;
+ }
+ if ((p->next == map) || (p->next->base > base+num-1))
+ break;
+ }
+ q = kmalloc(sizeof(struct resource_map), GFP_KERNEL);
+ if (!q) {
+ printk(KERN_WARNING "out of memory to update resources\n");
+ return -ENOMEM;
+ }
+ q->base = base; q->num = num;
+ q->next = p->next; p->next = q;
+ return 0;
+}
+
+/*====================================================================*/
+
+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) {
+ printk(KERN_WARNING "out of memory to update resources\n");
+ return -ENOMEM;
+ }
+ 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 0;
+}
+
+/*======================================================================
+
+ 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, unsigned int base,
+ unsigned int num)
+{
+ struct resource *res;
+ struct socket_data *s_data = s->resource_data;
+ unsigned int i, j, bad;
+ int any;
+ u_char *b, hole, most;
+
+ dev_info(&s->dev, "cs: IO port probe %#x-%#x:", base, base+num-1);
+
+ /* First, what does a floating port look like? */
+ b = kzalloc(256, GFP_KERNEL);
+ if (!b) {
+ pr_cont("\n");
+ dev_err(&s->dev, "do_io_probe: unable to kmalloc 256 bytes\n");
+ return;
+ }
+ for (i = base, most = 0; i < base+num; i += 8) {
+ res = claim_region(s, i, 8, IORESOURCE_IO, "PCMCIA ioprobe");
+ 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(s, i, 8, IORESOURCE_IO, "PCMCIA ioprobe");
+ if (!res) {
+ if (!any)
+ pr_cont(" excluding");
+ if (!bad)
+ bad = any = i;
+ continue;
+ }
+ for (j = 0; j < 8; j++)
+ if (inb(i+j) != most)
+ break;
+ free_region(res);
+ if (j < 8) {
+ if (!any)
+ pr_cont(" excluding");
+ if (!bad)
+ bad = any = i;
+ } else {
+ if (bad) {
+ sub_interval(&s_data->io_db, bad, i-bad);
+ pr_cont(" %#x-%#x", bad, i-1);
+ bad = 0;
+ }
+ }
+ }
+ if (bad) {
+ if ((num > 16) && (bad == base) && (i == base+num)) {
+ sub_interval(&s_data->io_db, bad, i-bad);
+ pr_cont(" nothing: probe failed.\n");
+ return;
+ } else {
+ sub_interval(&s_data->io_db, bad, i-bad);
+ pr_cont(" %#x-%#x", bad, i-1);
+ }
+ }
+
+ pr_cont("%s\n", !any ? " clean" : "");
+}
+#endif
+
+/*======================================================================*/
+
+/**
+ * readable() - iomem validation function for cards with a valid CIS
+ */
+static int readable(struct pcmcia_socket *s, struct resource *res,
+ unsigned int *count)
+{
+ int ret = -EINVAL;
+
+ if (s->fake_cis) {
+ dev_dbg(&s->dev, "fake CIS is being used: can't validate mem\n");
+ return 0;
+ }
+
+ s->cis_mem.res = res;
+ s->cis_virt = ioremap(res->start, s->map_size);
+ if (s->cis_virt) {
+ mutex_unlock(&s->ops_mutex);
+ /* as we're only called from pcmcia.c, we're safe */
+ if (s->callback->validate)
+ ret = s->callback->validate(s, count);
+ /* invalidate mapping */
+ mutex_lock(&s->ops_mutex);
+ iounmap(s->cis_virt);
+ s->cis_virt = NULL;
+ }
+ s->cis_mem.res = NULL;
+ if ((ret) || (*count == 0))
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * checksum() - iomem validation function for simple memory cards
+ */
+static int checksum(struct pcmcia_socket *s, struct resource *res,
+ unsigned int *value)
+{
+ 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);
+ }
+
+ if (b == -1)
+ return -EINVAL;
+
+ *value = a;
+
+ return 0;
+}
+
+/**
+ * do_validate_mem() - low level validate a memory region for PCMCIA use
+ * @s: PCMCIA socket to validate
+ * @base: start address of resource to check
+ * @size: size of resource to check
+ * @validate: validation function to use
+ *
+ * do_validate_mem() splits up the memory region which is to be checked
+ * into two parts. Both are passed to the @validate() function. If
+ * @validate() returns non-zero, or the value parameter to @validate()
+ * is zero, or the value parameter is different between both calls,
+ * the check fails, and -EINVAL is returned. Else, 0 is returned.
+ */
+static int do_validate_mem(struct pcmcia_socket *s,
+ unsigned long base, unsigned long size,
+ int validate (struct pcmcia_socket *s,
+ struct resource *res,
+ unsigned int *value))
+{
+ struct socket_data *s_data = s->resource_data;
+ struct resource *res1, *res2;
+ unsigned int info1 = 1, info2 = 1;
+ int ret = -EINVAL;
+
+ res1 = claim_region(s, base, size/2, IORESOURCE_MEM, "PCMCIA memprobe");
+ res2 = claim_region(s, base + size/2, size/2, IORESOURCE_MEM,
+ "PCMCIA memprobe");
+
+ if (res1 && res2) {
+ ret = 0;
+ if (validate) {
+ ret = validate(s, res1, &info1);
+ ret += validate(s, res2, &info2);
+ }
+ }
+
+ dev_dbg(&s->dev, "cs: memory probe 0x%06lx-0x%06lx: %pr %pr %u %u %u",
+ base, base+size-1, res1, res2, ret, info1, info2);
+
+ free_region(res2);
+ free_region(res1);
+
+ if ((ret) || (info1 != info2) || (info1 == 0))
+ return -EINVAL;
+
+ if (validate && !s->fake_cis) {
+ /* move it to the validated data set */
+ add_interval(&s_data->mem_db_valid, base, size);
+ sub_interval(&s_data->mem_db, base, size);
+ }
+
+ return 0;
+}
+
+
+/**
+ * do_mem_probe() - validate a memory region for PCMCIA use
+ * @s: PCMCIA socket to validate
+ * @base: start address of resource to check
+ * @num: size of resource to check
+ * @validate: validation function to use
+ * @fallback: validation function to use if validate fails
+ *
+ * do_mem_probe() checks a memory region for use by the PCMCIA subsystem.
+ * To do so, the area is split up into sensible parts, and then passed
+ * into the @validate() function. Only if @validate() and @fallback() fail,
+ * the area is marked as unavaibale for use by the PCMCIA subsystem. The
+ * function returns the size of the usable memory area.
+ */
+static int do_mem_probe(struct pcmcia_socket *s, u_long base, u_long num,
+ int validate (struct pcmcia_socket *s,
+ struct resource *res,
+ unsigned int *value),
+ int fallback (struct pcmcia_socket *s,
+ struct resource *res,
+ unsigned int *value))
+{
+ struct socket_data *s_data = s->resource_data;
+ u_long i, j, bad, fail, step;
+
+ dev_info(&s->dev, "cs: memory probe 0x%06lx-0x%06lx:",
+ base, base+num-1);
+ bad = fail = 0;
+ step = (num < 0x20000) ? 0x2000 : ((num>>4) & ~0x1fff);
+ /* don't allow too large steps */
+ if (step > 0x800000)
+ step = 0x800000;
+ /* 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 (!do_validate_mem(s, j, step, validate))
+ break;
+ }
+ fail = ((i == base) && (j == base+num));
+ }
+ if ((fail) && (fallback)) {
+ for (j = i; j < base+num; j += step)
+ if (!do_validate_mem(s, j, step, fallback))
+ break;
+ }
+ if (i != j) {
+ if (!bad)
+ pr_cont(" excluding");
+ pr_cont(" %#05lx-%#05lx", i, j-1);
+ sub_interval(&s_data->mem_db, i, j-i);
+ bad += j-i;
+ }
+ }
+ pr_cont("%s\n", !bad ? " clean" : "");
+ return num - bad;
+}
+
+
+#ifdef CONFIG_PCMCIA_PROBE
+
+/**
+ * inv_probe() - top-to-bottom search for one usuable high memory area
+ * @s: PCMCIA socket to validate
+ * @m: resource_map to check
+ */
+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(s, m->base, m->num, readable, checksum);
+}
+
+/**
+ * validate_mem() - memory probe function
+ * @s: PCMCIA socket to validate
+ * @probe_mask: MEM_PROBE_LOW | MEM_PROBE_HIGH
+ *
+ * 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. Returns 0 on usuable ports.
+ */
+static int validate_mem(struct pcmcia_socket *s, unsigned int probe_mask)
+{
+ struct resource_map *m, mm;
+ static unsigned char order[] = { 0xd0, 0xe0, 0xc0, 0xf0 };
+ unsigned 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 0;
+ if (s_data->mem_db_valid.next != &s_data->mem_db_valid)
+ return 0;
+ dev_notice(&s->dev,
+ "cs: warning: no high memory space available!\n");
+ return -ENODEV;
+ }
+
+ 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(s, mm.base, mm.num, readable,
+ checksum);
+ 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(s, b, 0x10000,
+ readable, checksum);
+ }
+ }
+ }
+
+ if (ok > 0)
+ return 0;
+
+ return -ENODEV;
+}
+
+#else /* CONFIG_PCMCIA_PROBE */
+
+/**
+ * validate_mem() - memory probe function
+ * @s: PCMCIA socket to validate
+ * @probe_mask: ignored
+ *
+ * Returns 0 on usuable ports.
+ */
+static int validate_mem(struct pcmcia_socket *s, unsigned int probe_mask)
+{
+ struct resource_map *m, mm;
+ struct socket_data *s_data = s->resource_data;
+ unsigned long ok = 0;
+
+ for (m = s_data->mem_db.next; m != &s_data->mem_db; m = mm.next) {
+ mm = *m;
+ ok += do_mem_probe(s, mm.base, mm.num, readable, checksum);
+ }
+ if (ok > 0)
+ return 0;
+ return -ENODEV;
+}
+
+#endif /* CONFIG_PCMCIA_PROBE */
+
+
+/**
+ * pcmcia_nonstatic_validate_mem() - try to validate iomem for PCMCIA use
+ * @s: PCMCIA socket to validate
+ *
+ * This is tricky... when we set up CIS memory, we try to validate
+ * the memory window space allocations.
+ *
+ * Locking note: Must be called with skt_mutex held!
+ */
+static int pcmcia_nonstatic_validate_mem(struct pcmcia_socket *s)
+{
+ struct socket_data *s_data = s->resource_data;
+ unsigned int probe_mask = MEM_PROBE_LOW;
+ int ret;
+
+ if (!probe_mem || !(s->state & SOCKET_PRESENT))
+ return 0;
+
+ if (s->features & SS_CAP_PAGE_REGS)
+ probe_mask = MEM_PROBE_HIGH;
+
+ ret = validate_mem(s, probe_mask);
+
+ if (s_data->mem_db_valid.next != &s_data->mem_db_valid)
+ return 0;
+
+ return ret;
+}
+
+struct pcmcia_align_data {
+ unsigned long mask;
+ unsigned long offset;
+ struct resource_map *map;
+};
+
+static resource_size_t pcmcia_common_align(struct pcmcia_align_data *align_data,
+ resource_size_t start)
+{
+ resource_size_t ret;
+ /*
+ * Ensure that we have the correct start address
+ */
+ ret = (start & ~align_data->mask) + align_data->offset;
+ if (ret < start)
+ ret += align_data->mask + 1;
+ return ret;
+}
+
+static resource_size_t
+pcmcia_align(void *align_data, const struct resource *res,
+ resource_size_t size, resource_size_t align)
+{
+ struct pcmcia_align_data *data = align_data;
+ struct resource_map *m;
+ resource_size_t start;
+
+ start = pcmcia_common_align(data, res->start);
+
+ for (m = data->map->next; m != data->map; m = m->next) {
+ unsigned long map_start = m->base;
+ unsigned long map_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 (start < map_start)
+ start = pcmcia_common_align(data, map_start);
+
+ /*
+ * If we're above the area which was passed in, there's
+ * no point proceeding.
+ */
+ if (start >= res->end)
+ break;
+
+ if ((start + size - 1) <= map_end)
+ break;
+ }
+
+ /*
+ * If we failed to find something suitable, ensure we fail.
+ */
+ if (m == data->map)
+ start = res->end;
+
+ return start;
+}
+
+/*
+ * 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 pcmcia_socket *s,
+ unsigned long r_start,
+ unsigned long r_end)
+{
+ struct resource_map *m;
+ struct socket_data *s_data = s->resource_data;
+ int ret = -ENOMEM;
+
+ 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 = 0;
+ }
+
+ 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'.
+
+======================================================================*/
+
+static struct resource *__nonstatic_find_io_region(struct pcmcia_socket *s,
+ unsigned long base, int num,
+ unsigned long align)
+{
+ struct resource *res = pcmcia_make_resource(0, num, IORESOURCE_IO,
+ dev_name(&s->dev));
+ struct socket_data *s_data = s->resource_data;
+ struct pcmcia_align_data data;
+ unsigned long min = base;
+ int ret;
+
+ if (!res)
+ return NULL;
+
+ data.mask = align - 1;
+ data.offset = base & data.mask;
+ data.map = &s_data->io_db;
+
+#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);
+
+ if (ret != 0) {
+ kfree(res);
+ res = NULL;
+ }
+ return res;
+}
+
+static int nonstatic_find_io(struct pcmcia_socket *s, unsigned int attr,
+ unsigned int *base, unsigned int num,
+ unsigned int align, struct resource **parent)
+{
+ int i, ret = 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].res)
+ continue;
+
+ if (!*base)
+ continue;
+
+ if ((s->io[i].res->start & (align-1)) == *base)
+ return -EBUSY;
+ }
+
+ for (i = 0; i < MAX_IO_WIN; i++) {
+ struct resource *res = s->io[i].res;
+ unsigned int try;
+
+ if (res && (res->flags & IORESOURCE_BITS) !=
+ (attr & IORESOURCE_BITS))
+ continue;
+
+ if (!res) {
+ if (align == 0)
+ align = 0x10000;
+
+ res = s->io[i].res = __nonstatic_find_io_region(s,
+ *base, num,
+ align);
+ if (!res)
+ return -EINVAL;
+
+ *base = res->start;
+ s->io[i].res->flags =
+ ((res->flags & ~IORESOURCE_BITS) |
+ (attr & IORESOURCE_BITS));
+ s->io[i].InUse = num;
+ *parent = res;
+ return 0;
+ }
+
+ /* Try to extend top of window */
+ try = res->end + 1;
+ if ((*base == 0) || (*base == try)) {
+ ret = __nonstatic_adjust_io_region(s, res->start,
+ res->end + num);
+ if (!ret) {
+ ret = adjust_resource(s->io[i].res, res->start,
+ resource_size(res) + num);
+ if (ret)
+ continue;
+ *base = try;
+ s->io[i].InUse += num;
+ *parent = res;
+ return 0;
+ }
+ }
+
+ /* Try to extend bottom of window */
+ try = res->start - num;
+ if ((*base == 0) || (*base == try)) {
+ ret = __nonstatic_adjust_io_region(s,
+ res->start - num,
+ res->end);
+ if (!ret) {
+ ret = adjust_resource(s->io[i].res,
+ res->start - num,
+ resource_size(res) + num);
+ if (ret)
+ continue;
+ *base = try;
+ s->io[i].InUse += num;
+ *parent = res;
+ return 0;
+ }
+ }
+ }
+
+ return -EINVAL;
+}
+
+
+static struct resource *nonstatic_find_mem_region(u_long base, u_long num,
+ u_long align, int low, struct pcmcia_socket *s)
+{
+ struct resource *res = pcmcia_make_resource(0, num, IORESOURCE_MEM,
+ dev_name(&s->dev));
+ struct socket_data *s_data = s->resource_data;
+ struct pcmcia_align_data data;
+ unsigned long min, max;
+ int ret, i, j;
+
+ if (!res)
+ return NULL;
+
+ low = low || !(s->features & SS_CAP_PAGE_REGS);
+
+ data.mask = align - 1;
+ data.offset = base & data.mask;
+
+ for (i = 0; i < 2; i++) {
+ data.map = &s_data->mem_db_valid;
+ if (low) {
+ max = 0x100000UL;
+ min = base < max ? base : 0;
+ } else {
+ max = ~0UL;
+ min = 0x100000UL + base;
+ }
+
+ for (j = 0; j < 2; j++) {
+#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);
+ }
+ if (ret == 0)
+ break;
+ data.map = &s_data->mem_db;
+ }
+ if (ret == 0 || low)
+ break;
+ low = 1;
+ }
+
+ if (ret != 0) {
+ kfree(res);
+ res = NULL;
+ }
+ return res;
+}
+
+
+static int adjust_memory(struct pcmcia_socket *s, unsigned int action, unsigned long start, unsigned long end)
+{
+ struct socket_data *data = s->resource_data;
+ unsigned long size = end - start + 1;
+ int ret = 0;
+
+ if (end < start)
+ return -EINVAL;
+
+ switch (action) {
+ case ADD_MANAGED_RESOURCE:
+ ret = add_interval(&data->mem_db, start, size);
+ if (!ret)
+ do_mem_probe(s, start, size, NULL, NULL);
+ break;
+ case REMOVE_MANAGED_RESOURCE:
+ ret = sub_interval(&data->mem_db, start, size);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+
+static int adjust_io(struct pcmcia_socket *s, unsigned int action, unsigned long start, unsigned long end)
+{
+ struct socket_data *data = s->resource_data;
+ unsigned long size;
+ int ret = 0;
+
+#if defined(CONFIG_X86)
+ /* on x86, avoid anything < 0x100 for it is often used for
+ * legacy platform devices */
+ if (start < 0x100)
+ start = 0x100;
+#endif
+
+ size = end - start + 1;
+
+ if (end < start)
+ return -EINVAL;
+
+ if (end > IO_SPACE_LIMIT)
+ return -EINVAL;
+
+ switch (action) {
+ case ADD_MANAGED_RESOURCE:
+ if (add_interval(&data->io_db, start, size) != 0) {
+ ret = -EBUSY;
+ break;
+ }
+#ifdef CONFIG_PCMCIA_PROBE
+ if (probe_io)
+ do_io_probe(s, start, size);
+#endif
+ break;
+ case REMOVE_MANAGED_RESOURCE:
+ sub_interval(&data->io_db, start, size);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+
+#ifdef CONFIG_PCI
+static int nonstatic_autoadd_resources(struct pcmcia_socket *s)
+{
+ struct resource *res;
+ int i, done = 0;
+
+ if (!s->cb_dev || !s->cb_dev->bus)
+ return -ENODEV;
+
+#if defined(CONFIG_X86)
+ /* If this is the root bus, the risk of hitting some strange
+ * system devices is too high: If a driver isn't loaded, the
+ * resources are not claimed; even if a driver is loaded, it
+ * may not request all resources or even the wrong one. We
+ * can neither trust the rest of the kernel nor ACPI/PNP and
+ * CRS parsing to get it right. Therefore, use several
+ * safeguards:
+ *
+ * - Do not auto-add resources if the CardBus bridge is on
+ * the PCI root bus
+ *
+ * - Avoid any I/O ports < 0x100.
+ *
+ * - On PCI-PCI bridges, only use resources which are set up
+ * exclusively for the secondary PCI bus: the risk of hitting
+ * system devices is quite low, as they usually aren't
+ * connected to the secondary PCI bus.
+ */
+ if (s->cb_dev->bus->number == 0)
+ return -EINVAL;
+
+ for (i = 0; i < PCI_BRIDGE_RESOURCE_NUM; i++) {
+ res = s->cb_dev->bus->resource[i];
+#else
+ pci_bus_for_each_resource(s->cb_dev->bus, res, i) {
+#endif
+ if (!res)
+ continue;
+
+ if (res->flags & IORESOURCE_IO) {
+ /* safeguard against the root resource, where the
+ * risk of hitting any other device would be too
+ * high */
+ if (res == &ioport_resource)
+ continue;
+
+ dev_info(&s->cb_dev->dev,
+ "pcmcia: parent PCI bridge window: %pR\n",
+ res);
+ if (!adjust_io(s, ADD_MANAGED_RESOURCE, res->start, res->end))
+ done |= IORESOURCE_IO;
+
+ }
+
+ if (res->flags & IORESOURCE_MEM) {
+ /* safeguard against the root resource, where the
+ * risk of hitting any other device would be too
+ * high */
+ if (res == &iomem_resource)
+ continue;
+
+ dev_info(&s->cb_dev->dev,
+ "pcmcia: parent PCI bridge window: %pR\n",
+ res);
+ if (!adjust_memory(s, ADD_MANAGED_RESOURCE, res->start, res->end))
+ done |= IORESOURCE_MEM;
+ }
+ }
+
+ /* if we got at least one of IO, and one of MEM, we can be glad and
+ * activate the PCMCIA subsystem */
+ if (done == (IORESOURCE_MEM | IORESOURCE_IO))
+ s->resource_setup_done = 1;
+
+ return 0;
+}
+
+#else
+
+static inline int nonstatic_autoadd_resources(struct pcmcia_socket *s)
+{
+ return -ENODEV;
+}
+
+#endif
+
+
+static int nonstatic_init(struct pcmcia_socket *s)
+{
+ struct socket_data *data;
+
+ data = kzalloc(sizeof(struct socket_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->mem_db.next = &data->mem_db;
+ data->mem_db_valid.next = &data->mem_db_valid;
+ data->io_db.next = &data->io_db;
+
+ s->resource_data = (void *) data;
+
+ nonstatic_autoadd_resources(s);
+
+ return 0;
+}
+
+static void nonstatic_release_resource_db(struct pcmcia_socket *s)
+{
+ struct socket_data *data = s->resource_data;
+ struct resource_map *p, *q;
+
+ for (p = data->mem_db_valid.next; p != &data->mem_db_valid; p = q) {
+ q = p->next;
+ kfree(p);
+ }
+ 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);
+ }
+
+ kfree(data);
+}
+
+
+struct pccard_resource_ops pccard_nonstatic_ops = {
+ .validate_mem = pcmcia_nonstatic_validate_mem,
+ .find_io = nonstatic_find_io,
+ .find_mem = nonstatic_find_mem_region,
+ .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 device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pcmcia_socket *s = dev_get_drvdata(dev);
+ struct socket_data *data;
+ struct resource_map *p;
+ ssize_t ret = 0;
+
+ mutex_lock(&s->ops_mutex);
+ data = s->resource_data;
+
+ for (p = data->io_db.next; p != &data->io_db; p = p->next) {
+ if (ret > (PAGE_SIZE - 10))
+ continue;
+ ret += scnprintf(&buf[ret], (PAGE_SIZE - ret - 1),
+ "0x%08lx - 0x%08lx\n",
+ ((unsigned long) p->base),
+ ((unsigned long) p->base + p->num - 1));
+ }
+
+ mutex_unlock(&s->ops_mutex);
+ return ret;
+}
+
+static ssize_t store_io_db(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pcmcia_socket *s = dev_get_drvdata(dev);
+ unsigned long start_addr, end_addr;
+ unsigned int add = ADD_MANAGED_RESOURCE;
+ 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 = REMOVE_MANAGED_RESOURCE;
+ if (ret != 2) {
+ ret = sscanf(buf, "0x%lx - 0x%lx", &start_addr,
+ &end_addr);
+ add = ADD_MANAGED_RESOURCE;
+ if (ret != 2)
+ return -EINVAL;
+ }
+ }
+ if (end_addr < start_addr)
+ return -EINVAL;
+
+ mutex_lock(&s->ops_mutex);
+ ret = adjust_io(s, add, start_addr, end_addr);
+ mutex_unlock(&s->ops_mutex);
+
+ return ret ? ret : count;
+}
+static DEVICE_ATTR(available_resources_io, 0600, show_io_db, store_io_db);
+
+static ssize_t show_mem_db(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct pcmcia_socket *s = dev_get_drvdata(dev);
+ struct socket_data *data;
+ struct resource_map *p;
+ ssize_t ret = 0;
+
+ mutex_lock(&s->ops_mutex);
+ data = s->resource_data;
+
+ for (p = data->mem_db_valid.next; p != &data->mem_db_valid;
+ p = p->next) {
+ if (ret > (PAGE_SIZE - 10))
+ continue;
+ ret += scnprintf(&buf[ret], (PAGE_SIZE - ret - 1),
+ "0x%08lx - 0x%08lx\n",
+ ((unsigned long) p->base),
+ ((unsigned long) p->base + p->num - 1));
+ }
+
+ for (p = data->mem_db.next; p != &data->mem_db; p = p->next) {
+ if (ret > (PAGE_SIZE - 10))
+ continue;
+ ret += scnprintf(&buf[ret], (PAGE_SIZE - ret - 1),
+ "0x%08lx - 0x%08lx\n",
+ ((unsigned long) p->base),
+ ((unsigned long) p->base + p->num - 1));
+ }
+
+ mutex_unlock(&s->ops_mutex);
+ return ret;
+}
+
+static ssize_t store_mem_db(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pcmcia_socket *s = dev_get_drvdata(dev);
+ unsigned long start_addr, end_addr;
+ unsigned int add = ADD_MANAGED_RESOURCE;
+ 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 = REMOVE_MANAGED_RESOURCE;
+ if (ret != 2) {
+ ret = sscanf(buf, "0x%lx - 0x%lx", &start_addr,
+ &end_addr);
+ add = ADD_MANAGED_RESOURCE;
+ if (ret != 2)
+ return -EINVAL;
+ }
+ }
+ if (end_addr < start_addr)
+ return -EINVAL;
+
+ mutex_lock(&s->ops_mutex);
+ ret = adjust_memory(s, add, start_addr, end_addr);
+ mutex_unlock(&s->ops_mutex);
+
+ return ret ? ret : count;
+}
+static DEVICE_ATTR(available_resources_mem, 0600, show_mem_db, store_mem_db);
+
+static struct attribute *pccard_rsrc_attributes[] = {
+ &dev_attr_available_resources_io.attr,
+ &dev_attr_available_resources_mem.attr,
+ NULL,
+};
+
+static const struct attribute_group rsrc_attributes = {
+ .attrs = pccard_rsrc_attributes,
+};
+
+static int pccard_sysfs_add_rsrc(struct device *dev,
+ struct class_interface *class_intf)
+{
+ struct pcmcia_socket *s = dev_get_drvdata(dev);
+
+ if (s->resource_ops != &pccard_nonstatic_ops)
+ return 0;
+ return sysfs_create_group(&dev->kobj, &rsrc_attributes);
+}
+
+static void pccard_sysfs_remove_rsrc(struct device *dev,
+ struct class_interface *class_intf)
+{
+ struct pcmcia_socket *s = dev_get_drvdata(dev);
+
+ if (s->resource_ops != &pccard_nonstatic_ops)
+ return;
+ sysfs_remove_group(&dev->kobj, &rsrc_attributes);
+}
+
+static struct class_interface pccard_rsrc_interface __refdata = {
+ .class = &pcmcia_socket_class,
+ .add_dev = &pccard_sysfs_add_rsrc,
+ .remove_dev = &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_generic.c b/drivers/pcmcia/sa1100_generic.c
new file mode 100644
index 000000000..47b060c57
--- /dev/null
+++ b/drivers/pcmcia/sa1100_generic.c
@@ -0,0 +1,216 @@
+/*======================================================================
+
+ 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/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+#include <pcmcia/ss.h>
+
+#include <asm/hardware/scoop.h>
+
+#include "sa1100_generic.h"
+
+static const char *sa11x0_cf_gpio_names[] = {
+ [SOC_STAT_CD] = "detect",
+ [SOC_STAT_BVD1] = "bvd1",
+ [SOC_STAT_BVD2] = "bvd2",
+ [SOC_STAT_RDY] = "ready",
+};
+
+static int sa11x0_cf_hw_init(struct soc_pcmcia_socket *skt)
+{
+ struct device *dev = skt->socket.dev.parent;
+ int i;
+
+ skt->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(skt->gpio_reset))
+ return PTR_ERR(skt->gpio_reset);
+
+ skt->gpio_bus_enable = devm_gpiod_get_optional(dev, "bus-enable",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(skt->gpio_bus_enable))
+ return PTR_ERR(skt->gpio_bus_enable);
+
+ skt->vcc.reg = devm_regulator_get_optional(dev, "vcc");
+ if (IS_ERR(skt->vcc.reg))
+ return PTR_ERR(skt->vcc.reg);
+
+ if (!skt->vcc.reg)
+ dev_warn(dev,
+ "no Vcc regulator provided, ignoring Vcc controls\n");
+
+ for (i = 0; i < ARRAY_SIZE(sa11x0_cf_gpio_names); i++) {
+ skt->stat[i].name = sa11x0_cf_gpio_names[i];
+ skt->stat[i].desc = devm_gpiod_get_optional(dev,
+ sa11x0_cf_gpio_names[i], GPIOD_IN);
+ if (IS_ERR(skt->stat[i].desc))
+ return PTR_ERR(skt->stat[i].desc);
+ }
+ return 0;
+}
+
+static int sa11x0_cf_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ return soc_pcmcia_regulator_set(skt, &skt->vcc, state->Vcc);
+}
+
+static struct pcmcia_low_level sa11x0_cf_ops = {
+ .owner = THIS_MODULE,
+ .hw_init = sa11x0_cf_hw_init,
+ .socket_state = soc_common_cf_socket_state,
+ .configure_socket = sa11x0_cf_configure_socket,
+};
+
+int __init pcmcia_collie_init(struct device *dev);
+
+static int (*sa11x0_pcmcia_legacy_hw_init[])(struct device *dev) = {
+#if defined(CONFIG_SA1100_H3100) || defined(CONFIG_SA1100_H3600)
+ pcmcia_h3600_init,
+#endif
+#ifdef CONFIG_SA1100_SIMPAD
+ pcmcia_simpad_init,
+#endif
+#ifdef CONFIG_SA1100_COLLIE
+ pcmcia_collie_init,
+#endif
+};
+
+static int sa11x0_drv_pcmcia_legacy_probe(struct platform_device *dev)
+{
+ int i, ret = -ENODEV;
+
+ /*
+ * Initialise any "on-board" PCMCIA sockets.
+ */
+ for (i = 0; i < ARRAY_SIZE(sa11x0_pcmcia_legacy_hw_init); i++) {
+ ret = sa11x0_pcmcia_legacy_hw_init[i](&dev->dev);
+ if (ret == 0)
+ break;
+ }
+
+ return ret;
+}
+
+static int sa11x0_drv_pcmcia_legacy_remove(struct platform_device *dev)
+{
+ struct skt_dev_info *sinfo = platform_get_drvdata(dev);
+ int i;
+
+ platform_set_drvdata(dev, NULL);
+
+ for (i = 0; i < sinfo->nskt; i++)
+ soc_pcmcia_remove_one(&sinfo->skt[i]);
+
+ return 0;
+}
+
+static int sa11x0_drv_pcmcia_probe(struct platform_device *pdev)
+{
+ struct soc_pcmcia_socket *skt;
+ struct device *dev = &pdev->dev;
+
+ if (pdev->id == -1)
+ return sa11x0_drv_pcmcia_legacy_probe(pdev);
+
+ skt = devm_kzalloc(dev, sizeof(*skt), GFP_KERNEL);
+ if (!skt)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, skt);
+
+ skt->nr = pdev->id;
+ skt->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(skt->clk))
+ return PTR_ERR(skt->clk);
+
+ sa11xx_drv_pcmcia_ops(&sa11x0_cf_ops);
+ soc_pcmcia_init_one(skt, &sa11x0_cf_ops, dev);
+
+ return sa11xx_drv_pcmcia_add_one(skt);
+}
+
+static int sa11x0_drv_pcmcia_remove(struct platform_device *dev)
+{
+ struct soc_pcmcia_socket *skt;
+
+ if (dev->id == -1)
+ return sa11x0_drv_pcmcia_legacy_remove(dev);
+
+ skt = platform_get_drvdata(dev);
+
+ soc_pcmcia_remove_one(skt);
+
+ return 0;
+}
+
+static struct platform_driver sa11x0_pcmcia_driver = {
+ .driver = {
+ .name = "sa11x0-pcmcia",
+ },
+ .probe = sa11x0_drv_pcmcia_probe,
+ .remove = sa11x0_drv_pcmcia_remove,
+};
+
+/* 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 platform_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)
+{
+ platform_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");
+
+fs_initcall(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 000000000..7b7cdcd20
--- /dev/null
+++ b/drivers/pcmcia/sa1100_generic.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#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_badge4_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_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 000000000..a91222bc3
--- /dev/null
+++ b/drivers/pcmcia/sa1100_h3600.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * drivers/pcmcia/sa1100_h3600.c
+ *
+ * PCMCIA implementation routines for H3600
+ *
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+
+#include <mach/hardware.h>
+#include <asm/irq.h>
+#include <asm/mach-types.h>
+#include <mach/h3xxx.h>
+
+#include "sa1100_generic.h"
+
+static int h3600_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ int err;
+
+ skt->stat[SOC_STAT_CD].name = skt->nr ? "pcmcia1-detect" : "pcmcia0-detect";
+ skt->stat[SOC_STAT_RDY].name = skt->nr ? "pcmcia1-ready" : "pcmcia0-ready";
+
+ err = soc_pcmcia_request_gpiods(skt);
+ if (err)
+ return err;
+
+ switch (skt->nr) {
+ case 0:
+ err = gpio_request(H3XXX_EGPIO_OPT_NVRAM_ON, "OPT NVRAM ON");
+ if (err)
+ goto err01;
+ err = gpio_direction_output(H3XXX_EGPIO_OPT_NVRAM_ON, 0);
+ if (err)
+ goto err03;
+ err = gpio_request(H3XXX_EGPIO_OPT_ON, "OPT ON");
+ if (err)
+ goto err03;
+ err = gpio_direction_output(H3XXX_EGPIO_OPT_ON, 0);
+ if (err)
+ goto err04;
+ err = gpio_request(H3XXX_EGPIO_OPT_RESET, "OPT RESET");
+ if (err)
+ goto err04;
+ err = gpio_direction_output(H3XXX_EGPIO_OPT_RESET, 0);
+ if (err)
+ goto err05;
+ err = gpio_request(H3XXX_EGPIO_CARD_RESET, "PCMCIA CARD RESET");
+ if (err)
+ goto err05;
+ err = gpio_direction_output(H3XXX_EGPIO_CARD_RESET, 0);
+ if (err)
+ goto err06;
+ break;
+ case 1:
+ break;
+ }
+ return 0;
+
+err06: gpio_free(H3XXX_EGPIO_CARD_RESET);
+err05: gpio_free(H3XXX_EGPIO_OPT_RESET);
+err04: gpio_free(H3XXX_EGPIO_OPT_ON);
+err03: gpio_free(H3XXX_EGPIO_OPT_NVRAM_ON);
+err01: gpio_free(H3XXX_GPIO_PCMCIA_IRQ0);
+ return err;
+}
+
+static void h3600_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
+{
+ switch (skt->nr) {
+ case 0:
+ /* Disable CF bus: */
+ gpio_set_value(H3XXX_EGPIO_OPT_NVRAM_ON, 0);
+ gpio_set_value(H3XXX_EGPIO_OPT_ON, 0);
+ gpio_set_value(H3XXX_EGPIO_OPT_RESET, 1);
+
+ gpio_free(H3XXX_EGPIO_CARD_RESET);
+ gpio_free(H3XXX_EGPIO_OPT_RESET);
+ gpio_free(H3XXX_EGPIO_OPT_ON);
+ gpio_free(H3XXX_EGPIO_OPT_NVRAM_ON);
+ break;
+ case 1:
+ break;
+ }
+}
+
+static void
+h3600_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state)
+{
+ state->bvd1 = 0;
+ state->bvd2 = 0;
+ state->vs_3v = 0;
+ state->vs_Xv = 0;
+}
+
+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;
+ }
+
+ gpio_set_value(H3XXX_EGPIO_CARD_RESET, !!(state->flags & SS_RESET));
+
+ /* Silently ignore Vpp, output enable, speaker enable. */
+
+ return 0;
+}
+
+static void h3600_pcmcia_socket_init(struct soc_pcmcia_socket *skt)
+{
+ /* Enable CF bus: */
+ gpio_set_value(H3XXX_EGPIO_OPT_NVRAM_ON, 1);
+ gpio_set_value(H3XXX_EGPIO_OPT_ON, 1);
+ gpio_set_value(H3XXX_EGPIO_OPT_RESET, 0);
+
+ msleep(10);
+}
+
+static void h3600_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt)
+{
+ /*
+ * 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) {
+ gpio_set_value(H3XXX_EGPIO_OPT_ON, 0);
+ gpio_set_value(H3XXX_EGPIO_OPT_NVRAM_ON, 0);
+ /* hmm, does this suck power? */
+ gpio_set_value(H3XXX_EGPIO_OPT_RESET, 1);
+ }
+}
+
+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 pcmcia_h3600_init(struct device *dev)
+{
+ int ret = -ENODEV;
+
+ if (machine_is_h3600() || machine_is_h3100())
+ ret = sa11xx_drv_pcmcia_probe(dev, &h3600_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 000000000..784ada5b8
--- /dev/null
+++ b/drivers/pcmcia/sa1100_simpad.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * drivers/pcmcia/sa1100_simpad.c
+ *
+ * PCMCIA implementation routines for simpad
+ *
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/init.h>
+
+#include <mach/hardware.h>
+#include <asm/mach-types.h>
+#include <mach/simpad.h>
+#include "sa1100_generic.h"
+
+static int simpad_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+
+ simpad_clear_cs3_bit(VCC_3V_EN|VCC_5V_EN|EN0|EN1);
+
+ skt->stat[SOC_STAT_CD].name = "cf-detect";
+ skt->stat[SOC_STAT_RDY].name = "cf-ready";
+
+ return soc_pcmcia_request_gpiods(skt);
+}
+
+static void simpad_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
+{
+ /* Disable CF bus: */
+ /*simpad_set_cs3_bit(PCMCIA_BUFF_DIS);*/
+ simpad_clear_cs3_bit(PCMCIA_RESET);
+}
+
+static void
+simpad_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ long cs3reg = simpad_get_cs3_ro();
+
+ /* bvd1 might be cs3reg & PCMCIA_BVD1 */
+ /* bvd2 might be cs3reg & PCMCIA_BVD2 */
+
+ if ((cs3reg & (PCMCIA_VS1|PCMCIA_VS2)) ==
+ (PCMCIA_VS1|PCMCIA_VS2)) {
+ 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:
+ simpad_clear_cs3_bit(VCC_3V_EN|VCC_5V_EN|EN0|EN1);
+ break;
+
+ case 33:
+ simpad_clear_cs3_bit(VCC_3V_EN|EN1);
+ simpad_set_cs3_bit(VCC_5V_EN|EN0);
+ break;
+
+ case 50:
+ simpad_clear_cs3_bit(VCC_5V_EN|EN1);
+ simpad_set_cs3_bit(VCC_3V_EN|EN0);
+ break;
+
+ default:
+ printk(KERN_ERR "%s(): unrecognized Vcc %u\n",
+ __func__, state->Vcc);
+ simpad_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_suspend(struct soc_pcmcia_socket *skt)
+{
+ simpad_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_suspend = simpad_pcmcia_socket_suspend,
+};
+
+int 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_badge4.c b/drivers/pcmcia/sa1111_badge4.c
new file mode 100644
index 000000000..e76d5ba92
--- /dev/null
+++ b/drivers/pcmcia/sa1111_badge4.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/sa1100_badge4.c
+ *
+ * BadgePAD 4 PCMCIA specific routines
+ *
+ * Christopher Hoover <ch@hpl.hp.com>
+ *
+ * Copyright (C) 2002 Hewlett-Packard Company
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+
+#include <mach/hardware.h>
+#include <asm/mach-types.h>
+#include <mach/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(__func__, "pcmvcc",
+ badge4_pcmvcc, state->Vcc);
+ // Apply power regardless of the jumpering.
+ // return -1;
+ }
+ if ((state->Vpp != 0) &&
+ (state->Vpp != badge4_pcmvpp)) {
+ complain_about_jumpering(__func__, "pcmvpp",
+ badge4_pcmvpp, state->Vpp);
+ return -1;
+ }
+ break;
+
+ case 1:
+ if ((state->Vcc != 0) &&
+ (state->Vcc != badge4_cfvcc)) {
+ complain_about_jumpering(__func__, "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 ret;
+}
+
+static struct pcmcia_low_level badge4_pcmcia_ops = {
+ .owner = THIS_MODULE,
+ .configure_socket = badge4_pcmcia_configure_socket,
+ .first = 0,
+ .nr = 2,
+};
+
+int pcmcia_badge4_init(struct sa1111_dev *dev)
+{
+ printk(KERN_INFO
+ "%s: badge4_pcmvcc=%d, badge4_pcmvpp=%d, badge4_cfvcc=%d\n",
+ __func__,
+ badge4_pcmvcc, badge4_pcmvpp, badge4_cfvcc);
+
+ sa11xx_drv_pcmcia_ops(&badge4_pcmcia_ops);
+ return sa1111_pcmcia_add(dev, &badge4_pcmcia_ops,
+ sa11xx_drv_pcmcia_add_one);
+}
+
+#ifndef MODULE
+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);
+#endif
diff --git a/drivers/pcmcia/sa1111_generic.c b/drivers/pcmcia/sa1111_generic.c
new file mode 100644
index 000000000..117834102
--- /dev/null
+++ b/drivers/pcmcia/sa1111_generic.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/ioport.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <pcmcia/ss.h>
+
+#include <mach/hardware.h>
+#include <asm/hardware/sa1111.h>
+#include <asm/mach-types.h>
+#include <asm/irq.h>
+
+#include "sa1111_generic.h"
+
+/*
+ * These are offsets from the above base.
+ */
+#define PCCR 0x0000
+#define PCSSR 0x0004
+#define PCSR 0x0008
+
+#define PCSR_S0_READY (1<<0)
+#define PCSR_S1_READY (1<<1)
+#define PCSR_S0_DETECT (1<<2)
+#define PCSR_S1_DETECT (1<<3)
+#define PCSR_S0_VS1 (1<<4)
+#define PCSR_S0_VS2 (1<<5)
+#define PCSR_S1_VS1 (1<<6)
+#define PCSR_S1_VS2 (1<<7)
+#define PCSR_S0_WP (1<<8)
+#define PCSR_S1_WP (1<<9)
+#define PCSR_S0_BVD1 (1<<10)
+#define PCSR_S0_BVD2 (1<<11)
+#define PCSR_S1_BVD1 (1<<12)
+#define PCSR_S1_BVD2 (1<<13)
+
+#define PCCR_S0_RST (1<<0)
+#define PCCR_S1_RST (1<<1)
+#define PCCR_S0_FLT (1<<2)
+#define PCCR_S1_FLT (1<<3)
+#define PCCR_S0_PWAITEN (1<<4)
+#define PCCR_S1_PWAITEN (1<<5)
+#define PCCR_S0_PSE (1<<6)
+#define PCCR_S1_PSE (1<<7)
+
+#define PCSSR_S0_SLEEP (1<<0)
+#define PCSSR_S1_SLEEP (1<<1)
+
+#define IDX_IRQ_S0_READY_NINT (0)
+#define IDX_IRQ_S0_CD_VALID (1)
+#define IDX_IRQ_S0_BVD1_STSCHG (2)
+#define IDX_IRQ_S1_READY_NINT (3)
+#define IDX_IRQ_S1_CD_VALID (4)
+#define IDX_IRQ_S1_BVD1_STSCHG (5)
+#define NUM_IRQS (6)
+
+void sa1111_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state)
+{
+ struct sa1111_pcmcia_socket *s = to_skt(skt);
+ u32 status = readl_relaxed(s->dev->mapbase + 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_pcmcia_socket *s = to_skt(skt);
+ u32 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 = readl_relaxed(s->dev->mapbase + PCCR);
+ val &= ~pccr_skt_mask;
+ val |= pccr_set_mask & pccr_skt_mask;
+ writel_relaxed(val, s->dev->mapbase + PCCR);
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+int sa1111_pcmcia_add(struct sa1111_dev *dev, struct pcmcia_low_level *ops,
+ int (*add)(struct soc_pcmcia_socket *))
+{
+ struct sa1111_pcmcia_socket *s;
+ struct clk *clk;
+ int i, ret = 0, irqs[NUM_IRQS];
+
+ clk = devm_clk_get(&dev->dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ for (i = 0; i < NUM_IRQS; i++) {
+ irqs[i] = sa1111_get_irq(dev, i);
+ if (irqs[i] <= 0)
+ return irqs[i] ? : -ENXIO;
+ }
+
+ ops->socket_state = sa1111_pcmcia_socket_state;
+
+ for (i = 0; i < ops->nr; i++) {
+ s = kzalloc(sizeof(*s), GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+
+ s->soc.nr = ops->first + i;
+ s->soc.clk = clk;
+
+ soc_pcmcia_init_one(&s->soc, ops, &dev->dev);
+ s->dev = dev;
+ if (s->soc.nr) {
+ s->soc.socket.pci_irq = irqs[IDX_IRQ_S1_READY_NINT];
+ s->soc.stat[SOC_STAT_CD].irq = irqs[IDX_IRQ_S1_CD_VALID];
+ s->soc.stat[SOC_STAT_CD].name = "SA1111 CF card detect";
+ s->soc.stat[SOC_STAT_BVD1].irq = irqs[IDX_IRQ_S1_BVD1_STSCHG];
+ s->soc.stat[SOC_STAT_BVD1].name = "SA1111 CF BVD1";
+ } else {
+ s->soc.socket.pci_irq = irqs[IDX_IRQ_S0_READY_NINT];
+ s->soc.stat[SOC_STAT_CD].irq = irqs[IDX_IRQ_S0_CD_VALID];
+ s->soc.stat[SOC_STAT_CD].name = "SA1111 PCMCIA card detect";
+ s->soc.stat[SOC_STAT_BVD1].irq = irqs[IDX_IRQ_S0_BVD1_STSCHG];
+ s->soc.stat[SOC_STAT_BVD1].name = "SA1111 PCMCIA BVD1";
+ }
+
+ ret = add(&s->soc);
+ if (ret == 0) {
+ s->next = dev_get_drvdata(&dev->dev);
+ dev_set_drvdata(&dev->dev, s);
+ } else
+ kfree(s);
+ }
+
+ return ret;
+}
+
+static int pcmcia_probe(struct sa1111_dev *dev)
+{
+ void __iomem *base;
+ int ret;
+
+ ret = sa1111_enable_device(dev);
+ if (ret)
+ return ret;
+
+ dev_set_drvdata(&dev->dev, NULL);
+
+ if (!request_mem_region(dev->res.start, 512, SA1111_DRIVER_NAME(dev))) {
+ sa1111_disable_device(dev);
+ return -EBUSY;
+ }
+
+ base = dev->mapbase;
+
+ /*
+ * Initialise the suspend state.
+ */
+ writel_relaxed(PCSSR_S0_SLEEP | PCSSR_S1_SLEEP, base + PCSSR);
+ writel_relaxed(PCCR_S0_FLT | PCCR_S1_FLT, base + PCCR);
+
+ ret = -ENODEV;
+#ifdef CONFIG_SA1100_BADGE4
+ if (machine_is_badge4())
+ ret = pcmcia_badge4_init(dev);
+#endif
+#ifdef CONFIG_SA1100_JORNADA720
+ if (machine_is_jornada720())
+ ret = pcmcia_jornada720_init(dev);
+#endif
+#ifdef CONFIG_ARCH_LUBBOCK
+ if (machine_is_lubbock())
+ ret = pcmcia_lubbock_init(dev);
+#endif
+#ifdef CONFIG_ASSABET_NEPONSET
+ if (machine_is_assabet())
+ ret = pcmcia_neponset_init(dev);
+#endif
+
+ if (ret) {
+ release_mem_region(dev->res.start, 512);
+ sa1111_disable_device(dev);
+ }
+
+ return ret;
+}
+
+static int pcmcia_remove(struct sa1111_dev *dev)
+{
+ struct sa1111_pcmcia_socket *next, *s = dev_get_drvdata(&dev->dev);
+
+ dev_set_drvdata(&dev->dev, NULL);
+
+ for (; s; s = next) {
+ next = s->next;
+ soc_pcmcia_remove_one(&s->soc);
+ kfree(s);
+ }
+
+ release_mem_region(dev->res.start, 512);
+ sa1111_disable_device(dev);
+ return 0;
+}
+
+static struct sa1111_driver pcmcia_driver = {
+ .drv = {
+ .name = "sa1111-pcmcia",
+ },
+ .devid = SA1111_DEVID_PCMCIA,
+ .probe = pcmcia_probe,
+ .remove = pcmcia_remove,
+};
+
+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);
+}
+
+fs_initcall(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 000000000..c01571d46
--- /dev/null
+++ b/drivers/pcmcia/sa1111_generic.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include "soc_common.h"
+#include "sa11xx_base.h"
+
+struct sa1111_pcmcia_socket {
+ struct soc_pcmcia_socket soc;
+ struct sa1111_dev *dev;
+ struct sa1111_pcmcia_socket *next;
+};
+
+static inline struct sa1111_pcmcia_socket *to_skt(struct soc_pcmcia_socket *s)
+{
+ return container_of(s, struct sa1111_pcmcia_socket, soc);
+}
+
+int sa1111_pcmcia_add(struct sa1111_dev *dev, struct pcmcia_low_level *ops,
+ int (*add)(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 int pcmcia_badge4_init(struct sa1111_dev *);
+extern int pcmcia_jornada720_init(struct sa1111_dev *);
+extern int pcmcia_lubbock_init(struct sa1111_dev *);
+extern int pcmcia_neponset_init(struct sa1111_dev *);
+
diff --git a/drivers/pcmcia/sa1111_jornada720.c b/drivers/pcmcia/sa1111_jornada720.c
new file mode 100644
index 000000000..1083e1b4f
--- /dev/null
+++ b/drivers/pcmcia/sa1111_jornada720.c
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * drivers/pcmcia/sa1100_jornada720.c
+ *
+ * Jornada720 PCMCIA specific routines
+ *
+ */
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/io.h>
+
+#include <mach/hardware.h>
+#include <asm/mach-types.h>
+
+#include "sa1111_generic.h"
+
+/*
+ * Socket 0 power: GPIO A0
+ * Socket 0 3V: GPIO A2
+ * Socket 1 power: GPIO A1 & GPIO A3
+ * Socket 1 3V: GPIO A3
+ * Does Socket 1 3V actually do anything?
+ */
+enum {
+ J720_GPIO_PWR,
+ J720_GPIO_3V,
+ J720_GPIO_MAX,
+};
+struct jornada720_data {
+ struct gpio_desc *gpio[J720_GPIO_MAX];
+};
+
+static int jornada720_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ struct device *dev = skt->socket.dev.parent;
+ struct jornada720_data *j;
+
+ j = devm_kzalloc(dev, sizeof(*j), GFP_KERNEL);
+ if (!j)
+ return -ENOMEM;
+
+ j->gpio[J720_GPIO_PWR] = devm_gpiod_get(dev, skt->nr ? "s1-power" :
+ "s0-power", GPIOD_OUT_LOW);
+ if (IS_ERR(j->gpio[J720_GPIO_PWR]))
+ return PTR_ERR(j->gpio[J720_GPIO_PWR]);
+
+ j->gpio[J720_GPIO_3V] = devm_gpiod_get(dev, skt->nr ? "s1-3v" :
+ "s0-3v", GPIOD_OUT_LOW);
+ if (IS_ERR(j->gpio[J720_GPIO_3V]))
+ return PTR_ERR(j->gpio[J720_GPIO_3V]);
+
+ skt->driver_data = j;
+
+ return 0;
+}
+
+static int
+jornada720_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_t *state)
+{
+ struct jornada720_data *j = skt->driver_data;
+ DECLARE_BITMAP(values, J720_GPIO_MAX) = { 0, };
+ int ret;
+
+ printk(KERN_INFO "%s(): config socket %d vcc %d vpp %d\n", __func__,
+ skt->nr, state->Vcc, state->Vpp);
+
+ switch (skt->nr) {
+ case 0:
+ switch (state->Vcc) {
+ default:
+ case 0:
+ __assign_bit(J720_GPIO_PWR, values, 0);
+ __assign_bit(J720_GPIO_3V, values, 0);
+ break;
+ case 33:
+ __assign_bit(J720_GPIO_PWR, values, 1);
+ __assign_bit(J720_GPIO_3V, values, 1);
+ break;
+ case 50:
+ __assign_bit(J720_GPIO_PWR, values, 1);
+ __assign_bit(J720_GPIO_3V, values, 0);
+ break;
+ }
+ break;
+
+ case 1:
+ switch (state->Vcc) {
+ default:
+ case 0:
+ __assign_bit(J720_GPIO_PWR, values, 0);
+ __assign_bit(J720_GPIO_3V, values, 0);
+ break;
+ case 33:
+ case 50:
+ __assign_bit(J720_GPIO_PWR, values, 1);
+ __assign_bit(J720_GPIO_3V, values, 1);
+ break;
+ }
+ break;
+
+ default:
+ return -1;
+ }
+
+ if (state->Vpp != state->Vcc && state->Vpp != 0) {
+ printk(KERN_ERR "%s(): slot cannot support VPP %u\n",
+ __func__, state->Vpp);
+ return -EPERM;
+ }
+
+ ret = sa1111_pcmcia_configure_socket(skt, state);
+ if (ret == 0)
+ ret = gpiod_set_array_value_cansleep(J720_GPIO_MAX, j->gpio,
+ NULL, values);
+
+ return ret;
+}
+
+static struct pcmcia_low_level jornada720_pcmcia_ops = {
+ .owner = THIS_MODULE,
+ .hw_init = jornada720_pcmcia_hw_init,
+ .configure_socket = jornada720_pcmcia_configure_socket,
+ .first = 0,
+ .nr = 2,
+};
+
+int pcmcia_jornada720_init(struct sa1111_dev *sadev)
+{
+ /* Fixme: why messing around with SA11x0's GPIO1? */
+ GRER |= 0x00000002;
+
+ sa11xx_drv_pcmcia_ops(&jornada720_pcmcia_ops);
+ return sa1111_pcmcia_add(sadev, &jornada720_pcmcia_ops,
+ sa11xx_drv_pcmcia_add_one);
+}
diff --git a/drivers/pcmcia/sa1111_lubbock.c b/drivers/pcmcia/sa1111_lubbock.c
new file mode 100644
index 000000000..7feb8d61c
--- /dev/null
+++ b/drivers/pcmcia/sa1111_lubbock.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * linux/drivers/pcmcia/pxa2xx_lubbock.c
+ *
+ * Author: George Davis
+ * Created: Jan 10, 2002
+ * Copyright: MontaVista Software Inc.
+ *
+ * Originally based upon linux/drivers/pcmcia/sa1100_neponset.c
+ *
+ * Lubbock PCMCIA specific routines.
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <mach/hardware.h>
+#include <asm/hardware/sa1111.h>
+#include <asm/mach-types.h>
+
+#include "sa1111_generic.h"
+#include "max1600.h"
+
+static int lubbock_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ struct max1600 *m;
+ int ret;
+
+ ret = max1600_init(skt->socket.dev.parent, &m,
+ skt->nr ? MAX1600_CHAN_B : MAX1600_CHAN_A,
+ MAX1600_CODE_HIGH);
+ if (ret == 0)
+ skt->driver_data = m;
+
+ return ret;
+}
+
+static int
+lubbock_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+ const socket_state_t *state)
+{
+ struct max1600 *m = skt->driver_data;
+ int ret = 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:
+ case 1:
+ break;
+
+ default:
+ ret = -1;
+ }
+
+ if (ret == 0)
+ ret = sa1111_pcmcia_configure_socket(skt, state);
+ if (ret == 0)
+ ret = max1600_configure(m, state->Vcc, state->Vpp);
+
+#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
+ */
+ max1600_configure(m, 0, 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
+ * available 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,
+ .configure_socket = lubbock_pcmcia_configure_socket,
+ .first = 0,
+ .nr = 2,
+};
+
+#include "pxa2xx_base.h"
+
+int pcmcia_lubbock_init(struct sa1111_dev *sadev)
+{
+ pxa2xx_drv_pcmcia_ops(&lubbock_pcmcia_ops);
+ pxa2xx_configure_sockets(&sadev->dev, &lubbock_pcmcia_ops);
+ return sa1111_pcmcia_add(sadev, &lubbock_pcmcia_ops,
+ pxa2xx_drv_pcmcia_add_one);
+}
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/pcmcia/sa1111_neponset.c b/drivers/pcmcia/sa1111_neponset.c
new file mode 100644
index 000000000..de0ce1335
--- /dev/null
+++ b/drivers/pcmcia/sa1111_neponset.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * linux/drivers/pcmcia/sa1100_neponset.c
+ *
+ * Neponset PCMCIA specific routines
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+
+#include <asm/mach-types.h>
+
+#include "sa1111_generic.h"
+#include "max1600.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_hw_init(struct soc_pcmcia_socket *skt)
+{
+ struct max1600 *m;
+ int ret;
+
+ ret = max1600_init(skt->socket.dev.parent, &m,
+ skt->nr ? MAX1600_CHAN_B : MAX1600_CHAN_A,
+ MAX1600_CODE_LOW);
+ if (ret == 0)
+ skt->driver_data = m;
+
+ return ret;
+}
+
+static int
+neponset_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_t *state)
+{
+ struct max1600 *m = skt->driver_data;
+ int ret;
+
+ ret = sa1111_pcmcia_configure_socket(skt, state);
+ if (ret == 0)
+ ret = max1600_configure(m, state->Vcc, state->Vpp);
+
+ return ret;
+}
+
+static struct pcmcia_low_level neponset_pcmcia_ops = {
+ .owner = THIS_MODULE,
+ .hw_init = neponset_pcmcia_hw_init,
+ .configure_socket = neponset_pcmcia_configure_socket,
+ .first = 0,
+ .nr = 2,
+};
+
+int pcmcia_neponset_init(struct sa1111_dev *sadev)
+{
+ sa11xx_drv_pcmcia_ops(&neponset_pcmcia_ops);
+ return sa1111_pcmcia_add(sadev, &neponset_pcmcia_ops,
+ sa11xx_drv_pcmcia_add_one);
+}
diff --git a/drivers/pcmcia/sa11xx_base.c b/drivers/pcmcia/sa11xx_base.c
new file mode 100644
index 000000000..48140ac73
--- /dev/null
+++ b/drivers/pcmcia/sa11xx_base.c
@@ -0,0 +1,263 @@
+/*======================================================================
+
+ 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/cpufreq.h>
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <mach/hardware.h>
+#include <asm/irq.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;
+ }
+
+ return 0;
+}
+
+#endif
+
+static int
+sa1100_pcmcia_set_timing(struct soc_pcmcia_socket *skt)
+{
+ unsigned long clk = clk_get_rate(skt->clk);
+
+ return sa1100_pcmcia_set_mecr(skt, clk / 1000);
+}
+
+static int
+sa1100_pcmcia_show_timing(struct soc_pcmcia_socket *skt, char *buf)
+{
+ struct soc_pcmcia_timing timing;
+ unsigned int clock = clk_get_rate(skt->clk) / 1000;
+ unsigned long mecr = MECR;
+ char *p = buf;
+
+ soc_common_pcmcia_get_timing(skt, &timing);
+
+ p+=sprintf(p, "I/O : %uns (%uns)\n", timing.io,
+ sa1100_pcmcia_cmd_time(clock, MECR_BSIO_GET(mecr, skt->nr)));
+
+ p+=sprintf(p, "attribute: %uns (%uns)\n", timing.attr,
+ sa1100_pcmcia_cmd_time(clock, MECR_BSA_GET(mecr, skt->nr)));
+
+ p+=sprintf(p, "common : %uns (%uns)\n", timing.mem,
+ sa1100_pcmcia_cmd_time(clock, MECR_BSM_GET(mecr, skt->nr)));
+
+ return p - buf;
+}
+
+static const char *skt_names[] = {
+ "PCMCIA socket 0",
+ "PCMCIA socket 1",
+};
+
+#define SKT_DEV_INFO_SIZE(n) \
+ (sizeof(struct skt_dev_info) + (n)*sizeof(struct soc_pcmcia_socket))
+
+int sa11xx_drv_pcmcia_add_one(struct soc_pcmcia_socket *skt)
+{
+ 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;
+
+ 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;
+
+ 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;
+
+ 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;
+
+ return soc_pcmcia_add_one(skt);
+}
+EXPORT_SYMBOL(sa11xx_drv_pcmcia_add_one);
+
+void sa11xx_drv_pcmcia_ops(struct pcmcia_low_level *ops)
+{
+ /*
+ * 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
+}
+EXPORT_SYMBOL(sa11xx_drv_pcmcia_ops);
+
+int sa11xx_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 i, ret = 0;
+ struct clk *clk;
+
+ clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ sa11xx_drv_pcmcia_ops(ops);
+
+ sinfo = devm_kzalloc(dev, SKT_DEV_INFO_SIZE(nr), GFP_KERNEL);
+ if (!sinfo)
+ return -ENOMEM;
+
+ sinfo->nskt = nr;
+
+ /* Initialize processor specific parameters */
+ for (i = 0; i < nr; i++) {
+ skt = &sinfo->skt[i];
+
+ skt->nr = first + i;
+ skt->clk = clk;
+ soc_pcmcia_init_one(skt, ops, dev);
+
+ ret = sa11xx_drv_pcmcia_add_one(skt);
+ if (ret)
+ break;
+ }
+
+ if (ret) {
+ while (--i >= 0)
+ soc_pcmcia_remove_one(&sinfo->skt[i]);
+ } else {
+ dev_set_drvdata(dev, sinfo);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL(sa11xx_drv_pcmcia_probe);
+
+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 000000000..3d76d720f
--- /dev/null
+++ b/drivers/pcmcia/sa11xx_base.h
@@ -0,0 +1,125 @@
+/*======================================================================
+
+ 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;
+}
+
+
+int sa11xx_drv_pcmcia_add_one(struct soc_pcmcia_socket *skt);
+void sa11xx_drv_pcmcia_ops(struct pcmcia_low_level *ops);
+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 000000000..3a8c84bb1
--- /dev/null
+++ b/drivers/pcmcia/soc_common.c
@@ -0,0 +1,894 @@
+/*======================================================================
+
+ 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/cpufreq.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/mutex.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+
+#include <mach/hardware.h>
+
+#include "soc_common.h"
+
+static irqreturn_t soc_common_pcmcia_interrupt(int irq, void *dev);
+
+#ifdef CONFIG_PCMCIA_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, ...)
+{
+ struct va_format vaf;
+ va_list args;
+ if (pc_debug > lvl) {
+ va_start(args, fmt);
+
+ vaf.fmt = fmt;
+ vaf.va = &args;
+
+ printk(KERN_DEBUG "skt%u: %s: %pV", skt->nr, func, &vaf);
+
+ va_end(args);
+ }
+}
+EXPORT_SYMBOL(soc_pcmcia_debug);
+
+#endif
+
+#define to_soc_pcmcia_socket(x) \
+ container_of(x, struct soc_pcmcia_socket, socket)
+
+int soc_pcmcia_regulator_set(struct soc_pcmcia_socket *skt,
+ struct soc_pcmcia_regulator *r, int v)
+{
+ bool on;
+ int ret;
+
+ if (!r->reg)
+ return 0;
+
+ on = v != 0;
+ if (r->on == on)
+ return 0;
+
+ if (on) {
+ ret = regulator_set_voltage(r->reg, v * 100000, v * 100000);
+ if (ret) {
+ int vout = regulator_get_voltage(r->reg) / 100000;
+
+ dev_warn(&skt->socket.dev,
+ "CS requested %s=%u.%uV, applying %u.%uV\n",
+ r == &skt->vcc ? "Vcc" : "Vpp",
+ v / 10, v % 10, vout / 10, vout % 10);
+ }
+
+ ret = regulator_enable(r->reg);
+ } else {
+ ret = regulator_disable(r->reg);
+ }
+ if (ret == 0)
+ r->on = on;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(soc_pcmcia_regulator_set);
+
+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 void __soc_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt,
+ unsigned int nr)
+{
+ unsigned int i;
+
+ for (i = 0; i < nr; i++)
+ if (skt->stat[i].irq)
+ free_irq(skt->stat[i].irq, skt);
+
+ if (skt->ops->hw_shutdown)
+ skt->ops->hw_shutdown(skt);
+
+ clk_disable_unprepare(skt->clk);
+}
+
+static void soc_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
+{
+ __soc_pcmcia_hw_shutdown(skt, ARRAY_SIZE(skt->stat));
+}
+
+int soc_pcmcia_request_gpiods(struct soc_pcmcia_socket *skt)
+{
+ struct device *dev = skt->socket.dev.parent;
+ struct gpio_desc *desc;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(skt->stat); i++) {
+ if (!skt->stat[i].name)
+ continue;
+
+ desc = devm_gpiod_get(dev, skt->stat[i].name, GPIOD_IN);
+ if (IS_ERR(desc)) {
+ dev_err(dev, "Failed to get GPIO for %s: %ld\n",
+ skt->stat[i].name, PTR_ERR(desc));
+ return PTR_ERR(desc);
+ }
+
+ skt->stat[i].desc = desc;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(soc_pcmcia_request_gpiods);
+
+static int soc_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+{
+ int ret = 0, i;
+
+ ret = clk_prepare_enable(skt->clk);
+ if (ret)
+ return ret;
+
+ if (skt->ops->hw_init) {
+ ret = skt->ops->hw_init(skt);
+ if (ret) {
+ clk_disable_unprepare(skt->clk);
+ return ret;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(skt->stat); i++) {
+ if (gpio_is_valid(skt->stat[i].gpio)) {
+ unsigned long flags = GPIOF_IN;
+
+ /* CD is active low by default */
+ if (i == SOC_STAT_CD)
+ flags |= GPIOF_ACTIVE_LOW;
+
+ ret = devm_gpio_request_one(skt->socket.dev.parent,
+ skt->stat[i].gpio, flags,
+ skt->stat[i].name);
+ if (ret) {
+ __soc_pcmcia_hw_shutdown(skt, i);
+ return ret;
+ }
+
+ skt->stat[i].desc = gpio_to_desc(skt->stat[i].gpio);
+ }
+
+ if (i < SOC_STAT_VS1 && skt->stat[i].desc) {
+ int irq = gpiod_to_irq(skt->stat[i].desc);
+
+ if (irq > 0) {
+ if (i == SOC_STAT_RDY)
+ skt->socket.pci_irq = irq;
+ else
+ skt->stat[i].irq = irq;
+ }
+ }
+
+ if (skt->stat[i].irq) {
+ ret = request_irq(skt->stat[i].irq,
+ soc_common_pcmcia_interrupt,
+ IRQF_TRIGGER_NONE,
+ skt->stat[i].name, skt);
+ if (ret) {
+ __soc_pcmcia_hw_shutdown(skt, i);
+ return ret;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static void soc_pcmcia_hw_enable(struct soc_pcmcia_socket *skt)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(skt->stat); i++)
+ if (skt->stat[i].irq) {
+ irq_set_irq_type(skt->stat[i].irq, IRQ_TYPE_EDGE_RISING);
+ irq_set_irq_type(skt->stat[i].irq, IRQ_TYPE_EDGE_BOTH);
+ }
+}
+
+static void soc_pcmcia_hw_disable(struct soc_pcmcia_socket *skt)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(skt->stat); i++)
+ if (skt->stat[i].irq)
+ irq_set_irq_type(skt->stat[i].irq, IRQ_TYPE_NONE);
+}
+
+/*
+ * The CF 3.0 specification says that cards tie VS1 to ground and leave
+ * VS2 open. Many implementations do not wire up the VS signals, so we
+ * provide hard-coded values as per the CF 3.0 spec.
+ */
+void soc_common_cf_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state)
+{
+ state->vs_3v = 1;
+}
+EXPORT_SYMBOL_GPL(soc_common_cf_socket_state);
+
+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));
+
+ /* Make battery voltage state report 'good' */
+ state.bvd1 = 1;
+ state.bvd2 = 1;
+
+ if (skt->stat[SOC_STAT_CD].desc)
+ state.detect = !!gpiod_get_value(skt->stat[SOC_STAT_CD].desc);
+ if (skt->stat[SOC_STAT_RDY].desc)
+ state.ready = !!gpiod_get_value(skt->stat[SOC_STAT_RDY].desc);
+ if (skt->stat[SOC_STAT_BVD1].desc)
+ state.bvd1 = !!gpiod_get_value(skt->stat[SOC_STAT_BVD1].desc);
+ if (skt->stat[SOC_STAT_BVD2].desc)
+ state.bvd2 = !!gpiod_get_value(skt->stat[SOC_STAT_BVD2].desc);
+ if (skt->stat[SOC_STAT_VS1].desc)
+ state.vs_3v = !!gpiod_get_value(skt->stat[SOC_STAT_VS1].desc);
+ if (skt->stat[SOC_STAT_VS2].desc)
+ state.vs_Xv = !!gpiod_get_value(skt->stat[SOC_STAT_VS2].desc);
+
+ 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 ? 0 : SS_STSCHG;
+ 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) {
+ pr_err("soc_common_pcmcia: unable to configure socket %d\n",
+ skt->nr);
+ /* restore the previous state */
+ WARN_ON(skt->ops->configure_socket(skt, &skt->cs_state));
+ return ret;
+ }
+
+ if (ret == 0) {
+ struct gpio_desc *descs[2];
+ DECLARE_BITMAP(values, 2);
+ int n = 0;
+
+ if (skt->gpio_reset) {
+ descs[n] = skt->gpio_reset;
+ __assign_bit(n++, values, state->flags & SS_RESET);
+ }
+ if (skt->gpio_bus_enable) {
+ descs[n] = skt->gpio_bus_enable;
+ __assign_bit(n++, values, state->flags & SS_OUTPUT_ENA);
+ }
+
+ if (n)
+ gpiod_set_array_value_cansleep(n, descs, NULL, values);
+
+ /*
+ * 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;
+ irq_set_irq_type(skt->socket.pci_irq,
+ IRQ_TYPE_EDGE_FALLING);
+ } else if (skt->irq_state == 1 && state->io_irq == 0) {
+ skt->irq_state = 0;
+ irq_set_irq_type(skt->socket.pci_irq, IRQ_TYPE_NONE);
+ }
+
+ skt->cs_state = *state;
+ }
+
+ 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");
+ if (skt->ops->socket_init)
+ skt->ops->socket_init(skt);
+ soc_pcmcia_hw_enable(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");
+
+ soc_pcmcia_hw_disable(skt);
+ if (skt->ops->socket_suspend)
+ 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(struct timer_list *t)
+{
+ struct soc_pcmcia_socket *skt = from_timer(skt, t, poll_timer);
+ 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 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 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.
+ */
+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%s flags: %s%s%s%s%s%s Vcc %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%08llx stop 0x%08llx\n",
+ map->map, map->speed, (unsigned long long)map->start,
+ (unsigned long long)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", __func__,
+ 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, -ERRNO 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 device *dev, struct device_attribute *attr, char *buf)
+{
+ struct soc_pcmcia_socket *skt =
+ container_of(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->socket.pci_irq);
+ if (skt->ops->show_timing)
+ p += skt->ops->show_timing(skt, p);
+
+ return p-buf;
+}
+static 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,
+ .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,
+};
+
+
+#ifdef CONFIG_CPU_FREQ
+static int soc_common_pcmcia_cpufreq_nb(struct notifier_block *nb,
+ unsigned long val, void *data)
+{
+ struct soc_pcmcia_socket *skt = container_of(nb, struct soc_pcmcia_socket, cpufreq_nb);
+ struct cpufreq_freqs *freqs = data;
+
+ return skt->ops->frequency_change(skt, val, freqs);
+}
+#endif
+
+void soc_pcmcia_init_one(struct soc_pcmcia_socket *skt,
+ const struct pcmcia_low_level *ops, struct device *dev)
+{
+ int i;
+
+ skt->ops = ops;
+ skt->socket.owner = ops->owner;
+ skt->socket.dev.parent = dev;
+ skt->socket.pci_irq = NO_IRQ;
+
+ for (i = 0; i < ARRAY_SIZE(skt->stat); i++)
+ skt->stat[i].gpio = -EINVAL;
+}
+EXPORT_SYMBOL(soc_pcmcia_init_one);
+
+void soc_pcmcia_remove_one(struct soc_pcmcia_socket *skt)
+{
+ del_timer_sync(&skt->poll_timer);
+
+ pcmcia_unregister_socket(&skt->socket);
+
+#ifdef CONFIG_CPU_FREQ
+ if (skt->ops->frequency_change)
+ cpufreq_unregister_notifier(&skt->cpufreq_nb,
+ CPUFREQ_TRANSITION_NOTIFIER);
+#endif
+
+ soc_pcmcia_hw_shutdown(skt);
+
+ /* should not be required; violates some lowlevel drivers */
+ soc_common_pcmcia_config_skt(skt, &dead_socket);
+
+ 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);
+}
+EXPORT_SYMBOL(soc_pcmcia_remove_one);
+
+int soc_pcmcia_add_one(struct soc_pcmcia_socket *skt)
+{
+ int ret;
+
+ skt->cs_state = dead_socket;
+
+ timer_setup(&skt->poll_timer, soc_common_pcmcia_poll_event, 0);
+ skt->poll_timer.expires = jiffies + SOC_PCMCIA_POLL_PERIOD;
+
+ ret = request_resource(&iomem_resource, &skt->res_skt);
+ if (ret)
+ goto out_err_1;
+
+ ret = request_resource(&skt->res_skt, &skt->res_io);
+ if (ret)
+ goto out_err_2;
+
+ ret = request_resource(&skt->res_skt, &skt->res_mem);
+ if (ret)
+ goto out_err_3;
+
+ 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;
+ }
+
+ /*
+ * We initialize default socket timing here, because
+ * we are not guaranteed to see a SetIOMap operation at
+ * runtime.
+ */
+ skt->ops->set_timing(skt);
+
+ ret = soc_pcmcia_hw_init(skt);
+ if (ret)
+ goto out_err_6;
+
+ skt->socket.ops = &soc_common_pcmcia_operations;
+ 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.io_offset = (unsigned long)skt->virt_io;
+
+ skt->status = soc_common_pcmcia_skt_state(skt);
+
+#ifdef CONFIG_CPU_FREQ
+ if (skt->ops->frequency_change) {
+ skt->cpufreq_nb.notifier_call = soc_common_pcmcia_cpufreq_nb;
+
+ ret = cpufreq_register_notifier(&skt->cpufreq_nb,
+ CPUFREQ_TRANSITION_NOTIFIER);
+ if (ret < 0)
+ dev_err(skt->socket.dev.parent,
+ "unable to register CPU frequency change notifier for PCMCIA (%d)\n",
+ ret);
+ }
+#endif
+
+ ret = pcmcia_register_socket(&skt->socket);
+ if (ret)
+ goto out_err_7;
+
+ ret = device_create_file(&skt->socket.dev, &dev_attr_status);
+ if (ret)
+ goto out_err_8;
+
+ return ret;
+
+ out_err_8:
+ del_timer_sync(&skt->poll_timer);
+ pcmcia_unregister_socket(&skt->socket);
+
+ out_err_7:
+ soc_pcmcia_hw_shutdown(skt);
+ out_err_6:
+ 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:
+
+ return ret;
+}
+EXPORT_SYMBOL(soc_pcmcia_add_one);
+
+MODULE_AUTHOR("John Dorsey <john+@cs.cmu.edu>");
+MODULE_DESCRIPTION("Linux PCMCIA Card Services: Common SoC support");
+MODULE_LICENSE("Dual MPL/GPL");
diff --git a/drivers/pcmcia/soc_common.h b/drivers/pcmcia/soc_common.h
new file mode 100644
index 000000000..222e81c79
--- /dev/null
+++ b/drivers/pcmcia/soc_common.h
@@ -0,0 +1,218 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * 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/clk.h>
+#include <linux/cpufreq.h>
+#include <pcmcia/ss.h>
+#include <pcmcia/cistpl.h>
+
+
+struct device;
+struct gpio_desc;
+struct pcmcia_low_level;
+struct regulator;
+
+struct soc_pcmcia_regulator {
+ struct regulator *reg;
+ bool on;
+};
+
+/*
+ * 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
+ */
+ unsigned int nr;
+ struct clk *clk;
+
+ /*
+ * Core PCMCIA state
+ */
+ const 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;
+
+ struct {
+ int gpio;
+ struct gpio_desc *desc;
+ unsigned int irq;
+ const char *name;
+ } stat[6];
+#define SOC_STAT_CD 0 /* Card detect */
+#define SOC_STAT_BVD1 1 /* BATDEAD / IOSTSCHG */
+#define SOC_STAT_BVD2 2 /* BATWARN / IOSPKR */
+#define SOC_STAT_RDY 3 /* Ready / Interrupt */
+#define SOC_STAT_VS1 4 /* Voltage sense 1 */
+#define SOC_STAT_VS2 5 /* Voltage sense 2 */
+
+ struct gpio_desc *gpio_reset;
+ struct gpio_desc *gpio_bus_enable;
+ struct soc_pcmcia_regulator vcc;
+ struct soc_pcmcia_regulator vpp;
+
+ unsigned int irq_state;
+
+#ifdef CONFIG_CPU_FREQ
+ struct notifier_block cpufreq_nb;
+#endif
+ struct timer_list poll_timer;
+ struct list_head node;
+ void *driver_data;
+};
+
+struct skt_dev_info {
+ int nskt;
+ struct soc_pcmcia_socket skt[];
+};
+
+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 soc_pcmcia_timing {
+ unsigned short io;
+ unsigned short mem;
+ unsigned short attr;
+};
+
+extern void soc_common_pcmcia_get_timing(struct soc_pcmcia_socket *, struct soc_pcmcia_timing *);
+
+void soc_pcmcia_init_one(struct soc_pcmcia_socket *skt,
+ const struct pcmcia_low_level *ops, struct device *dev);
+void soc_pcmcia_remove_one(struct soc_pcmcia_socket *skt);
+int soc_pcmcia_add_one(struct soc_pcmcia_socket *skt);
+int soc_pcmcia_request_gpiods(struct soc_pcmcia_socket *skt);
+
+void soc_common_cf_socket_state(struct soc_pcmcia_socket *skt,
+ struct pcmcia_state *state);
+
+int soc_pcmcia_regulator_set(struct soc_pcmcia_socket *skt,
+ struct soc_pcmcia_regulator *r, int v);
+
+#ifdef CONFIG_PCMCIA_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 000000000..d1b220a1e
--- /dev/null
+++ b/drivers/pcmcia/socket_sysfs.c
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * socket_sysfs.c -- most of socket-related sysfs output
+ *
+ * (C) 2003 - 2004 Dominik Brodowski
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/major.h>
+#include <linux/errno.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/device.h>
+#include <linux/mutex.h>
+#include <asm/irq.h>
+
+#include <pcmcia/ss.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 device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct pcmcia_socket *s = to_socket(dev);
+
+ if (!(s->state & SOCKET_PRESENT))
+ return -ENODEV;
+ if (s->state & SOCKET_CARDBUS)
+ return sprintf(buf, "32-bit\n");
+ return sprintf(buf, "16-bit\n");
+}
+static DEVICE_ATTR(card_type, 0444, pccard_show_type, NULL);
+
+static ssize_t pccard_show_voltage(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct pcmcia_socket *s = to_socket(dev);
+
+ if (!(s->state & SOCKET_PRESENT))
+ return -ENODEV;
+ if (s->socket.Vcc)
+ return sprintf(buf, "%d.%dV\n", s->socket.Vcc / 10,
+ s->socket.Vcc % 10);
+ return sprintf(buf, "X.XV\n");
+}
+static DEVICE_ATTR(card_voltage, 0444, pccard_show_voltage, NULL);
+
+static ssize_t pccard_show_vpp(struct device *dev, struct device_attribute *attr,
+ 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 DEVICE_ATTR(card_vpp, 0444, pccard_show_vpp, NULL);
+
+static ssize_t pccard_show_vcc(struct device *dev, struct device_attribute *attr,
+ 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 DEVICE_ATTR(card_vcc, 0444, pccard_show_vcc, NULL);
+
+
+static ssize_t pccard_store_insert(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pcmcia_socket *s = to_socket(dev);
+
+ if (!count)
+ return -EINVAL;
+
+ pcmcia_parse_uevents(s, PCMCIA_UEVENT_INSERT);
+
+ return count;
+}
+static DEVICE_ATTR(card_insert, 0200, NULL, pccard_store_insert);
+
+
+static ssize_t pccard_show_card_pm_state(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pcmcia_socket *s = to_socket(dev);
+ return sprintf(buf, "%s\n", s->state & SOCKET_SUSPEND ? "off" : "on");
+}
+
+static ssize_t pccard_store_card_pm_state(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pcmcia_socket *s = to_socket(dev);
+ ssize_t ret = count;
+
+ if (!count)
+ return -EINVAL;
+
+ if (!strncmp(buf, "off", 3))
+ pcmcia_parse_uevents(s, PCMCIA_UEVENT_SUSPEND);
+ else {
+ if (!strncmp(buf, "on", 2))
+ pcmcia_parse_uevents(s, PCMCIA_UEVENT_RESUME);
+ else
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+static DEVICE_ATTR(card_pm_state, 0644, pccard_show_card_pm_state, pccard_store_card_pm_state);
+
+static ssize_t pccard_store_eject(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pcmcia_socket *s = to_socket(dev);
+
+ if (!count)
+ return -EINVAL;
+
+ pcmcia_parse_uevents(s, PCMCIA_UEVENT_EJECT);
+
+ return count;
+}
+static DEVICE_ATTR(card_eject, 0200, NULL, pccard_store_eject);
+
+
+static ssize_t pccard_show_irq_mask(struct device *dev,
+ struct device_attribute *attr,
+ 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 device *dev,
+ struct device_attribute *attr,
+ 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) {
+ mutex_lock(&s->ops_mutex);
+ s->irq_mask &= mask;
+ mutex_unlock(&s->ops_mutex);
+ ret = 0;
+ }
+
+ return ret ? ret : count;
+}
+static DEVICE_ATTR(card_irq_mask, 0600, pccard_show_irq_mask, pccard_store_irq_mask);
+
+
+static ssize_t pccard_show_resource(struct device *dev,
+ struct device_attribute *attr, 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 device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pcmcia_socket *s = to_socket(dev);
+
+ if (!count)
+ return -EINVAL;
+
+ mutex_lock(&s->ops_mutex);
+ if (!s->resource_setup_done)
+ s->resource_setup_done = 1;
+ mutex_unlock(&s->ops_mutex);
+
+ pcmcia_parse_uevents(s, PCMCIA_UEVENT_REQUERY);
+
+ return count;
+}
+static DEVICE_ATTR(available_resources_setup_done, 0600, pccard_show_resource, pccard_store_resource);
+
+static struct attribute *pccard_socket_attributes[] = {
+ &dev_attr_card_type.attr,
+ &dev_attr_card_voltage.attr,
+ &dev_attr_card_vpp.attr,
+ &dev_attr_card_vcc.attr,
+ &dev_attr_card_insert.attr,
+ &dev_attr_card_pm_state.attr,
+ &dev_attr_card_eject.attr,
+ &dev_attr_card_irq_mask.attr,
+ &dev_attr_available_resources_setup_done.attr,
+ NULL,
+};
+
+static const struct attribute_group socket_attrs = {
+ .attrs = pccard_socket_attributes,
+};
+
+int pccard_sysfs_add_socket(struct device *dev)
+{
+ return sysfs_create_group(&dev->kobj, &socket_attrs);
+}
+
+void pccard_sysfs_remove_socket(struct device *dev)
+{
+ sysfs_remove_group(&dev->kobj, &socket_attrs);
+}
diff --git a/drivers/pcmcia/tcic.c b/drivers/pcmcia/tcic.c
new file mode 100644
index 000000000..1a0e3f098
--- /dev/null
+++ b/drivers/pcmcia/tcic.c
@@ -0,0 +1,805 @@
+/*======================================================================
+
+ 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/timer.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/bitops.h>
+
+#include <asm/io.h>
+
+#include <pcmcia/ss.h>
+#include "tcic.h"
+
+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 unsigned 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_hw(tcic_base, ulong, ioport, 0444);
+module_param(ignore, int, 0444);
+module_param(do_scan, int, 0444);
+module_param_hw(irq_mask, int, other, 0444);
+module_param_hw_array(irq_list, int, irq, &irq_list_count, 0444);
+module_param_hw(cs_irq, int, irq, 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);
+static void tcic_timer(struct timer_list *unused);
+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 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)
+{
+ 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 struct platform_driver tcic_driver = {
+ .driver = {
+ .name = "tcic-pcmcia",
+ },
+};
+
+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 (platform_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 ");
+ platform_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);
+ platform_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 */
+ timer_setup(&poll_timer, &tcic_timer, 0);
+
+ /* Build interrupt mask */
+ printk(KERN_CONT ", %d sockets\n", sockets);
+ printk(KERN_INFO " irq list (");
+ 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);
+
+ 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.parent = &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);
+ platform_driver_unregister(&tcic_driver);
+} /* exit_tcic */
+
+/*====================================================================*/
+
+static irqreturn_t tcic_interrupt(int irq, void *dev)
+{
+ 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;
+
+ pr_debug("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;
+
+ pr_debug("interrupt done\n");
+ return IRQ_HANDLED;
+} /* tcic_interrupt */
+
+static void tcic_timer(struct timer_list *unused)
+{
+ pr_debug("tcic_timer()\n");
+ tcic_timer_pending = 0;
+ tcic_interrupt(0, 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;
+ dev_dbg(&sock->dev, "GetStatus(%d) = %#2.2x\n", psock, *value);
+ return 0;
+} /* tcic_get_status */
+
+/*====================================================================*/
+
+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;
+
+ dev_dbg(&sock->dev, "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;
+
+ dev_dbg(&sock->dev, "SetIOMap(%d, %d, %#2.2x, %d ns, "
+ "%#llx-%#llx)\n", psock, io->map, io->flags, io->speed,
+ (unsigned long long)io->start, (unsigned long long)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;
+
+ dev_dbg(&sock->dev, "SetMemMap(%d, %d, %#2.2x, %d ns, "
+ "%#llx-%#llx, %#x)\n", psock, mem->map, mem->flags,
+ mem->speed, (unsigned long long)mem->res->start,
+ (unsigned long long)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,
+ .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 000000000..2c0b8f65a
--- /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 000000000..5cb670e03
--- /dev/null
+++ b/drivers/pcmcia/ti113x.h
@@ -0,0 +1,978 @@
+/*
+ * 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
+
+
+/* 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 TIXX21_SCR_TIEALL 0x10000000
+#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
+#define ENE_TEST_C9_PFENABLE_F0 0x04
+#define ENE_TEST_C9_PFENABLE_F1 0x08
+#define ENE_TEST_C9_PFENABLE (ENE_TEST_C9_PFENABLE_F0 | ENE_TEST_C9_PFENABLE_F1)
+#define ENE_TEST_C9_WPDISALBLE_F0 0x40
+#define ENE_TEST_C9_WPDISALBLE_F1 0x80
+#define ENE_TEST_C9_WPDISALBLE (ENE_TEST_C9_WPDISALBLE_F0 | ENE_TEST_C9_WPDISALBLE_F1)
+
+/*
+ * 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->dev->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 void ti113x_use_isa_irq(struct yenta_socket *socket)
+{
+ int isa_irq = -1;
+ u8 intctl;
+ u32 isa_irq_mask = 0;
+
+ if (!isa_probe)
+ return;
+
+ /* get a free isa int */
+ isa_irq_mask = yenta_probe_irq(socket, isa_interrupts);
+ if (!isa_irq_mask)
+ return; /* no useable isa irq found */
+
+ /* choose highest available */
+ for (; isa_irq_mask; isa_irq++)
+ isa_irq_mask >>= 1;
+ socket->cb_irq = isa_irq;
+
+ exca_writeb(socket, I365_CSCINT, (isa_irq << 4));
+
+ intctl = exca_readb(socket, I365_INTCTL);
+ intctl &= ~(I365_INTR_ENA | I365_IRQ_MASK); /* CSC Enable */
+ exca_writeb(socket, I365_INTCTL, intctl);
+
+ dev_info(&socket->dev->dev,
+ "Yenta TI113x: using isa irq %d for CardBus\n", isa_irq);
+}
+
+
+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->dev->irq)
+ cardctl |= TI113X_CCR_PCI_IRQ_ENA | TI113X_CCR_PCI_CSC | TI113X_CCR_PCI_IREQ;
+ else
+ ti113x_use_isa_irq(socket);
+
+ 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);
+ dev_info(&socket->dev->dev, "TI: mfunc 0x%08x, devctl 0x%02x\n",
+ 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)
+ */
+ dev_info(&socket->dev->dev,
+ "TI: probing PCI interrupt failed, trying to fix\n");
+
+ /* 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) {
+ dev_info(&socket->dev->dev,
+ "TI: all-serial interrupts ok\n");
+ 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 */
+ dev_info(&socket->dev->dev,
+ "TI: falling back to parallel PCI interrupts\n");
+ 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;
+ dev_info(&socket->dev->dev, "TI: parallel PCI interrupts ok\n");
+ } 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;
+ dev_info(&socket->dev->dev,
+ "Yenta TI: no PCI interrupts. Fish. Please report.\n");
+ }
+}
+
+
+/* changes the irq of func1 to match that of func0 */
+static int ti12xx_align_irqs(struct yenta_socket *socket, int *old_irq)
+{
+ struct pci_dev *func0;
+
+ /* find func0 device */
+ func0 = pci_get_slot(socket->dev->bus, socket->dev->devfn & ~0x07);
+ if (!func0)
+ return 0;
+
+ if (old_irq)
+ *old_irq = socket->cb_irq;
+ socket->cb_irq = socket->dev->irq = func0->irq;
+
+ pci_dev_put(func0);
+
+ return 1;
+}
+
+/*
+ * 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)
+{
+ u32 sysctl;
+ int ret;
+
+ sysctl = config_readl(socket, TI113X_SYSTEM_CONTROL);
+ if (sysctl & TI122X_SCR_INTRTIE)
+ return 0;
+
+ /* align */
+ ret = ti12xx_align_irqs(socket, old_irq);
+ if (!ret)
+ return 0;
+
+ /* tie */
+ sysctl |= TI122X_SCR_INTRTIE;
+ config_writel(socket, TI113X_SYSTEM_CONTROL, sysctl);
+
+ 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, sysctl;
+ int pci_irq_status;
+
+ mfunc = mfunc_old = config_readl(socket, TI122X_MFUNC);
+ devctl = config_readb(socket, TI113X_DEVICE_CONTROL);
+ dev_info(&socket->dev->dev, "TI: mfunc 0x%08x, devctl 0x%02x\n",
+ mfunc, devctl);
+
+ /* if IRQs are configured as tied, align irq of func1 with func0 */
+ sysctl = config_readl(socket, TI113X_SYSTEM_CONTROL);
+ if (sysctl & TI122X_SCR_INTRTIE)
+ ti12xx_align_irqs(socket, NULL);
+
+ /* 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
+ */
+ dev_info(&socket->dev->dev,
+ "TI: probing PCI interrupt failed, trying to fix\n");
+
+ /* 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) {
+ dev_info(&socket->dev->dev,
+ "TI: all-serial interrupts, tied ok\n");
+ 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) {
+ dev_info(&socket->dev->dev,
+ "TI: parallel PCI interrupts ok\n");
+ 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) {
+ dev_info(&socket->dev->dev,
+ "TI: parallel PCI interrupts, tied ok\n");
+ goto out;
+ }
+
+ ti12xx_untie_interrupts(socket, old_irq);
+ }
+ }
+
+out:
+ if (pci_irq_status < 1) {
+ socket->cb_irq = 0;
+ dev_info(&socket->dev->dev,
+ "TI: no PCI interrupts. Fish. Please report.\n");
+ }
+}
+
+
+/* Returns true value if the second slot of a two-slot controller is empty */
+static int ti12xx_2nd_slot_empty(struct yenta_socket *socket)
+{
+ struct pci_dev *func;
+ struct yenta_socket *slot2;
+ int devfn;
+ unsigned int state;
+ int ret = 1;
+ u32 sysctl;
+
+ /* catch the two-slot controllers */
+ switch (socket->dev->device) {
+ case PCI_DEVICE_ID_TI_1220:
+ case PCI_DEVICE_ID_TI_1221:
+ case PCI_DEVICE_ID_TI_1225:
+ case PCI_DEVICE_ID_TI_1251A:
+ case PCI_DEVICE_ID_TI_1251B:
+ case PCI_DEVICE_ID_TI_1420:
+ case PCI_DEVICE_ID_TI_1450:
+ case PCI_DEVICE_ID_TI_1451A:
+ case PCI_DEVICE_ID_TI_1520:
+ case PCI_DEVICE_ID_TI_1620:
+ case PCI_DEVICE_ID_TI_4520:
+ case PCI_DEVICE_ID_TI_4450:
+ case PCI_DEVICE_ID_TI_4451:
+ /*
+ * there are way more, but they need to be added in yenta_socket.c
+ * and pci_ids.h first anyway.
+ */
+ break;
+
+ case PCI_DEVICE_ID_TI_XX12:
+ case PCI_DEVICE_ID_TI_X515:
+ case PCI_DEVICE_ID_TI_X420:
+ case PCI_DEVICE_ID_TI_X620:
+ case PCI_DEVICE_ID_TI_XX21_XX11:
+ case PCI_DEVICE_ID_TI_7410:
+ case PCI_DEVICE_ID_TI_7610:
+ /*
+ * those are either single or dual slot CB with additional functions
+ * like 1394, smartcard reader, etc. check the TIEALL flag for them
+ * the TIEALL flag binds the IRQ of all functions together.
+ * we catch the single slot variants later.
+ */
+ sysctl = config_readl(socket, TI113X_SYSTEM_CONTROL);
+ if (sysctl & TIXX21_SCR_TIEALL)
+ return 0;
+
+ break;
+
+ /* single-slot controllers have the 2nd slot empty always :) */
+ default:
+ return 1;
+ }
+
+ /* get other slot */
+ devfn = socket->dev->devfn & ~0x07;
+ func = pci_get_slot(socket->dev->bus,
+ (socket->dev->devfn & 0x07) ? devfn : devfn | 0x01);
+ if (!func)
+ return 1;
+
+ /*
+ * check that the device id of both slots match. this is needed for the
+ * XX21 and the XX11 controller that share the same device id for single
+ * and dual slot controllers. return '2nd slot empty'. we already checked
+ * if the interrupt is tied to another function.
+ */
+ if (socket->dev->device != func->device)
+ goto out;
+
+ slot2 = pci_get_drvdata(func);
+ if (!slot2)
+ goto out;
+
+ /* check state */
+ yenta_get_status(&slot2->socket, &state);
+ if (state & SS_DETECT) {
+ ret = 0;
+ goto out;
+ }
+
+out:
+ pci_dev_put(func);
+ return ret;
+}
+
+/*
+ * TI specifiy parts for the power hook.
+ *
+ * some TI's with some CB's produces interrupt storm on power on. it has been
+ * seen with atheros wlan cards on TI1225 and TI1410. solution is simply to
+ * disable any CB interrupts during this time.
+ */
+static int ti12xx_power_hook(struct pcmcia_socket *sock, int operation)
+{
+ struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket);
+ u32 mfunc, devctl, sysctl;
+ u8 gpio3;
+
+ /* only POWER_PRE and POWER_POST are interesting */
+ if ((operation != HOOK_POWER_PRE) && (operation != HOOK_POWER_POST))
+ return 0;
+
+ devctl = config_readb(socket, TI113X_DEVICE_CONTROL);
+ sysctl = config_readl(socket, TI113X_SYSTEM_CONTROL);
+ mfunc = config_readl(socket, TI122X_MFUNC);
+
+ /*
+ * all serial/tied: only disable when modparm set. always doing it
+ * would mean a regression for working setups 'cos it disables the
+ * interrupts for both both slots on 2-slot controllers
+ * (and users of single slot controllers where it's save have to
+ * live with setting the modparm, most don't have to anyway)
+ */
+ if (((devctl & TI113X_DCR_IMODE_MASK) == TI12XX_DCR_IMODE_ALL_SERIAL) &&
+ (pwr_irqs_off || ti12xx_2nd_slot_empty(socket))) {
+ 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:
+ if (operation == HOOK_POWER_PRE)
+ mfunc = (mfunc & ~TI122X_MFUNC3_MASK);
+ else
+ mfunc = (mfunc & ~TI122X_MFUNC3_MASK) | TI122X_MFUNC3_IRQSER;
+ }
+
+ return 0;
+ }
+
+ /* do the job differently for func0/1 */
+ if ((PCI_FUNC(socket->dev->devfn) == 0) ||
+ ((sysctl & TI122X_SCR_INTRTIE) &&
+ (pwr_irqs_off || ti12xx_2nd_slot_empty(socket)))) {
+ /* some bridges are different */
+ 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:
+ /* those oldies use gpio3 for INTA */
+ gpio3 = config_readb(socket, TI1250_GPIO3_CONTROL);
+ if (operation == HOOK_POWER_PRE)
+ gpio3 = (gpio3 & ~TI1250_GPIO_MODE_MASK) | 0x40;
+ else
+ gpio3 &= ~TI1250_GPIO_MODE_MASK;
+ config_writeb(socket, TI1250_GPIO3_CONTROL, gpio3);
+ break;
+
+ default:
+ /* all new bridges are the same */
+ if (operation == HOOK_POWER_PRE)
+ mfunc &= ~TI122X_MFUNC0_MASK;
+ else
+ mfunc |= TI122X_MFUNC0_INTA;
+ config_writel(socket, TI122X_MFUNC, mfunc);
+ }
+ } else {
+ switch (socket->dev->device) {
+ case PCI_DEVICE_ID_TI_1251A:
+ case PCI_DEVICE_ID_TI_1251B:
+ case PCI_DEVICE_ID_TI_1450:
+ /* those have INTA elsewhere and INTB in MFUNC0 */
+ if (operation == HOOK_POWER_PRE)
+ mfunc &= ~TI122X_MFUNC0_MASK;
+ else
+ mfunc |= TI125X_MFUNC0_INTB;
+ config_writel(socket, TI122X_MFUNC, mfunc);
+
+ break;
+
+ default:
+ /* all new bridges are the same */
+ if (operation == HOOK_POWER_PRE)
+ mfunc &= ~TI122X_MFUNC1_MASK;
+ else
+ mfunc |= TI122X_MFUNC1_INTB;
+ config_writel(socket, TI122X_MFUNC, mfunc);
+ }
+ }
+
+ return 0;
+}
+
+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) {
+ dev_info(&socket->dev->dev, "Disabling CLKRUN feature\n");
+ val |= TI113X_SCR_KEEPCLK;
+ }
+ if (!(val & TI122X_SCR_MRBURSTUP)) {
+ dev_info(&socket->dev->dev,
+ "Enabling burst memory read transactions\n");
+ val |= TI122X_SCR_MRBURSTUP;
+ }
+ if (val_orig != val)
+ config_writel(socket, TI113X_SYSTEM_CONTROL, val);
+
+ /*
+ * Yenta expects controllers to use CSCINT to route
+ * CSC interrupts to PCI rather than INTVAL.
+ */
+ val = config_readb(socket, TI1250_DIAGNOSTIC);
+ dev_info(&socket->dev->dev, "Using %s to route CSC interrupts to PCI\n",
+ (val & TI1250_DIAG_PCI_CSC) ? "CSCINT" : "INTVAL");
+ dev_info(&socket->dev->dev, "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);
+
+ /* install power hook */
+ socket->socket.power_hook = ti12xx_power_hook;
+
+ 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) {
+ dev_info(&socket->dev->dev,
+ "adjusting diagnostic: %02x -> %02x\n",
+ old, diag);
+ config_writeb(socket, TI1250_DIAGNOSTIC, diag);
+ }
+
+ return ti12xx_override(socket);
+}
+
+
+/**
+ * EnE specific part. EnE bridges are register compatible with TI bridges but
+ * have their own test registers and more important their own little problems.
+ * Some fixup code to make everybody happy (TM).
+ */
+
+#ifdef CONFIG_YENTA_ENE_TUNE
+/*
+ * set/clear various test bits:
+ * Defaults to clear the bit.
+ * - mask (u8) defines what bits to change
+ * - bits (u8) is the values to change them to
+ * -> it's
+ * current = (current & ~mask) | bits
+ */
+/* pci ids of devices that wants to have the bit set */
+#define DEVID(_vend,_dev,_subvend,_subdev,mask,bits) { \
+ .vendor = _vend, \
+ .device = _dev, \
+ .subvendor = _subvend, \
+ .subdevice = _subdev, \
+ .driver_data = ((mask) << 8 | (bits)), \
+ }
+static struct pci_device_id ene_tune_tbl[] = {
+ /* Echo Audio products based on motorola DSP56301 and DSP56361 */
+ DEVID(PCI_VENDOR_ID_MOTOROLA, 0x1801, 0xECC0, PCI_ANY_ID,
+ ENE_TEST_C9_TLTENABLE | ENE_TEST_C9_PFENABLE, ENE_TEST_C9_TLTENABLE),
+ DEVID(PCI_VENDOR_ID_MOTOROLA, 0x3410, 0xECC0, PCI_ANY_ID,
+ ENE_TEST_C9_TLTENABLE | ENE_TEST_C9_PFENABLE, ENE_TEST_C9_TLTENABLE),
+
+ {}
+};
+
+static void ene_tune_bridge(struct pcmcia_socket *sock, struct pci_bus *bus)
+{
+ struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket);
+ struct pci_dev *dev;
+ struct pci_device_id *id = NULL;
+ u8 test_c9, old_c9, mask, bits;
+
+ list_for_each_entry(dev, &bus->devices, bus_list) {
+ id = (struct pci_device_id *) pci_match_id(ene_tune_tbl, dev);
+ if (id)
+ break;
+ }
+
+ test_c9 = old_c9 = config_readb(socket, ENE_TEST_C9);
+ if (id) {
+ mask = (id->driver_data >> 8) & 0xFF;
+ bits = id->driver_data & 0xFF;
+
+ test_c9 = (test_c9 & ~mask) | bits;
+ }
+ else
+ /* default to clear TLTEnable bit, old behaviour */
+ test_c9 &= ~ENE_TEST_C9_TLTENABLE;
+
+ dev_info(&socket->dev->dev,
+ "EnE: changing testregister 0xC9, %02x -> %02x\n",
+ old_c9, test_c9);
+ config_writeb(socket, ENE_TEST_C9, test_c9);
+}
+
+static int ene_override(struct yenta_socket *socket)
+{
+ /* install tune_bridge() function */
+ socket->socket.tune_bridge = ene_tune_bridge;
+
+ return ti1250_override(socket);
+}
+#else
+# define ene_override ti1250_override
+#endif /* !CONFIG_YENTA_ENE_TUNE */
+
+#endif /* _LINUX_TI113X_H */
+
diff --git a/drivers/pcmcia/topic.h b/drivers/pcmcia/topic.h
new file mode 100644
index 000000000..582688fe7
--- /dev/null
+++ b/drivers/pcmcia/topic.h
@@ -0,0 +1,168 @@
+/*
+ * 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
+
+#define TOPIC_EXCA_IF_CONTROL 0x3e /* 8 bit */
+#define TOPIC_EXCA_IFC_33V_ENA 0x01
+
+#define TOPIC_PCI_CFG_PPBCN 0x3e /* 16-bit */
+#define TOPIC_PCI_CFG_PPBCN_WBEN 0x0400
+
+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_AUDIO_VIDEO_SWITCH);
+ reg |= TOPIC97_AVS_AUDIO_CONTROL | TOPIC97_AVS_VIDEO_CONTROL;
+ config_writeb(socket, TOPIC97_AUDIO_VIDEO_SWITCH, reg);
+ } else {
+ reg_zv &= ~TOPIC97_ZV_CONTROL_ENABLE;
+ config_writeb(socket, TOPIC97_ZOOM_VIDEO_CONTROL, reg_zv);
+
+ 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);
+ }
+}
+
+static int topic97_override(struct yenta_socket *socket)
+{
+ /* ToPIC97/100 support ZV */
+ socket->socket.zoom_video = topic97_zoom_video;
+ return 0;
+}
+
+
+static int topic95_override(struct yenta_socket *socket)
+{
+ u8 fctrl;
+ u16 ppbcn;
+
+ /* enable 3.3V support for 16bit cards */
+ fctrl = exca_readb(socket, TOPIC_EXCA_IF_CONTROL);
+ exca_writeb(socket, TOPIC_EXCA_IF_CONTROL, fctrl | TOPIC_EXCA_IFC_33V_ENA);
+
+ /* tell yenta to use exca registers to power 16bit cards */
+ socket->flags |= YENTA_16BIT_POWER_EXCA | YENTA_16BIT_POWER_DF;
+
+ /* Disable write buffers to prevent lockups under load with numerous
+ Cardbus cards, observed on Tecra 500CDT and reported elsewhere on the
+ net. This is not a power-on default according to the datasheet
+ but some BIOSes seem to set it. */
+ if (pci_read_config_word(socket->dev, TOPIC_PCI_CFG_PPBCN, &ppbcn) == 0
+ && socket->dev->revision <= 7
+ && (ppbcn & TOPIC_PCI_CFG_PPBCN_WBEN)) {
+ ppbcn &= ~TOPIC_PCI_CFG_PPBCN_WBEN;
+ pci_write_config_word(socket->dev, TOPIC_PCI_CFG_PPBCN, ppbcn);
+ dev_info(&socket->dev->dev, "Disabled ToPIC95 Cardbus write buffers.\n");
+ }
+
+ return 0;
+}
+
+#endif /* _LINUX_TOPIC_H */
diff --git a/drivers/pcmcia/vg468.h b/drivers/pcmcia/vg468.h
new file mode 100644
index 000000000..88c2b487f
--- /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 000000000..177d77892
--- /dev/null
+++ b/drivers/pcmcia/vrc4171_card.c
@@ -0,0 +1,745 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * vrc4171_card.c, NEC VRC4171 Card Controller driver for Socket Services.
+ *
+ * Copyright (C) 2003-2005 Yoichi Yuasa <yuasa@linux-mips.org>
+ */
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/platform_device.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@linux-mips.org>");
+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
+
+enum vrc4171_slot {
+ SLOT_PROBE = 0,
+ SLOT_NOPROBE_IO,
+ SLOT_NOPROBE_MEM,
+ SLOT_NOPROBE_ALL,
+ SLOT_INITIALIZED,
+};
+
+enum vrc4171_slotb {
+ SLOTB_IS_NONE,
+ SLOTB_IS_PCCARD,
+ SLOTB_IS_CF,
+ SLOTB_IS_FLASHROM,
+};
+
+struct vrc4171_socket {
+ enum vrc4171_slot slot;
+ struct pcmcia_socket pcmcia_socket;
+ char name[24];
+ int csc_irq;
+ int io_irq;
+ spinlock_t lock;
+};
+
+static struct vrc4171_socket vrc4171_sockets[CARD_MAX_SLOTS];
+static enum vrc4171_slotb 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(enum vrc4171_slotb 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)
+{
+ struct vrc4171_socket *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();
+ spin_lock_init(&socket->lock);
+
+ 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 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)
+{
+ struct vrc4171_socket *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(&socket->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(&socket->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,
+ .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 vrc4171_socket *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 vrc4171_add_sockets(void)
+{
+ struct vrc4171_socket *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.parent = &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)
+{
+ struct vrc4171_socket *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 vrc4171_card_setup(char *options)
+{
+ if (options == NULL || *options == '\0')
+ return 1;
+
+ 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 1;
+ 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 1;
+ options++;
+ } else
+ return 1;
+
+ }
+
+ 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 1;
+ 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 1;
+}
+
+__setup("vrc4171_card=", vrc4171_card_setup);
+
+static struct platform_driver vrc4171_card_driver = {
+ .driver = {
+ .name = vrc4171_card_name,
+ },
+};
+
+static int vrc4171_card_init(void)
+{
+ int retval;
+
+ retval = platform_driver_register(&vrc4171_card_driver);
+ if (retval < 0)
+ return retval;
+
+ retval = platform_device_register(&vrc4171_card_device);
+ if (retval < 0) {
+ platform_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, IRQF_SHARED,
+ vrc4171_card_name, vrc4171_sockets);
+
+ if (retval < 0) {
+ vrc4171_remove_sockets();
+ platform_device_unregister(&vrc4171_card_device);
+ platform_driver_unregister(&vrc4171_card_driver);
+ return retval;
+ }
+
+ printk(KERN_INFO "%s, connected to IRQ %d\n",
+ vrc4171_card_driver.driver.name, vrc4171_irq);
+
+ return 0;
+}
+
+static void vrc4171_card_exit(void)
+{
+ free_irq(vrc4171_irq, vrc4171_sockets);
+ vrc4171_remove_sockets();
+ platform_device_unregister(&vrc4171_card_device);
+ platform_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 000000000..9fb0c3add
--- /dev/null
+++ b/drivers/pcmcia/vrc4173_cardu.c
@@ -0,0 +1,591 @@
+/*
+ * 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@linux-mips.org>
+ *
+ * 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@linux-mips.org>");
+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 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,
+ .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)
+{
+ 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 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, ret;
+
+ 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) {
+ ret = -ENODEV;
+ goto disable;
+ }
+
+ len = pci_resource_len(dev, 0);
+ if (len == 0) {
+ ret = -ENODEV;
+ goto disable;
+ }
+
+ flags = pci_resource_flags(dev, 0);
+ if ((flags & IORESOURCE_MEM) == 0) {
+ ret = -EBUSY;
+ goto disable;
+ }
+
+ err = pci_request_regions(dev, socket->name);
+ if (err < 0) {
+ ret = err;
+ goto disable;
+ }
+
+ socket->base = ioremap(start, len);
+ if (socket->base == NULL) {
+ ret = -ENODEV;
+ goto release;
+ }
+
+ socket->dev = dev;
+
+ socket->pcmcia_socket = pcmcia_register_socket(slot, &cardu_operations, 1);
+ if (socket->pcmcia_socket == NULL) {
+ ret = -ENOMEM;
+ goto unmap;
+ }
+
+ if (request_irq(dev->irq, cardu_interrupt, IRQF_SHARED, socket->name, socket) < 0) {
+ ret = -EBUSY;
+ goto unregister;
+ }
+
+ printk(KERN_INFO "%s at %#08lx, IRQ %d\n", socket->name, start, dev->irq);
+
+ return 0;
+
+unregister:
+ pcmcia_unregister_socket(socket->pcmcia_socket);
+ socket->pcmcia_socket = NULL;
+unmap:
+ iounmap(socket->base);
+ socket->base = NULL;
+release:
+ pci_release_regions(dev);
+disable:
+ pci_disable_device(dev);
+ return ret;
+}
+
+static int vrc4173_cardu_setup(char *options)
+{
+ if (options == NULL || *options == '\0')
+ return 1;
+
+ 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 1;
+ } else
+ return 1;
+ }
+
+ if (strncmp(options, "cardu2:", 7) == 0) {
+ options += 7;
+ if ((*options != '\0') && (strncmp(options, "noprobe", 7) == 0))
+ cardu_sockets[CARDU2].noprobe = 1;
+ }
+
+ return 1;
+}
+
+__setup("vrc4173_cardu=", vrc4173_cardu_setup);
+
+static const struct pci_device_id vrc4173_cardu_id_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_NEC_NAPCCARD) },
+ {0, }
+};
+
+static struct pci_driver vrc4173_cardu_driver = {
+ .name = "NEC VRC4173 CARDU",
+ .probe = vrc4173_cardu_probe,
+ .id_table = vrc4173_cardu_id_table,
+};
+
+static int vrc4173_cardu_init(void)
+{
+ vrc4173_cardu_slots = 0;
+
+ return pci_register_driver(&vrc4173_cardu_driver);
+}
+
+static void 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 000000000..a7d96018e
--- /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@linux-mips.org>
+ *
+ * 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/xxs1500_ss.c b/drivers/pcmcia/xxs1500_ss.c
new file mode 100644
index 000000000..b11c7abb1
--- /dev/null
+++ b/drivers/pcmcia/xxs1500_ss.c
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PCMCIA socket code for the MyCable XXS1500 system.
+ *
+ * Copyright (c) 2009 Manuel Lauss <manuel.lauss@gmail.com>
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/resource.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <pcmcia/ss.h>
+#include <pcmcia/cistpl.h>
+
+#include <asm/irq.h>
+#include <asm/mach-au1x00/au1000.h>
+
+#define MEM_MAP_SIZE 0x400000
+#define IO_MAP_SIZE 0x1000
+
+
+/*
+ * 3.3V cards only; all interfacing is done via gpios:
+ *
+ * 0/1: carddetect (00 = card present, xx = huh)
+ * 4: card irq
+ * 204: reset (high-act)
+ * 205: buffer enable (low-act)
+ * 208/209: card voltage key (00,01,10,11)
+ * 210: battwarn
+ * 211: batdead
+ * 214: power (low-act)
+ */
+#define GPIO_CDA 0
+#define GPIO_CDB 1
+#define GPIO_CARDIRQ 4
+#define GPIO_RESET 204
+#define GPIO_OUTEN 205
+#define GPIO_VSL 208
+#define GPIO_VSH 209
+#define GPIO_BATTDEAD 210
+#define GPIO_BATTWARN 211
+#define GPIO_POWER 214
+
+struct xxs1500_pcmcia_sock {
+ struct pcmcia_socket socket;
+ void *virt_io;
+
+ phys_addr_t phys_io;
+ phys_addr_t phys_attr;
+ phys_addr_t phys_mem;
+
+ /* previous flags for set_socket() */
+ unsigned int old_flags;
+};
+
+#define to_xxs_socket(x) container_of(x, struct xxs1500_pcmcia_sock, socket)
+
+static irqreturn_t cdirq(int irq, void *data)
+{
+ struct xxs1500_pcmcia_sock *sock = data;
+
+ pcmcia_parse_events(&sock->socket, SS_DETECT);
+
+ return IRQ_HANDLED;
+}
+
+static int xxs1500_pcmcia_configure(struct pcmcia_socket *skt,
+ struct socket_state_t *state)
+{
+ struct xxs1500_pcmcia_sock *sock = to_xxs_socket(skt);
+ unsigned int changed;
+
+ /* power control */
+ switch (state->Vcc) {
+ case 0:
+ gpio_set_value(GPIO_POWER, 1); /* power off */
+ break;
+ case 33:
+ gpio_set_value(GPIO_POWER, 0); /* power on */
+ break;
+ case 50:
+ default:
+ return -EINVAL;
+ }
+
+ changed = state->flags ^ sock->old_flags;
+
+ if (changed & SS_RESET) {
+ if (state->flags & SS_RESET) {
+ gpio_set_value(GPIO_RESET, 1); /* assert reset */
+ gpio_set_value(GPIO_OUTEN, 1); /* buffers off */
+ } else {
+ gpio_set_value(GPIO_RESET, 0); /* deassert reset */
+ gpio_set_value(GPIO_OUTEN, 0); /* buffers on */
+ msleep(500);
+ }
+ }
+
+ sock->old_flags = state->flags;
+
+ return 0;
+}
+
+static int xxs1500_pcmcia_get_status(struct pcmcia_socket *skt,
+ unsigned int *value)
+{
+ unsigned int status;
+ int i;
+
+ status = 0;
+
+ /* check carddetects: GPIO[0:1] must both be low */
+ if (!gpio_get_value(GPIO_CDA) && !gpio_get_value(GPIO_CDB))
+ status |= SS_DETECT;
+
+ /* determine card voltage: GPIO[208:209] binary value */
+ i = (!!gpio_get_value(GPIO_VSL)) | ((!!gpio_get_value(GPIO_VSH)) << 1);
+
+ switch (i) {
+ case 0:
+ case 1:
+ case 2:
+ status |= SS_3VCARD; /* 3V card */
+ break;
+ case 3: /* 5V card, unsupported */
+ default:
+ status |= SS_XVCARD; /* treated as unsupported in core */
+ }
+
+ /* GPIO214: low active power switch */
+ status |= gpio_get_value(GPIO_POWER) ? 0 : SS_POWERON;
+
+ /* GPIO204: high-active reset line */
+ status |= gpio_get_value(GPIO_RESET) ? SS_RESET : SS_READY;
+
+ /* other stuff */
+ status |= gpio_get_value(GPIO_BATTDEAD) ? 0 : SS_BATDEAD;
+ status |= gpio_get_value(GPIO_BATTWARN) ? 0 : SS_BATWARN;
+
+ *value = status;
+
+ return 0;
+}
+
+static int xxs1500_pcmcia_sock_init(struct pcmcia_socket *skt)
+{
+ gpio_direction_input(GPIO_CDA);
+ gpio_direction_input(GPIO_CDB);
+ gpio_direction_input(GPIO_VSL);
+ gpio_direction_input(GPIO_VSH);
+ gpio_direction_input(GPIO_BATTDEAD);
+ gpio_direction_input(GPIO_BATTWARN);
+ gpio_direction_output(GPIO_RESET, 1); /* assert reset */
+ gpio_direction_output(GPIO_OUTEN, 1); /* disable buffers */
+ gpio_direction_output(GPIO_POWER, 1); /* power off */
+
+ return 0;
+}
+
+static int xxs1500_pcmcia_sock_suspend(struct pcmcia_socket *skt)
+{
+ return 0;
+}
+
+static int au1x00_pcmcia_set_io_map(struct pcmcia_socket *skt,
+ struct pccard_io_map *map)
+{
+ struct xxs1500_pcmcia_sock *sock = to_xxs_socket(skt);
+
+ map->start = (u32)sock->virt_io;
+ map->stop = map->start + IO_MAP_SIZE;
+
+ return 0;
+}
+
+static int au1x00_pcmcia_set_mem_map(struct pcmcia_socket *skt,
+ struct pccard_mem_map *map)
+{
+ struct xxs1500_pcmcia_sock *sock = to_xxs_socket(skt);
+
+ if (map->flags & MAP_ATTRIB)
+ map->static_start = sock->phys_attr + map->card_start;
+ else
+ map->static_start = sock->phys_mem + map->card_start;
+
+ return 0;
+}
+
+static struct pccard_operations xxs1500_pcmcia_operations = {
+ .init = xxs1500_pcmcia_sock_init,
+ .suspend = xxs1500_pcmcia_sock_suspend,
+ .get_status = xxs1500_pcmcia_get_status,
+ .set_socket = xxs1500_pcmcia_configure,
+ .set_io_map = au1x00_pcmcia_set_io_map,
+ .set_mem_map = au1x00_pcmcia_set_mem_map,
+};
+
+static int xxs1500_pcmcia_probe(struct platform_device *pdev)
+{
+ struct xxs1500_pcmcia_sock *sock;
+ struct resource *r;
+ int ret, irq;
+
+ sock = kzalloc(sizeof(struct xxs1500_pcmcia_sock), GFP_KERNEL);
+ if (!sock)
+ return -ENOMEM;
+
+ ret = -ENODEV;
+
+ /* 36bit PCMCIA Attribute area address */
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcmcia-attr");
+ if (!r) {
+ dev_err(&pdev->dev, "missing 'pcmcia-attr' resource!\n");
+ goto out0;
+ }
+ sock->phys_attr = r->start;
+
+ /* 36bit PCMCIA Memory area address */
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcmcia-mem");
+ if (!r) {
+ dev_err(&pdev->dev, "missing 'pcmcia-mem' resource!\n");
+ goto out0;
+ }
+ sock->phys_mem = r->start;
+
+ /* 36bit PCMCIA IO area address */
+ r = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pcmcia-io");
+ if (!r) {
+ dev_err(&pdev->dev, "missing 'pcmcia-io' resource!\n");
+ goto out0;
+ }
+ sock->phys_io = r->start;
+
+
+ /*
+ * 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.
+ */
+ sock->virt_io = (void *)(ioremap(sock->phys_io, IO_MAP_SIZE) -
+ mips_io_port_base);
+
+ if (!sock->virt_io) {
+ dev_err(&pdev->dev, "cannot remap IO area\n");
+ ret = -ENOMEM;
+ goto out0;
+ }
+
+ sock->socket.ops = &xxs1500_pcmcia_operations;
+ sock->socket.owner = THIS_MODULE;
+ sock->socket.pci_irq = gpio_to_irq(GPIO_CARDIRQ);
+ sock->socket.features = SS_CAP_STATIC_MAP | SS_CAP_PCCARD;
+ sock->socket.map_size = MEM_MAP_SIZE;
+ sock->socket.io_offset = (unsigned long)sock->virt_io;
+ sock->socket.dev.parent = &pdev->dev;
+ sock->socket.resource_ops = &pccard_static_ops;
+
+ platform_set_drvdata(pdev, sock);
+
+ /* setup carddetect irq: use one of the 2 GPIOs as an
+ * edge detector.
+ */
+ irq = gpio_to_irq(GPIO_CDA);
+ irq_set_irq_type(irq, IRQ_TYPE_EDGE_BOTH);
+ ret = request_irq(irq, cdirq, 0, "pcmcia_carddetect", sock);
+ if (ret) {
+ dev_err(&pdev->dev, "cannot setup cd irq\n");
+ goto out1;
+ }
+
+ ret = pcmcia_register_socket(&sock->socket);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register\n");
+ goto out2;
+ }
+
+ printk(KERN_INFO "MyCable XXS1500 PCMCIA socket services\n");
+
+ return 0;
+
+out2:
+ free_irq(gpio_to_irq(GPIO_CDA), sock);
+out1:
+ iounmap((void *)(sock->virt_io + (u32)mips_io_port_base));
+out0:
+ kfree(sock);
+ return ret;
+}
+
+static int xxs1500_pcmcia_remove(struct platform_device *pdev)
+{
+ struct xxs1500_pcmcia_sock *sock = platform_get_drvdata(pdev);
+
+ pcmcia_unregister_socket(&sock->socket);
+ free_irq(gpio_to_irq(GPIO_CDA), sock);
+ iounmap((void *)(sock->virt_io + (u32)mips_io_port_base));
+ kfree(sock);
+
+ return 0;
+}
+
+static struct platform_driver xxs1500_pcmcia_socket_driver = {
+ .driver = {
+ .name = "xxs1500_pcmcia",
+ },
+ .probe = xxs1500_pcmcia_probe,
+ .remove = xxs1500_pcmcia_remove,
+};
+
+module_platform_driver(xxs1500_pcmcia_socket_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("PCMCIA Socket Services for MyCable XXS1500 systems");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/drivers/pcmcia/yenta_socket.c b/drivers/pcmcia/yenta_socket.c
new file mode 100644
index 000000000..84bfc0e85
--- /dev/null
+++ b/drivers/pcmcia/yenta_socket.c
@@ -0,0 +1,1458 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 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/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <pcmcia/ss.h>
+
+#include "yenta_socket.h"
+#include "i82365.h"
+
+static bool disable_clkrun;
+module_param(disable_clkrun, bool, 0444);
+MODULE_PARM_DESC(disable_clkrun,
+ "If PC card doesn't function properly, please try this option (TI and Ricoh bridges only)");
+
+static bool isa_probe = 1;
+module_param(isa_probe, bool, 0444);
+MODULE_PARM_DESC(isa_probe, "If set ISA interrupts are probed (default). Set to N to disable probing");
+
+static bool pwr_irqs_off;
+module_param(pwr_irqs_off, bool, 0644);
+MODULE_PARM_DESC(pwr_irqs_off, "Force IRQs off during power-on of slot. Use only when seeing IRQ storms!");
+
+static char o2_speedup[] = "default";
+module_param_string(o2_speedup, o2_speedup, sizeof(o2_speedup), 0444);
+MODULE_PARM_DESC(o2_speedup, "Use prefetch/burst for O2-bridges: 'on', 'off' "
+ "or 'default' (uses recommended behaviour for the detected bridge)");
+
+/*
+ * 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;
+
+
+#define debug(x, s, args...) dev_dbg(&s->dev->dev, x, ##args)
+
+/* Don't ask.. */
+#define to_cycles(ns) ((ns)/120)
+#define to_ns(cycles) ((cycles)*120)
+
+/*
+ * yenta PCI irq probing.
+ * currently only used in the TI/EnE initialization code
+ */
+#ifdef CONFIG_YENTA_TI
+static int yenta_probe_cb_irq(struct yenta_socket *socket);
+static unsigned int yenta_probe_irq(struct yenta_socket *socket,
+ u32 isa_irq_mask);
+#endif
+
+
+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("%04x %08x\n", socket, reg, val);
+ return val;
+}
+
+static inline void cb_writel(struct yenta_socket *socket, unsigned reg, u32 val)
+{
+ debug("%04x %08x\n", socket, reg, val);
+ writel(val, socket->base + reg);
+ readl(socket->base + reg); /* avoid problems with PCI write posting */
+}
+
+static inline u8 config_readb(struct yenta_socket *socket, unsigned offset)
+{
+ u8 val;
+ pci_read_config_byte(socket->dev, offset, &val);
+ debug("%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("%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("%04x %08x\n", socket, offset, val);
+ return val;
+}
+
+static inline void config_writeb(struct yenta_socket *socket, unsigned offset, u8 val)
+{
+ debug("%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("%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("%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("%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("%04x %04x\n", socket, reg, val);
+ return val;
+}
+
+static inline void exca_writeb(struct yenta_socket *socket, unsigned reg, u8 val)
+{
+ debug("%04x %02x\n", socket, reg, val);
+ writeb(val, socket->base + 0x800 + reg);
+ readb(socket->base + 0x800 + reg); /* PCI write posting... */
+}
+
+static void exca_writew(struct yenta_socket *socket, unsigned reg, u16 val)
+{
+ debug("%04x %04x\n", socket, reg, val);
+ writeb(val, socket->base + 0x800 + reg);
+ writeb(val >> 8, socket->base + 0x800 + reg + 1);
+
+ /* PCI write posting... */
+ readb(socket->base + 0x800 + reg);
+ readb(socket->base + 0x800 + reg + 1);
+}
+
+static ssize_t show_yenta_registers(struct device *yentadev, struct device_attribute *attr, char *buf)
+{
+ struct yenta_socket *socket = dev_get_drvdata(yentadev);
+ int offset = 0, i;
+
+ offset = snprintf(buf, PAGE_SIZE, "CB registers:");
+ for (i = 0; i < 0x24; i += 4) {
+ unsigned val;
+ if (!(i & 15))
+ offset += scnprintf(buf + offset, PAGE_SIZE - offset, "\n%02x:", i);
+ val = cb_readl(socket, i);
+ offset += scnprintf(buf + offset, PAGE_SIZE - offset, " %08x", val);
+ }
+
+ offset += scnprintf(buf + offset, PAGE_SIZE - offset, "\n\nExCA registers:");
+ for (i = 0; i < 0x45; i++) {
+ unsigned char val;
+ if (!(i & 7)) {
+ if (i & 8) {
+ memcpy(buf + offset, " -", 2);
+ offset += 2;
+ } else
+ offset += scnprintf(buf + offset, PAGE_SIZE - offset, "\n%02x:", i);
+ }
+ val = exca_readb(socket, i);
+ offset += scnprintf(buf + offset, PAGE_SIZE - offset, " %02x", val);
+ }
+ buf[offset++] = '\n';
+ return offset;
+}
+
+static DEVICE_ATTR(yenta_registers, S_IRUSR, show_yenta_registers, NULL);
+
+/*
+ * 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_5VCARD | CB_3VCARD | CB_XVCARD | CB_YVCARD)) ? 0 : SS_PENDING;
+ val |= (state & (CB_CDETECT1 | CB_CDETECT2)) ? SS_PENDING : 0;
+
+
+ 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 if (state & CB_16BITCARD) {
+ 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 void yenta_set_power(struct yenta_socket *socket, socket_state_t *state)
+{
+ /* some birdges require to use the ExCA registers to power 16bit cards */
+ if (!(cb_readl(socket, CB_SOCKET_STATE) & CB_CBCARD) &&
+ (socket->flags & YENTA_16BIT_POWER_EXCA)) {
+ u8 reg, old;
+ reg = old = exca_readb(socket, I365_POWER);
+ reg &= ~(I365_VCC_MASK | I365_VPP1_MASK | I365_VPP2_MASK);
+
+ /* i82365SL-DF style */
+ if (socket->flags & YENTA_16BIT_POWER_DF) {
+ switch (state->Vcc) {
+ case 33:
+ reg |= I365_VCC_3V;
+ break;
+ case 50:
+ reg |= I365_VCC_5V;
+ break;
+ default:
+ reg = 0;
+ break;
+ }
+ switch (state->Vpp) {
+ case 33:
+ case 50:
+ reg |= I365_VPP1_5V;
+ break;
+ case 120:
+ reg |= I365_VPP1_12V;
+ break;
+ }
+ } else {
+ /* i82365SL-B style */
+ switch (state->Vcc) {
+ case 50:
+ reg |= I365_VCC_5V;
+ break;
+ default:
+ reg = 0;
+ break;
+ }
+ switch (state->Vpp) {
+ case 50:
+ reg |= I365_VPP1_5V | I365_VPP2_5V;
+ break;
+ case 120:
+ reg |= I365_VPP1_12V | I365_VPP2_12V;
+ break;
+ }
+ }
+
+ if (reg != old)
+ exca_writeb(socket, I365_POWER, reg);
+ } else {
+ 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;
+
+ /* if powering down: do it immediately */
+ if (state->Vcc == 0)
+ 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->dev->irq) {
+ intr |= socket->cb_irq ? socket->cb_irq : 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->dev->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 = exca_readb(socket, I365_CSCINT);
+ reg &= I365_CSC_IRQ_MASK;
+ 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);
+
+ /* if powering up: do it as the last step when the socket is configured */
+ if (state->Vcc != 0)
+ yenta_set_power(socket, state);
+ 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->bus, &region, 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 irqreturn_t yenta_interrupt(int irq, void *dev_id)
+{
+ unsigned int events;
+ 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, cb_event);
+
+ csc = exca_readb(socket, I365_CSC);
+
+ if (!(cb_event || csc))
+ return IRQ_NONE;
+
+ 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;
+ }
+
+ if (events)
+ pcmcia_parse_events(&socket->socket, events);
+
+ return IRQ_HANDLED;
+}
+
+static void yenta_interrupt_wrapper(struct timer_list *t)
+{
+ struct yenta_socket *socket = from_timer(socket, t, poll_timer);
+
+ yenta_interrupt(0, (void *)socket);
+ 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);
+ }
+}
+
+/* redoes voltage interrogation if required */
+static void yenta_interrogate(struct yenta_socket *socket)
+{
+ u32 state;
+
+ state = cb_readl(socket, CB_SOCKET_STATE);
+ if (!(state & (CB_5VCARD | CB_3VCARD | CB_XVCARD | CB_YVCARD)) ||
+ (state & (CB_CDETECT1 | CB_CDETECT2 | CB_NOTACARD | CB_BADVCCREQ)) ||
+ ((state & (CB_16BITCARD | CB_CBCARD)) == (CB_16BITCARD | CB_CBCARD)))
+ cb_writel(socket, CB_SOCKET_FORCE, CB_CVSTEST);
+}
+
+/* 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);
+
+ exca_writeb(socket, I365_GBLCTL, 0x00);
+ exca_writeb(socket, I365_GENCTL, 0x00);
+
+ /* Redo card voltage interrogation */
+ yenta_interrogate(socket);
+
+ 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. We try very hard to not get below
+ * the "ACC" values, though.
+ */
+#define BRIDGE_MEM_MAX (4*1024*1024)
+#define BRIDGE_MEM_ACC (128*1024)
+#define BRIDGE_MEM_MIN (16*1024)
+
+#define BRIDGE_IO_MAX 512
+#define BRIDGE_IO_ACC 256
+#define BRIDGE_IO_MIN 32
+
+#ifndef PCIBIOS_MIN_CARDBUS_IO
+#define PCIBIOS_MIN_CARDBUS_IO PCIBIOS_MIN_IO
+#endif
+
+static int yenta_search_one_res(struct resource *root, struct resource *res,
+ u32 min)
+{
+ u32 align, size, start, end;
+
+ if (res->flags & IORESOURCE_IO) {
+ align = 1024;
+ size = BRIDGE_IO_MAX;
+ 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 < min)
+ size = min;
+ align = size;
+ start = PCIBIOS_MIN_MEM;
+ end = ~0U;
+ }
+
+ do {
+ if (allocate_resource(root, res, size, start, end, align,
+ NULL, NULL) == 0) {
+ return 1;
+ }
+ size = size/2;
+ align = size;
+ } while (size >= min);
+
+ return 0;
+}
+
+
+static int yenta_search_res(struct yenta_socket *socket, struct resource *res,
+ u32 min)
+{
+ struct resource *root;
+ int i;
+
+ pci_bus_for_each_resource(socket->dev->bus, root, i) {
+ if (!root)
+ continue;
+
+ if ((res->flags ^ root->flags) &
+ (IORESOURCE_IO | IORESOURCE_MEM | IORESOURCE_PREFETCH))
+ continue; /* Wrong type */
+
+ if (yenta_search_one_res(root, res, min))
+ return 1;
+ }
+ return 0;
+}
+
+static int yenta_allocate_res(struct yenta_socket *socket, int nr, unsigned type, int addr_start, int addr_end)
+{
+ struct pci_dev *dev = socket->dev;
+ struct resource *res;
+ struct pci_bus_region region;
+ unsigned mask;
+
+ res = &dev->resource[nr];
+ /* Already allocated? */
+ if (res->parent)
+ return 0;
+
+ /* The granularity of the memory limit is 4kB, on IO it's 4 bytes */
+ mask = ~0xfff;
+ if (type & IORESOURCE_IO)
+ mask = ~3;
+
+ res->name = dev->subordinate->name;
+ res->flags = type;
+
+ region.start = config_readl(socket, addr_start) & mask;
+ region.end = config_readl(socket, addr_end) | ~mask;
+ if (region.start && region.end > region.start && !override_bios) {
+ pcibios_bus_to_resource(dev->bus, res, &region);
+ if (pci_claim_resource(dev, nr) == 0)
+ return 0;
+ dev_info(&dev->dev,
+ "Preassigned resource %d busy or not available, reconfiguring...\n",
+ nr);
+ }
+
+ if (type & IORESOURCE_IO) {
+ if ((yenta_search_res(socket, res, BRIDGE_IO_MAX)) ||
+ (yenta_search_res(socket, res, BRIDGE_IO_ACC)) ||
+ (yenta_search_res(socket, res, BRIDGE_IO_MIN)))
+ return 1;
+ } else {
+ if (type & IORESOURCE_PREFETCH) {
+ if ((yenta_search_res(socket, res, BRIDGE_MEM_MAX)) ||
+ (yenta_search_res(socket, res, BRIDGE_MEM_ACC)) ||
+ (yenta_search_res(socket, res, BRIDGE_MEM_MIN)))
+ return 1;
+ /* Approximating prefetchable by non-prefetchable */
+ res->flags = IORESOURCE_MEM;
+ }
+ if ((yenta_search_res(socket, res, BRIDGE_MEM_MAX)) ||
+ (yenta_search_res(socket, res, BRIDGE_MEM_ACC)) ||
+ (yenta_search_res(socket, res, BRIDGE_MEM_MIN)))
+ return 1;
+ }
+
+ dev_info(&dev->dev,
+ "no resource of type %x available, trying to continue...\n",
+ type);
+ res->start = res->end = res->flags = 0;
+ return 0;
+}
+
+static void yenta_free_res(struct yenta_socket *socket, int nr)
+{
+ struct pci_dev *dev = socket->dev;
+ struct resource *res;
+
+ res = &dev->resource[nr];
+ if (res->start != 0 && res->end != 0)
+ release_resource(res);
+
+ res->start = res->end = res->flags = 0;
+}
+
+/*
+ * Allocate the bridge mappings for the device..
+ */
+static void yenta_allocate_resources(struct yenta_socket *socket)
+{
+ int program = 0;
+ program += yenta_allocate_res(socket, PCI_CB_BRIDGE_IO_0_WINDOW,
+ IORESOURCE_IO,
+ PCI_CB_IO_BASE_0, PCI_CB_IO_LIMIT_0);
+ program += yenta_allocate_res(socket, PCI_CB_BRIDGE_IO_1_WINDOW,
+ IORESOURCE_IO,
+ PCI_CB_IO_BASE_1, PCI_CB_IO_LIMIT_1);
+ program += yenta_allocate_res(socket, PCI_CB_BRIDGE_MEM_0_WINDOW,
+ IORESOURCE_MEM | IORESOURCE_PREFETCH,
+ PCI_CB_MEMORY_BASE_0, PCI_CB_MEMORY_LIMIT_0);
+ program += yenta_allocate_res(socket, PCI_CB_BRIDGE_MEM_1_WINDOW,
+ IORESOURCE_MEM,
+ PCI_CB_MEMORY_BASE_1, PCI_CB_MEMORY_LIMIT_1);
+ if (program)
+ pci_setup_cardbus(socket->dev->subordinate);
+}
+
+
+/*
+ * Free the bridge mappings for the device..
+ */
+static void yenta_free_resources(struct yenta_socket *socket)
+{
+ yenta_free_res(socket, PCI_CB_BRIDGE_IO_0_WINDOW);
+ yenta_free_res(socket, PCI_CB_BRIDGE_IO_1_WINDOW);
+ yenta_free_res(socket, PCI_CB_BRIDGE_MEM_0_WINDOW);
+ yenta_free_res(socket, PCI_CB_BRIDGE_MEM_1_WINDOW);
+}
+
+
+/*
+ * 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);
+
+ /* Remove the register attributes */
+ device_remove_file(&dev->dev, &dev_attr_yenta_registers);
+
+ /* 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);
+
+ iounmap(sock->base);
+ yenta_free_resources(sock);
+
+ pci_release_regions(dev);
+ pci_disable_device(dev);
+ pci_set_drvdata(dev, NULL);
+ kfree(sock);
+}
+
+
+static struct pccard_operations yenta_socket_operations = {
+ .init = yenta_sock_init,
+ .suspend = yenta_sock_suspend,
+ .get_status = yenta_get_status,
+ .set_socket = yenta_set_socket,
+ .set_io_map = yenta_set_io_map,
+ .set_mem_map = yenta_set_mem_map,
+};
+
+
+#ifdef CONFIG_YENTA_TI
+#include "ti113x.h"
+#endif
+#ifdef CONFIG_YENTA_RICOH
+#include "ricoh.h"
+#endif
+#ifdef CONFIG_YENTA_TOSHIBA
+#include "topic.h"
+#endif
+#ifdef CONFIG_YENTA_O2
+#include "o2micro.h"
+#endif
+
+enum {
+ CARDBUS_TYPE_DEFAULT = -1,
+ CARDBUS_TYPE_TI,
+ CARDBUS_TYPE_TI113X,
+ CARDBUS_TYPE_TI12XX,
+ CARDBUS_TYPE_TI1250,
+ CARDBUS_TYPE_RICOH,
+ CARDBUS_TYPE_TOPIC95,
+ CARDBUS_TYPE_TOPIC97,
+ CARDBUS_TYPE_O2MICRO,
+ CARDBUS_TYPE_ENE,
+};
+
+/*
+ * Different cardbus controllers have slightly different
+ * initialization sequences etc details. List them here..
+ */
+static struct cardbus_type cardbus_type[] = {
+#ifdef CONFIG_YENTA_TI
+ [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_ENE] = {
+ .override = ene_override,
+ .save_state = ti_save_state,
+ .restore_state = ti_restore_state,
+ .sock_init = ti_init,
+ },
+#endif
+#ifdef CONFIG_YENTA_RICOH
+ [CARDBUS_TYPE_RICOH] = {
+ .override = ricoh_override,
+ .save_state = ricoh_save_state,
+ .restore_state = ricoh_restore_state,
+ },
+#endif
+#ifdef CONFIG_YENTA_TOSHIBA
+ [CARDBUS_TYPE_TOPIC95] = {
+ .override = topic95_override,
+ },
+ [CARDBUS_TYPE_TOPIC97] = {
+ .override = topic97_override,
+ },
+#endif
+#ifdef CONFIG_YENTA_O2
+ [CARDBUS_TYPE_O2MICRO] = {
+ .override = o2micro_override,
+ .restore_state = o2micro_restore_state,
+ },
+#endif
+};
+
+
+static unsigned int yenta_probe_irq(struct yenta_socket *socket, u32 isa_irq_mask)
+{
+ int i;
+ unsigned long val;
+ u32 mask;
+ u8 reg;
+
+ /*
+ * 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);
+ reg = exca_readb(socket, I365_CSCINT);
+ 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, reg);
+
+ mask = probe_irq_mask(val) & 0xffff;
+
+ return mask;
+}
+
+
+/*
+ * yenta PCI irq probing.
+ * currently only used in the TI/EnE initialization code
+ */
+#ifdef CONFIG_YENTA_TI
+
+/* interrupt handler, only used during probing */
+static irqreturn_t yenta_probe_handler(int irq, void *dev_id)
+{
+ 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)
+{
+ u8 reg = 0;
+
+ if (!socket->cb_irq)
+ return -1;
+
+ socket->probe_status = 0;
+
+ if (request_irq(socket->cb_irq, yenta_probe_handler, IRQF_SHARED, "yenta", socket)) {
+ dev_warn(&socket->dev->dev,
+ "request_irq() in yenta_probe_cb_irq() failed!\n");
+ return -1;
+ }
+
+ /* generate interrupt, wait */
+ if (!socket->dev->irq)
+ reg = exca_readb(socket, I365_CSCINT);
+ exca_writeb(socket, I365_CSCINT, reg | 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, reg);
+ cb_writel(socket, CB_SOCKET_EVENT, -1);
+ exca_readb(socket, I365_CSC);
+
+ free_irq(socket->cb_irq, socket);
+
+ return (int) socket->probe_status;
+}
+
+#endif /* CONFIG_YENTA_TI */
+
+
+/*
+ * 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.pci_irq = socket->cb_irq;
+ if (isa_probe)
+ socket->socket.irq_mask = yenta_probe_irq(socket, isa_irq_mask);
+ else
+ socket->socket.irq_mask = 0;
+
+ dev_info(&socket->dev->dev, "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;
+ struct pci_bus_region region;
+
+ pcibios_resource_to_bus(socket->dev->bus, &region, &dev->resource[0]);
+
+ config_writel(socket, CB_LEGACY_MODE_BASE, 0);
+ config_writel(socket, PCI_BASE_ADDRESS_0, region.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 */
+ ((unsigned int)dev->subordinate->busn_res.end << 16) | /* subordinate bus */
+ ((unsigned int)dev->subordinate->busn_res.start << 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_ISAEN | CB_BRIDGE_VGAEN);
+ bridge |= CB_BRIDGE_PREFETCH0 | CB_BRIDGE_POSTEN;
+ config_writew(socket, CB_BRIDGE_CONTROL, bridge);
+}
+
+/**
+ * yenta_fixup_parent_bridge - Fix subordinate bus# of the parent bridge
+ * @cardbus_bridge: The PCI bus which the CardBus bridge bridges to
+ *
+ * Checks if devices on the bus which the CardBus bridge bridges to would be
+ * invisible during PCI scans because of a misconfigured subordinate number
+ * of the parent brige - some BIOSes seem to be too lazy to set it right.
+ * Does the fixup carefully by checking how far it can go without conflicts.
+ * See http://bugzilla.kernel.org/show_bug.cgi?id=2944 for more information.
+ */
+static void yenta_fixup_parent_bridge(struct pci_bus *cardbus_bridge)
+{
+ struct pci_bus *sibling;
+ unsigned char upper_limit;
+ /*
+ * We only check and fix the parent bridge: All systems which need
+ * this fixup that have been reviewed are laptops and the only bridge
+ * which needed fixing was the parent bridge of the CardBus bridge:
+ */
+ struct pci_bus *bridge_to_fix = cardbus_bridge->parent;
+
+ /* Check bus numbers are already set up correctly: */
+ if (bridge_to_fix->busn_res.end >= cardbus_bridge->busn_res.end)
+ return; /* The subordinate number is ok, nothing to do */
+
+ if (!bridge_to_fix->parent)
+ return; /* Root bridges are ok */
+
+ /* stay within the limits of the bus range of the parent: */
+ upper_limit = bridge_to_fix->parent->busn_res.end;
+
+ /* check the bus ranges of all sibling bridges to prevent overlap */
+ list_for_each_entry(sibling, &bridge_to_fix->parent->children,
+ node) {
+ /*
+ * If the sibling has a higher secondary bus number
+ * and it's secondary is equal or smaller than our
+ * current upper limit, set the new upper limit to
+ * the bus number below the sibling's range:
+ */
+ if (sibling->busn_res.start > bridge_to_fix->busn_res.end
+ && sibling->busn_res.start <= upper_limit)
+ upper_limit = sibling->busn_res.start - 1;
+ }
+
+ /* Show that the wanted subordinate number is not possible: */
+ if (cardbus_bridge->busn_res.end > upper_limit)
+ dev_warn(&cardbus_bridge->dev,
+ "Upper limit for fixing this bridge's parent bridge: #%02x\n",
+ upper_limit);
+
+ /* If we have room to increase the bridge's subordinate number, */
+ if (bridge_to_fix->busn_res.end < upper_limit) {
+
+ /* use the highest number of the hidden bus, within limits */
+ unsigned char subordinate_to_assign =
+ min_t(int, cardbus_bridge->busn_res.end, upper_limit);
+
+ dev_info(&bridge_to_fix->dev,
+ "Raising subordinate bus# of parent bus (#%02x) from #%02x to #%02x\n",
+ bridge_to_fix->number,
+ (int)bridge_to_fix->busn_res.end,
+ subordinate_to_assign);
+
+ /* Save the new subordinate in the bus struct of the bridge */
+ bridge_to_fix->busn_res.end = subordinate_to_assign;
+
+ /* and update the PCI config space with the new subordinate */
+ pci_write_config_byte(bridge_to_fix->self,
+ PCI_SUBORDINATE_BUS, bridge_to_fix->busn_res.end);
+ }
+}
+
+/*
+ * 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 yenta_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+ struct yenta_socket *socket;
+ int ret;
+
+ /*
+ * If we failed to assign proper bus numbers for this cardbus
+ * controller during PCI probe, its subordinate pci_bus is NULL.
+ * Bail out if so.
+ */
+ if (!dev->subordinate) {
+ dev_err(&dev->dev, "no bus associated! (try 'pci=assign-busses')\n");
+ return -ENODEV;
+ }
+
+ socket = kzalloc(sizeof(struct yenta_socket), GFP_KERNEL);
+ if (!socket)
+ return -ENOMEM;
+
+ /* prepare pcmcia_socket */
+ socket->socket.ops = &yenta_socket_operations;
+ socket->socket.resource_ops = &pccard_nonstatic_ops;
+ socket->socket.dev.parent = &dev->dev;
+ socket->socket.driver_data = socket;
+ socket->socket.owner = THIS_MODULE;
+ socket->socket.features = SS_CAP_PAGE_REGS | SS_CAP_PCCARD;
+ socket->socket.map_size = 0x1000;
+ socket->socket.cb_dev = dev;
+
+ /* 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)) {
+ dev_err(&dev->dev, "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...
+ */
+ dev_info(&dev->dev, "CardBus bridge found [%04x:%04x]\n",
+ 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, IRQF_SHARED, "yenta", socket)) {
+ /* No IRQ or request_irq failed. Poll */
+ socket->cb_irq = 0; /* But zero is a valid IRQ number. */
+ timer_setup(&socket->poll_timer, yenta_interrupt_wrapper, 0);
+ mod_timer(&socket->poll_timer, jiffies + HZ);
+ dev_info(&dev->dev,
+ "no PCI IRQ, CardBus support disabled for this socket.\n");
+ dev_info(&dev->dev,
+ "check your BIOS CardBus, BIOS IRQ or ACPI settings.\n");
+ } else {
+ socket->socket.features |= SS_CAP_CARDBUS;
+ }
+
+ /* Figure out what the dang thing can do for the PCMCIA layer... */
+ yenta_interrogate(socket);
+ yenta_get_socket_capabilities(socket, isa_interrupts);
+ dev_info(&dev->dev, "Socket status: %08x\n",
+ cb_readl(socket, CB_SOCKET_STATE));
+
+ yenta_fixup_parent_bridge(dev->subordinate);
+
+ /* Register it with the pcmcia layer.. */
+ ret = pcmcia_register_socket(&socket->socket);
+ if (ret)
+ goto free_irq;
+
+ /* Add the yenta register attributes */
+ ret = device_create_file(&dev->dev, &dev_attr_yenta_registers);
+ if (ret)
+ goto unregister_socket;
+
+ return ret;
+
+ /* error path... */
+ unregister_socket:
+ pcmcia_unregister_socket(&socket->socket);
+ free_irq:
+ if (socket->cb_irq)
+ free_irq(socket->cb_irq, socket);
+ else
+ del_timer_sync(&socket->poll_timer);
+ unmap:
+ iounmap(socket->base);
+ yenta_free_resources(socket);
+ release:
+ pci_release_regions(dev);
+ disable:
+ pci_disable_device(dev);
+ free:
+ pci_set_drvdata(dev, NULL);
+ kfree(socket);
+ return ret;
+}
+
+#ifdef CONFIG_PM
+static int yenta_dev_suspend_noirq(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct yenta_socket *socket = pci_get_drvdata(pdev);
+
+ if (!socket)
+ return 0;
+
+ if (socket->type && socket->type->save_state)
+ socket->type->save_state(socket);
+
+ pci_save_state(pdev);
+ pci_read_config_dword(pdev, 16*4, &socket->saved_state[0]);
+ pci_read_config_dword(pdev, 17*4, &socket->saved_state[1]);
+ pci_disable_device(pdev);
+
+ return 0;
+}
+
+static int yenta_dev_resume_noirq(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct yenta_socket *socket = pci_get_drvdata(pdev);
+ int ret;
+
+ if (!socket)
+ return 0;
+
+ pci_write_config_dword(pdev, 16*4, socket->saved_state[0]);
+ pci_write_config_dword(pdev, 17*4, socket->saved_state[1]);
+
+ ret = pci_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ pci_set_master(pdev);
+
+ if (socket->type && socket->type->restore_state)
+ socket->type->restore_state(socket);
+
+ return 0;
+}
+
+static const struct dev_pm_ops yenta_pm_ops = {
+ .suspend_noirq = yenta_dev_suspend_noirq,
+ .resume_noirq = yenta_dev_resume_noirq,
+ .freeze_noirq = yenta_dev_suspend_noirq,
+ .thaw_noirq = yenta_dev_resume_noirq,
+ .poweroff_noirq = yenta_dev_suspend_noirq,
+ .restore_noirq = yenta_dev_resume_noirq,
+};
+
+#define YENTA_PM_OPS (&yenta_pm_ops)
+#else
+#define YENTA_PM_OPS NULL
+#endif
+
+#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 const 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)
+ */
+#ifdef CONFIG_YENTA_TI
+ 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_TI, PCI_DEVICE_ID_TI_XX21_XX11, TI12XX),
+ CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_X515, TI12XX),
+ CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_XX12, TI12XX),
+ CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_X420, TI12XX),
+ CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_X620, TI12XX),
+ CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_7410, TI12XX),
+ CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_7510, TI12XX),
+ CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_7610, TI12XX),
+
+ CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_710, ENE),
+ CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_712, ENE),
+ CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_720, ENE),
+ CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_722, ENE),
+ CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_1211, ENE),
+ CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_1225, ENE),
+ CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_1410, ENE),
+ CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_1420, ENE),
+#endif /* CONFIG_YENTA_TI */
+
+#ifdef CONFIG_YENTA_RICOH
+ 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),
+#endif
+
+#ifdef CONFIG_YENTA_TOSHIBA
+ CB_ID(PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_TOSHIBA_TOPIC95, TOPIC95),
+ CB_ID(PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_TOSHIBA_TOPIC97, TOPIC97),
+ CB_ID(PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_TOSHIBA_TOPIC100, TOPIC97),
+#endif
+
+#ifdef CONFIG_YENTA_O2
+ CB_ID(PCI_VENDOR_ID_O2, PCI_ANY_ID, O2MICRO),
+#endif
+
+ /* 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 = yenta_close,
+ .driver.pm = YENTA_PM_OPS,
+};
+
+module_pci_driver(yenta_cardbus_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/pcmcia/yenta_socket.h b/drivers/pcmcia/yenta_socket.h
new file mode 100644
index 000000000..efeed19e2
--- /dev/null
+++ b/drivers/pcmcia/yenta_socket.h
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#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))
+
+
+/* control how 16bit cards are powered */
+#define YENTA_16BIT_POWER_EXCA 0x00000001
+#define YENTA_16BIT_POWER_DF 0x00000002
+
+
+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;
+
+ u32 flags;
+
+ /* 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