diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 01:02:30 +0000 |
commit | 76cb841cb886eef6b3bee341a2266c76578724ad (patch) | |
tree | f5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /drivers/pcmcia | |
parent | Initial commit. (diff) | |
download | linux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip |
Adding upstream version 4.19.249.upstream/4.19.249
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/pcmcia')
71 files changed, 25205 insertions, 0 deletions
diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig new file mode 100644 index 000000000..a8fdd6df6 --- /dev/null +++ b/drivers/pcmcia/Kconfig @@ -0,0 +1,271 @@ +# +# PCCARD (PCMCIA/CardBus) bus subsystem configuration +# + +menuconfig PCCARD + tristate "PCCard (PCMCIA/CardBus) support" + ---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. + +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 + 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 + 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..28502bd15 --- /dev/null +++ b/drivers/pcmcia/Makefile @@ -0,0 +1,70 @@ +# 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 + +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..7da7b0f0a --- /dev/null +++ b/drivers/pcmcia/at91_cf.c @@ -0,0 +1,419 @@ +/* + * at91_cf.c -- AT91 CompactFlash controller driver + * + * Copyright (C) 2005 David Brownell + * + * 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. + */ + +#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..5c0170597 --- /dev/null +++ b/drivers/pcmcia/cardbus.c @@ -0,0 +1,125 @@ +/* + * cardbus.c -- 16-bit PCMCIA core support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + */ + +/* + * Cardbus handling has been re-written to be more of a PCI bridge thing, + * and the PCI code basically does all the resource handling. + * + * Linus, Jan 2000 + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> + +#include <pcmcia/ss.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..ac0672b8d --- /dev/null +++ b/drivers/pcmcia/cistpl.c @@ -0,0 +1,1607 @@ +/* + * cistpl.c -- 16-bit PCMCIA Card Information Structure parser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + */ + +#include <linux/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 <asm/byteorder.h> +#include <asm/unaligned.h> + +#include <pcmcia/ss.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" + +static const u_char mantissa[] = { + 10, 12, 13, 15, 20, 25, 30, 35, + 40, 45, 50, 55, 60, 70, 80, 90 +}; + +static const u_int exponent[] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000 +}; + +/* Convert an extended speed byte to a time in nanoseconds */ +#define SPEED_CVT(v) \ + (mantissa[(((v)>>3)&15)-1] * exponent[(v)&7] / 10) +/* Convert a power byte to a current in 0.1 microamps */ +#define POWER_CVT(v) \ + (mantissa[((v)>>3)&15] * exponent[(v)&7] / 10) +#define POWER_SCALE(v) (exponent[(v)&7]) + +/* Upper limit on reasonable # of tuples */ +#define MAX_TUPLES 200 + +/* 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; + + 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..182e5ef4a --- /dev/null +++ b/drivers/pcmcia/cs.c @@ -0,0 +1,921 @@ +/* + * cs.c -- Kernel Card Services - core services + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/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); + 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..03ec43802 --- /dev/null +++ b/drivers/pcmcia/cs_internal.h @@ -0,0 +1,185 @@ +/* + * cs_internal.h -- definitions internal to the PCMCIA core modules + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + * (C) 2003 - 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[0]; +}; + +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_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)); + +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..19e17829f --- /dev/null +++ b/drivers/pcmcia/db1xxx_ss.c @@ -0,0 +1,598 @@ +/* + * 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; + case 33: + ++v; + case 0: + break; + default: + printk(KERN_INFO "pcmcia%d unsupported Vcc %d\n", + sock->nr, state->Vcc); + } + + switch (state->Vpp) { + case 12: + ++p; + case 33: + case 50: + ++p; + 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..a9258f641 --- /dev/null +++ b/drivers/pcmcia/ds.c @@ -0,0 +1,1460 @@ +/* + * ds.c -- 16-bit PCMCIA core support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + * (C) 2003 - 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.txt 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 = DMA_MASK_NONE; + p_dev->dev.dma_mask = &p_dev->dma_mask; + dev_set_name(&p_dev->dev, "%d.%d", p_dev->socket->sock, p_dev->device_no); + if (!dev_name(&p_dev->dev)) + goto err_free; + 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); + + if (device_register(&p_dev->dev)) + goto err_unreg; + + 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..9671ded54 --- /dev/null +++ b/drivers/pcmcia/electra_cf.c @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2007 PA Semi, Inc + * + * Maintained by: Olof Johansson <olof@lixom.net> + * + * Based on drivers/pcmcia/omap_cf.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/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; + const unsigned int *prop; + int err; + struct vm_struct *area; + + 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); + cf->io_size = PAGE_ALIGN(resource_size(&io)); + + area = __get_vm_area(cf->io_size, 0, PHB_IO_BASE, PHB_IO_END); + if (area == NULL) { + status = -ENOMEM; + goto fail1; + } + + cf->io_virt = (void __iomem *)(area->addr); + + cf->gpio_base = ioremap(0xfc103000, 0x1000); + dev_set_drvdata(device, cf); + + if (!cf->mem_base || !cf->io_virt || !cf->gpio_base || + (__ioremap_at(io.start, cf->io_virt, cf->io_size, + pgprot_val(pgprot_noncached(__pgprot(0)))) == NULL)) { + dev_err(device, "can't ioremap ranges\n"); + status = -ENOMEM; + goto fail1; + } + + + 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); + + if (cf->io_virt) + __iounmap_at(cf->io_virt, cf->io_size); + if (cf->mem_base) + iounmap(cf->mem_base); + if (cf->gpio_base) + iounmap(cf->gpio_base); + if (area) + device_init_wakeup(&ofdev->dev, 0); + 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_at(cf->io_virt, cf->io_size); + 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..ba13e3c3d --- /dev/null +++ b/drivers/pcmcia/i82092.c @@ -0,0 +1,705 @@ +/* + * 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 <asm/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; + + enter("i82092aa_pci_probe"); + + if ((ret = pci_enable_device(dev))) + return ret; + + pci_read_config_byte(dev, 0x40, &configbyte); /* PCI Configuration Control */ + switch(configbyte&6) { + case 0: + socket_count = 2; + break; + case 2: + socket_count = 1; + break; + case 4: + case 6: + socket_count = 4; + break; + + default: + printk(KERN_ERR "i82092aa: Oops, you did something we didn't think of.\n"); + ret = -EIO; + goto err_out_disable; + } + printk(KERN_INFO "i82092aa: configured as a %d socket device.\n", socket_count); + + if (!request_region(pci_resource_start(dev, 0), 2, "i82092aa")) { + ret = -EBUSY; + goto err_out_disable; + } + + for (i = 0;i<socket_count;i++) { + sockets[i].card_state = 1; /* 1 = present but empty */ + sockets[i].io_base = pci_resource_start(dev, 0); + sockets[i].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; + dprintk(KERN_DEBUG "i82092aa: slot %i is occupied\n",i); + } else { + dprintk(KERN_DEBUG "i82092aa: slot %i is vacant\n",i); + } + } + + /* Now, specifiy that all interrupts are to be done as PCI interrupts */ + configbyte = 0xFF; /* bitmask, one bit per event, 1 = PCI interrupt, 0 = ISA interrupt */ + pci_write_config_byte(dev, 0x50, configbyte); /* PCI Interrupt Routing Register */ + + /* Register the interrupt handler */ + dprintk(KERN_DEBUG "Requesting interrupt %i \n",dev->irq); + if ((ret = request_irq(dev->irq, i82092aa_interrupt, IRQF_SHARED, "i82092aa", i82092aa_interrupt))) { + printk(KERN_ERR "i82092aa: 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; + } + } + + leave("i82092aa_pci_probe"); + return 0; + +err_out_free_sockets: + if (i) { + for (i--;i>=0;i--) { + pcmcia_unregister_socket(&sockets[i].socket); + } + } + free_irq(dev->irq, i82092aa_interrupt); +err_out_free_res: + release_region(pci_resource_start(dev, 0), 2); +err_out_disable: + pci_disable_device(dev); + return ret; +} + +static void i82092aa_pci_remove(struct pci_dev *dev) +{ + int i; + + enter("i82092aa_pci_remove"); + + free_irq(dev->irq, i82092aa_interrupt); + + for (i = 0; i < socket_count; i++) + pcmcia_unregister_socket(&sockets[i].socket); + + leave("i82092aa_pci_remove"); +} + +static DEFINE_SPINLOCK(port_lock); + +/* basic value read/write functions */ + +static unsigned char indirect_read(int socket, unsigned short reg) +{ + unsigned short int port; + unsigned char val; + unsigned long flags; + spin_lock_irqsave(&port_lock,flags); + reg += socket * 0x40; + port = sockets[socket].io_base; + outb(reg,port); + val = inb(port+1); + spin_unlock_irqrestore(&port_lock,flags); + return val; +} + +#if 0 +static unsigned short indirect_read16(int socket, unsigned short reg) +{ + unsigned short int port; + unsigned short tmp; + unsigned long flags; + spin_lock_irqsave(&port_lock,flags); + reg = reg + socket * 0x40; + port = sockets[socket].io_base; + outb(reg,port); + tmp = inb(port+1); + reg++; + outb(reg,port); + tmp = tmp | (inb(port+1)<<8); + spin_unlock_irqrestore(&port_lock,flags); + return tmp; +} +#endif + +static void indirect_write(int socket, unsigned short reg, unsigned char value) +{ + unsigned short int port; + unsigned long flags; + spin_lock_irqsave(&port_lock,flags); + reg = reg + socket * 0x40; + port = sockets[socket].io_base; + outb(reg,port); + outb(value,port+1); + spin_unlock_irqrestore(&port_lock,flags); +} + +static void indirect_setbit(int socket, unsigned short reg, unsigned char mask) +{ + unsigned short int port; + unsigned char val; + unsigned long flags; + spin_lock_irqsave(&port_lock,flags); + reg = reg + socket * 0x40; + port = sockets[socket].io_base; + outb(reg,port); + val = inb(port+1); + val |= mask; + outb(reg,port); + outb(val,port+1); + spin_unlock_irqrestore(&port_lock,flags); +} + + +static void indirect_resetbit(int socket, unsigned short reg, unsigned char mask) +{ + unsigned short int port; + unsigned char val; + unsigned long flags; + spin_lock_irqsave(&port_lock,flags); + reg = reg + socket * 0x40; + port = sockets[socket].io_base; + outb(reg,port); + val = inb(port+1); + val &= ~mask; + outb(reg,port); + outb(val,port+1); + spin_unlock_irqrestore(&port_lock,flags); +} + +static void indirect_write16(int socket, unsigned short reg, unsigned short value) +{ + unsigned short int port; + unsigned char val; + unsigned long flags; + spin_lock_irqsave(&port_lock,flags); + reg = reg + socket * 0x40; + port = sockets[socket].io_base; + + outb(reg,port); + val = value & 255; + outb(val,port+1); + + reg++; + + outb(reg,port); + val = value>>8; + outb(val,port+1); + spin_unlock_irqrestore(&port_lock,flags); +} + +/* simple helper functions */ +/* External clock time, in nanoseconds. 120 ns = 8.33 MHz */ +static int cycle_time = 120; + +static int to_cycles(int ns) +{ + if (cycle_time!=0) + return ns/cycle_time; + else + return 0; +} + + +/* Interrupt handler functionality */ + +static irqreturn_t i82092aa_interrupt(int irq, void *dev) +{ + int i; + int loopcount = 0; + int handled = 0; + + unsigned int events, active=0; + +/* enter("i82092aa_interrupt");*/ + + while (1) { + loopcount++; + if (loopcount>20) { + printk(KERN_ERR "i82092aa: infinite eventloop in interrupt \n"); + break; + } + + active = 0; + + for (i=0;i<socket_count;i++) { + int csc; + if (sockets[i].card_state==0) /* Inactive socket, should not happen */ + continue; + + csc = indirect_read(i,I365_CSC); /* card status change register */ + + if (csc==0) /* no events on this socket */ + continue; + handled = 1; + events = 0; + + if (csc & I365_CSC_DETECT) { + events |= SS_DETECT; + printk("Card detected in socket %i!\n",i); + } + + if (indirect_read(i,I365_INTCTL) & I365_PC_IOCARD) { + /* For IO/CARDS, bit 0 means "read the card" */ + events |= (csc & I365_CSC_STSCHG) ? SS_STSCHG : 0; + } else { + /* Check for battery/ready events */ + events |= (csc & I365_CSC_BVD1) ? SS_BATDEAD : 0; + events |= (csc & I365_CSC_BVD2) ? SS_BATWARN : 0; + events |= (csc & I365_CSC_READY) ? SS_READY : 0; + } + + if (events) { + pcmcia_parse_events(&sockets[i].socket, events); + } + active |= events; + } + + if (active==0) /* no more events to handle */ + break; + + } + return IRQ_RETVAL(handled); +/* leave("i82092aa_interrupt");*/ +} + + + +/* socket functions */ + +static int card_present(int socketno) +{ + unsigned int val; + enter("card_present"); + + if ((socketno<0) || (socketno >= MAX_SOCKETS)) + return 0; + if (sockets[socketno].io_base == 0) + return 0; + + + val = indirect_read(socketno, 1); /* Interface status register */ + if ((val&12)==12) { + leave("card_present 1"); + return 1; + } + + leave("card_present 0"); + return 0; +} + +static void set_bridge_state(int sock) +{ + enter("set_bridge_state"); + indirect_write(sock, I365_GBLCTL,0x00); + indirect_write(sock, I365_GENCTL,0x00); + + indirect_setbit(sock, I365_INTCTL,0x08); + leave("set_bridge_state"); +} + + + + + + +static int i82092aa_init(struct pcmcia_socket *sock) +{ + int i; + struct resource res = { .start = 0, .end = 0x0fff }; + pccard_io_map io = { 0, 0, 0, 0, 1 }; + pccard_mem_map mem = { .res = &res, }; + + enter("i82092aa_init"); + + for (i = 0; i < 2; i++) { + io.map = i; + i82092aa_set_io_map(sock, &io); + } + for (i = 0; i < 5; i++) { + mem.map = i; + i82092aa_set_mem_map(sock, &mem); + } + + leave("i82092aa_init"); + return 0; +} + +static int i82092aa_get_status(struct pcmcia_socket *socket, u_int *value) +{ + unsigned int sock = container_of(socket, struct socket_info, socket)->number; + unsigned int status; + + enter("i82092aa_get_status"); + + status = indirect_read(sock,I365_STATUS); /* Interface Status Register */ + *value = 0; + + if ((status & I365_CS_DETECT) == I365_CS_DETECT) { + *value |= SS_DETECT; + } + + /* IO cards have a different meaning of bits 0,1 */ + /* Also notice the inverse-logic on the bits */ + if (indirect_read(sock, I365_INTCTL) & I365_PC_IOCARD) { + /* IO card */ + if (!(status & I365_CS_STSCHG)) + *value |= SS_STSCHG; + } else { /* non I/O card */ + if (!(status & I365_CS_BVD1)) + *value |= SS_BATDEAD; + if (!(status & I365_CS_BVD2)) + *value |= SS_BATWARN; + + } + + if (status & I365_CS_WRPROT) + (*value) |= SS_WRPROT; /* card is write protected */ + + if (status & I365_CS_READY) + (*value) |= SS_READY; /* card is not busy */ + + if (status & I365_CS_POWERON) + (*value) |= SS_POWERON; /* power is applied to the card */ + + + leave("i82092aa_get_status"); + return 0; +} + + +static int i82092aa_set_socket(struct pcmcia_socket *socket, socket_state_t *state) +{ + unsigned int sock = container_of(socket, struct socket_info, socket)->number; + unsigned char reg; + + enter("i82092aa_set_socket"); + + /* First, set the global controller options */ + + set_bridge_state(sock); + + /* Values for the IGENC register */ + + reg = 0; + if (!(state->flags & SS_RESET)) /* The reset bit has "inverse" logic */ + reg = reg | I365_PC_RESET; + if (state->flags & SS_IOCARD) + reg = reg | I365_PC_IOCARD; + + indirect_write(sock,I365_INTCTL,reg); /* IGENC, Interrupt and General Control Register */ + + /* Power registers */ + + reg = I365_PWR_NORESET; /* default: disable resetdrv on resume */ + + if (state->flags & SS_PWR_AUTO) { + printk("Auto power\n"); + reg |= I365_PWR_AUTO; /* automatic power mngmnt */ + } + if (state->flags & SS_OUTPUT_ENA) { + printk("Power Enabled \n"); + reg |= I365_PWR_OUT; /* enable power */ + } + + switch (state->Vcc) { + case 0: + break; + case 50: + printk("setting voltage to Vcc to 5V on socket %i\n",sock); + reg |= I365_VCC_5V; + break; + default: + printk("i82092aa: i82092aa_set_socket called with invalid VCC power value: %i ", state->Vcc); + leave("i82092aa_set_socket"); + return -EINVAL; + } + + + switch (state->Vpp) { + case 0: + printk("not setting Vpp on socket %i\n",sock); + break; + case 50: + printk("setting Vpp to 5.0 for socket %i\n",sock); + reg |= I365_VPP1_5V | I365_VPP2_5V; + break; + case 120: + printk("setting Vpp to 12.0\n"); + reg |= I365_VPP1_12V | I365_VPP2_12V; + break; + default: + printk("i82092aa: i82092aa_set_socket called with invalid VPP power value: %i ", state->Vcc); + leave("i82092aa_set_socket"); + return -EINVAL; + } + + if (reg != indirect_read(sock,I365_POWER)) /* only write if changed */ + indirect_write(sock,I365_POWER,reg); + + /* Enable specific interrupt events */ + + reg = 0x00; + if (state->csc_mask & SS_DETECT) { + reg |= I365_CSC_DETECT; + } + if (state->flags & SS_IOCARD) { + if (state->csc_mask & SS_STSCHG) + reg |= I365_CSC_STSCHG; + } else { + if (state->csc_mask & SS_BATDEAD) + reg |= I365_CSC_BVD1; + if (state->csc_mask & SS_BATWARN) + reg |= I365_CSC_BVD2; + if (state->csc_mask & SS_READY) + reg |= I365_CSC_READY; + + } + + /* now write the value and clear the (probably bogus) pending stuff by doing a dummy read*/ + + indirect_write(sock,I365_CSCINT,reg); + (void)indirect_read(sock,I365_CSC); + + leave("i82092aa_set_socket"); + return 0; +} + +static int i82092aa_set_io_map(struct pcmcia_socket *socket, struct pccard_io_map *io) +{ + unsigned int sock = container_of(socket, struct socket_info, socket)->number; + unsigned char map, ioctl; + + enter("i82092aa_set_io_map"); + + map = io->map; + + /* Check error conditions */ + if (map > 1) { + leave("i82092aa_set_io_map with invalid map"); + return -EINVAL; + } + if ((io->start > 0xffff) || (io->stop > 0xffff) || (io->stop < io->start)){ + leave("i82092aa_set_io_map with invalid io"); + return -EINVAL; + } + + /* Turn off the window before changing anything */ + if (indirect_read(sock, I365_ADDRWIN) & I365_ENA_IO(map)) + indirect_resetbit(sock, I365_ADDRWIN, I365_ENA_IO(map)); + +/* printk("set_io_map: Setting range to %x - %x \n",io->start,io->stop); */ + + /* write the new values */ + indirect_write16(sock,I365_IO(map)+I365_W_START,io->start); + indirect_write16(sock,I365_IO(map)+I365_W_STOP,io->stop); + + ioctl = indirect_read(sock,I365_IOCTL) & ~I365_IOCTL_MASK(map); + + if (io->flags & (MAP_16BIT|MAP_AUTOSZ)) + ioctl |= I365_IOCTL_16BIT(map); + + indirect_write(sock,I365_IOCTL,ioctl); + + /* Turn the window back on if needed */ + if (io->flags & MAP_ACTIVE) + indirect_setbit(sock,I365_ADDRWIN,I365_ENA_IO(map)); + + leave("i82092aa_set_io_map"); + return 0; +} + +static int i82092aa_set_mem_map(struct pcmcia_socket *socket, struct pccard_mem_map *mem) +{ + struct socket_info *sock_info = container_of(socket, struct socket_info, socket); + unsigned int sock = sock_info->number; + struct pci_bus_region region; + unsigned short base, i; + unsigned char map; + + enter("i82092aa_set_mem_map"); + + pcibios_resource_to_bus(sock_info->dev->bus, ®ion, mem->res); + + map = mem->map; + if (map > 4) { + leave("i82092aa_set_mem_map: invalid map"); + return -EINVAL; + } + + + if ( (mem->card_start > 0x3ffffff) || (region.start > region.end) || + (mem->speed > 1000) ) { + leave("i82092aa_set_mem_map: invalid address / speed"); + printk("invalid mem map for socket %i: %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)); + + +/* printk("set_mem_map: Setting map %i range to %x - %x on socket %i, speed is %i, active = %i \n",map, region.start,region.end,sock,mem->speed,mem->flags & MAP_ACTIVE); */ + + /* write the start address */ + base = I365_MEM(map); + i = (region.start >> 12) & 0x0fff; + if (mem->flags & MAP_16BIT) + i |= I365_MEM_16BIT; + if (mem->flags & MAP_0WS) + i |= I365_MEM_0WS; + indirect_write16(sock,base+I365_W_START,i); + + /* write the stop address */ + + i= (region.end >> 12) & 0x0fff; + switch (to_cycles(mem->speed)) { + case 0: + break; + case 1: + i |= I365_MEM_WS0; + break; + case 2: + i |= I365_MEM_WS1; + break; + default: + i |= I365_MEM_WS1 | I365_MEM_WS0; + break; + } + + indirect_write16(sock,base+I365_W_STOP,i); + + /* card start */ + + i = ((mem->card_start - region.start) >> 12) & 0x3fff; + if (mem->flags & MAP_WRPROT) + i |= I365_MEM_WRPROT; + if (mem->flags & MAP_ATTRIB) { +/* printk("requesting attribute memory for socket %i\n",sock);*/ + i |= I365_MEM_REG; + } else { +/* printk("requesting normal memory for socket %i\n",sock);*/ + } + indirect_write16(sock,base+I365_W_OFF,i); + + /* Enable the window if necessary */ + if (mem->flags & MAP_ACTIVE) + indirect_setbit(sock, I365_ADDRWIN, I365_ENA_MEM(map)); + + leave("i82092aa_set_mem_map"); + return 0; +} + +static int i82092aa_module_init(void) +{ + return pci_register_driver(&i82092aa_pci_driver); +} + +static void i82092aa_module_exit(void) +{ + enter("i82092aa_module_exit"); + pci_unregister_driver(&i82092aa_pci_driver); + if (sockets[0].io_base>0) + release_region(sockets[0].io_base, 2); + leave("i82092aa_module_exit"); +} + +module_init(i82092aa_module_init); +module_exit(i82092aa_module_exit); + diff --git a/drivers/pcmcia/i82092aa.h b/drivers/pcmcia/i82092aa.h new file mode 100644 index 000000000..fabe08c3e --- /dev/null +++ b/drivers/pcmcia/i82092aa.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _INCLUDE_GUARD_i82092aa_H_ +#define _INCLUDE_GUARD_i82092aa_H_ + +#include <linux/interrupt.h> + +/* Debuging defines */ +#ifdef NOTRACE +#define enter(x) printk("Enter: %s, %s line %i\n",x,__FILE__,__LINE__) +#define leave(x) printk("Leave: %s, %s line %i\n",x,__FILE__,__LINE__) +#define dprintk(fmt, args...) printk(fmt , ## args) +#else +#define enter(x) do {} while (0) +#define leave(x) do {} while (0) +#define dprintk(fmt, args...) do {} while (0) +#endif + + + +/* prototypes */ + +static int i82092aa_pci_probe(struct pci_dev *dev, const struct pci_device_id *id); +static void i82092aa_pci_remove(struct pci_dev *dev); +static int card_present(int socketno); +static irqreturn_t i82092aa_interrupt(int irq, void *dev); + + + + +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/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..c2a17a79f --- /dev/null +++ b/drivers/pcmcia/omap_cf.c @@ -0,0 +1,359 @@ +/* + * omap_cf.c -- OMAP 16xx CompactFlash controller driver + * + * Copyright (c) 2005 David Brownell + * + * 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. + */ + +#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 <asm/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 = (char *) 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..1c05d74e8 --- /dev/null +++ b/drivers/pcmcia/pcmcia_cis.c @@ -0,0 +1,438 @@ +/* + * 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/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. + */ +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..188020961 --- /dev/null +++ b/drivers/pcmcia/pcmcia_resource.c @@ -0,0 +1,961 @@ +/* + * 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/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); + mdelay(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); + mdelay(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..2ef576c5b --- /dev/null +++ b/drivers/pcmcia/pxa2xx_balloon3.c @@ -0,0 +1,140 @@ +/* + * 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> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/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..91b5f5724 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_base.c @@ -0,0 +1,385 @@ +/*====================================================================== + + Device driver for the PCMCIA control functionality of PXA2xx + microprocessors. + + The contents of this file may be used under the + terms of the GNU Public License version 2 (the "GPL") + + (c) Ian Molton (spyro@f2s.com) 2003 + (c) Stefan Eletzhofer (stefan.eletzhofer@inquant.de) 2003,4 + + derived from sa11xx_base.c + + Portions created by John G. Dorsey are + Copyright (C) 1999 John G. Dorsey. + + ======================================================================*/ + +#include <linux/module.h> +#include <linux/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..da40908b2 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_cm_x255.c @@ -0,0 +1,128 @@ +/* + * linux/drivers/pcmcia/pxa/pxa_cm_x255.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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..f59223f23 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_cm_x270.c @@ -0,0 +1,107 @@ +/* + * linux/drivers/pcmcia/pxa/pxa_cm_x270.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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..6e7dcfd22 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_cm_x2xx.c @@ -0,0 +1,48 @@ +/* + * linux/drivers/pcmcia/pxa/pxa_cm_x2xx.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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..4dee7b2a8 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_colibri.c @@ -0,0 +1,169 @@ +/* + * linux/drivers/pcmcia/pxa2xx_colibri.c + * + * Driver for Toradex Colibri PXA270 CF socket + * + * Copyright (C) 2010 Marek Vasut <marek.vasut@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/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..8751a323b --- /dev/null +++ b/drivers/pcmcia/pxa2xx_e740.c @@ -0,0 +1,130 @@ +/* + * Toshiba e740 PCMCIA specific routines. + * + * (c) 2004 Ian Molton <spyro@f2s.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/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..7dfef3ee5 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_hx4700.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2012 Paul Parsons <lost.distance@yahoo.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/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..7e32e25cd --- /dev/null +++ b/drivers/pcmcia/pxa2xx_mainstone.c @@ -0,0 +1,170 @@ +/* + * linux/drivers/pcmcia/pxa2xx_mainstone.c + * + * Mainstone PCMCIA specific routines. + * + * Created: May 12, 2004 + * Author: Nicolas Pitre + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <pcmcia/ss.h> + +#include <asm/mach-types.h> +#include <asm/irq.h> + +#include <mach/pxa2xx-regs.h> +#include <mach/mainstone.h> + +#include "soc_common.h" + + +static int mst_pcmcia_hw_init(struct soc_pcmcia_socket *skt) +{ + /* + * Setup default state of GPIO outputs + * before we enable them as outputs. + */ + if (skt->nr == 0) { + skt->socket.pci_irq = MAINSTONE_S0_IRQ; + skt->stat[SOC_STAT_CD].irq = MAINSTONE_S0_CD_IRQ; + skt->stat[SOC_STAT_CD].name = "PCMCIA0 CD"; + skt->stat[SOC_STAT_BVD1].irq = MAINSTONE_S0_STSCHG_IRQ; + skt->stat[SOC_STAT_BVD1].name = "PCMCIA0 STSCHG"; + } else { + skt->socket.pci_irq = MAINSTONE_S1_IRQ; + skt->stat[SOC_STAT_CD].irq = MAINSTONE_S1_CD_IRQ; + skt->stat[SOC_STAT_CD].name = "PCMCIA1 CD"; + skt->stat[SOC_STAT_BVD1].irq = MAINSTONE_S1_STSCHG_IRQ; + skt->stat[SOC_STAT_BVD1].name = "PCMCIA1 STSCHG"; + } + return 0; +} + +static unsigned long mst_pcmcia_status[2]; + +static void mst_pcmcia_socket_state(struct soc_pcmcia_socket *skt, + struct pcmcia_state *state) +{ + unsigned long status, flip; + + status = (skt->nr == 0) ? MST_PCMCIA0 : MST_PCMCIA1; + flip = (status ^ mst_pcmcia_status[skt->nr]) & MST_PCMCIA_nSTSCHG_BVD1; + + /* + * Workaround for STSCHG which can't be deasserted: + * We therefore disable/enable corresponding IRQs + * as needed to avoid IRQ locks. + */ + if (flip) { + mst_pcmcia_status[skt->nr] = status; + if (status & MST_PCMCIA_nSTSCHG_BVD1) + enable_irq( (skt->nr == 0) ? MAINSTONE_S0_STSCHG_IRQ + : MAINSTONE_S1_STSCHG_IRQ ); + else + disable_irq( (skt->nr == 0) ? MAINSTONE_S0_STSCHG_IRQ + : MAINSTONE_S1_STSCHG_IRQ ); + } + + state->detect = (status & MST_PCMCIA_nCD) ? 0 : 1; + state->ready = (status & MST_PCMCIA_nIRQ) ? 1 : 0; + state->bvd1 = (status & MST_PCMCIA_nSTSCHG_BVD1) ? 1 : 0; + state->bvd2 = (status & MST_PCMCIA_nSPKR_BVD2) ? 1 : 0; + state->vs_3v = (status & MST_PCMCIA_nVS1) ? 0 : 1; + state->vs_Xv = (status & MST_PCMCIA_nVS2) ? 0 : 1; +} + +static int mst_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, + const socket_state_t *state) +{ + unsigned long power = 0; + int ret = 0; + + switch (state->Vcc) { + case 0: power |= MST_PCMCIA_PWR_VCC_0; break; + case 33: power |= MST_PCMCIA_PWR_VCC_33; break; + case 50: power |= MST_PCMCIA_PWR_VCC_50; break; + default: + printk(KERN_ERR "%s(): bad Vcc %u\n", + __func__, state->Vcc); + ret = -1; + } + + switch (state->Vpp) { + case 0: power |= MST_PCMCIA_PWR_VPP_0; break; + case 120: power |= MST_PCMCIA_PWR_VPP_120; break; + default: + if(state->Vpp == state->Vcc) { + power |= MST_PCMCIA_PWR_VPP_VCC; + } else { + printk(KERN_ERR "%s(): bad Vpp %u\n", + __func__, state->Vpp); + ret = -1; + } + } + + if (state->flags & SS_RESET) + power |= MST_PCMCIA_RESET; + + switch (skt->nr) { + case 0: MST_PCMCIA0 = power; break; + case 1: MST_PCMCIA1 = power; break; + default: ret = -1; + } + + return ret; +} + +static 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..ed7d4dbc3 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_palmld.c @@ -0,0 +1,114 @@ +/* + * 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> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/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..81225a7a8 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_palmtc.c @@ -0,0 +1,166 @@ +/* + * 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> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/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..069b6bbcf --- /dev/null +++ b/drivers/pcmcia/pxa2xx_palmtx.c @@ -0,0 +1,115 @@ +/* + * linux/drivers/pcmcia/pxa2xx_palmtx.c + * + * Driver for Palm T|X PCMCIA + * + * Copyright (C) 2007-2011 Marek Vasut <marek.vasut@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/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..89ebd8c76 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_sharpsl.c @@ -0,0 +1,262 @@ +/* + * Sharp SL-C7xx Series PCMCIA routines + * + * Copyright (c) 2004-2005 Richard Purdie + * + * Based on Sharp's 2.4 kernel patches and pxa2xx_mainstone.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/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..1d73c4401 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_stargate2.c @@ -0,0 +1,140 @@ +/* + * 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 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/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..d326ba1fa --- /dev/null +++ b/drivers/pcmcia/pxa2xx_trizeps4.c @@ -0,0 +1,203 @@ +/* + * linux/drivers/pcmcia/pxa2xx_trizeps4.c + * + * TRIZEPS PCMCIA specific routines. + * + * Author: Jürgen Schindele + * Created: 20 02, 2006 + * Copyright: Jürgen Schindele + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/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..33c5b8823 --- /dev/null +++ b/drivers/pcmcia/pxa2xx_vpac270.c @@ -0,0 +1,141 @@ +/* + * linux/drivers/pcmcia/pxa2xx_vpac270.c + * + * Driver for Voipac PXA270 PCMCIA and CF sockets + * + * Copyright (C) 2010-2011 Marek Vasut <marek.vasut@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/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..f53c237bd --- /dev/null +++ b/drivers/pcmcia/rsrc_iodyn.c @@ -0,0 +1,171 @@ +/* + * rsrc_iodyn.c -- Resource management routines for MEM-static sockets. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + */ + +#include <linux/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..df2cb70ae --- /dev/null +++ b/drivers/pcmcia/rsrc_mgr.c @@ -0,0 +1,73 @@ +/* + * rsrc_mgr.c -- Resource management routines and/or wrappers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + */ + +#include <linux/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..123420cac --- /dev/null +++ b/drivers/pcmcia/rsrc_nonstatic.c @@ -0,0 +1,1243 @@ +/* + * rsrc_nonstatic.c -- Resource management routines for !SS_CAP_STATIC_MAP sockets + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * The initial developer of the original code is David A. Hinds + * <dahinds@users.sourceforge.net>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + * + * (C) 1999 David A. Hinds + */ + +#include <linux/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); + } +} + + +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 += snprintf(&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 += snprintf(&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 += snprintf(&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..e235ee14e --- /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(); + + state->bvd1 = 1; /* Might be cs3reg & PCMCIA_BVD1 */ + state->bvd2 = 1; /* 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..93a5c7423 --- /dev/null +++ b/drivers/pcmcia/sa1111_badge4.c @@ -0,0 +1,162 @@ +/* + * linux/drivers/pcmcia/sa1100_badge4.c + * + * BadgePAD 4 PCMCIA specific routines + * + * Christopher Hoover <ch@hpl.hp.com> + * + * Copyright (C) 2002 Hewlett-Packard Company + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/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..5ef351f87 --- /dev/null +++ b/drivers/pcmcia/sa1111_generic.c @@ -0,0 +1,280 @@ +/* + * 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..3d4ca87ca --- /dev/null +++ b/drivers/pcmcia/sa1111_jornada720.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drivers/pcmcia/sa1100_jornada720.c + * + * Jornada720 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/io.h> + +#include <mach/hardware.h> +#include <asm/hardware/sa1111.h> +#include <asm/mach-types.h> + +#include "sa1111_generic.h" + +/* Does SOCKET1_3V actually do anything? */ +#define SOCKET0_POWER GPIO_GPIO0 +#define SOCKET0_3V GPIO_GPIO2 +#define SOCKET1_POWER (GPIO_GPIO1 | GPIO_GPIO3) +#define SOCKET1_3V GPIO_GPIO3 + +static int +jornada720_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_t *state) +{ + struct sa1111_pcmcia_socket *s = to_skt(skt); + unsigned int pa_dwr_mask, pa_dwr_set; + 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: + pa_dwr_mask = SOCKET0_POWER | SOCKET0_3V; + + switch (state->Vcc) { + default: + case 0: + pa_dwr_set = 0; + break; + case 33: + pa_dwr_set = SOCKET0_POWER | SOCKET0_3V; + break; + case 50: + pa_dwr_set = SOCKET0_POWER; + break; + } + break; + + case 1: + pa_dwr_mask = SOCKET1_POWER; + + switch (state->Vcc) { + default: + case 0: + pa_dwr_set = 0; + break; + case 33: + pa_dwr_set = SOCKET1_POWER; + break; + case 50: + pa_dwr_set = SOCKET1_POWER; + break; + } + break; + + default: + return -1; + } + + if (state->Vpp != state->Vcc && state->Vpp != 0) { + printk(KERN_ERR "%s(): slot cannot support VPP %u\n", + __func__, state->Vpp); + return -EPERM; + } + + ret = sa1111_pcmcia_configure_socket(skt, state); + if (ret == 0) + sa1111_set_io(s->dev, pa_dwr_mask, pa_dwr_set); + + return ret; +} + +static struct pcmcia_low_level jornada720_pcmcia_ops = { + .owner = THIS_MODULE, + .configure_socket = jornada720_pcmcia_configure_socket, + .first = 0, + .nr = 2, +}; + +int pcmcia_jornada720_init(struct sa1111_dev *sadev) +{ + unsigned int pin = GPIO_A0 | GPIO_A1 | GPIO_A2 | GPIO_A3; + + /* Fixme: why messing around with SA11x0's GPIO1? */ + GRER |= 0x00000002; + + /* Set GPIO_A<3:1> to be outputs for PCMCIA/CF power controller: */ + sa1111_set_io_dir(sadev, pin, 0, 0); + sa1111_set_io(sadev, pin, 0); + sa1111_set_sleep_io(sadev, pin, 0); + + 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..e741f499c --- /dev/null +++ b/drivers/pcmcia/sa1111_lubbock.c @@ -0,0 +1,230 @@ +/* + * linux/drivers/pcmcia/pxa2xx_lubbock.c + * + * Author: George Davis + * Created: Jan 10, 2002 + * Copyright: MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Originally based upon linux/drivers/pcmcia/sa1100_neponset.c + * + * Lubbock PCMCIA specific routines. + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/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 <mach/lubbock.h> + +#include "sa1111_generic.h" + +static int +lubbock_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, + const socket_state_t *state) +{ + struct sa1111_pcmcia_socket *s = to_skt(skt); + unsigned int pa_dwr_mask, pa_dwr_set, misc_mask, misc_set; + int ret = 0; + + pa_dwr_mask = pa_dwr_set = misc_mask = misc_set = 0; + + /* Lubbock uses the Maxim MAX1602, with the following connections: + * + * Socket 0 (PCMCIA): + * MAX1602 Lubbock Register + * Pin Signal + * ----- ------- ---------------------- + * A0VPP S0_PWR0 SA-1111 GPIO A<0> + * A1VPP S0_PWR1 SA-1111 GPIO A<1> + * A0VCC S0_PWR2 SA-1111 GPIO A<2> + * A1VCC S0_PWR3 SA-1111 GPIO A<3> + * VX VCC + * VY +3.3V + * 12IN +12V + * CODE +3.3V Cirrus Code, CODE = High (VY) + * + * Socket 1 (CF): + * MAX1602 Lubbock Register + * Pin Signal + * ----- ------- ---------------------- + * A0VPP GND VPP is not connected + * A1VPP GND VPP is not connected + * A0VCC S1_PWR0 MISC_WR<14> + * A1VCC S1_PWR1 MISC_WR<15> + * VX VCC + * VY +3.3V + * 12IN GND VPP is not connected + * CODE +3.3V Cirrus Code, CODE = High (VY) + * + */ + + again: + switch (skt->nr) { + case 0: + pa_dwr_mask = GPIO_A0 | GPIO_A1 | GPIO_A2 | GPIO_A3; + + switch (state->Vcc) { + case 0: /* Hi-Z */ + break; + + case 33: /* VY */ + pa_dwr_set |= GPIO_A3; + break; + + case 50: /* VX */ + pa_dwr_set |= GPIO_A2; + break; + + default: + printk(KERN_ERR "%s(): unrecognized Vcc %u\n", + __func__, state->Vcc); + ret = -1; + } + + switch (state->Vpp) { + case 0: /* Hi-Z */ + break; + + case 120: /* 12IN */ + pa_dwr_set |= GPIO_A1; + break; + + default: /* VCC */ + if (state->Vpp == state->Vcc) + pa_dwr_set |= GPIO_A0; + else { + printk(KERN_ERR "%s(): unrecognized Vpp %u\n", + __func__, state->Vpp); + ret = -1; + break; + } + } + break; + + case 1: + misc_mask = (1 << 15) | (1 << 14); + + switch (state->Vcc) { + case 0: /* Hi-Z */ + break; + + case 33: /* VY */ + misc_set |= 1 << 15; + break; + + case 50: /* VX */ + misc_set |= 1 << 14; + break; + + default: + printk(KERN_ERR "%s(): unrecognized Vcc %u\n", + __func__, state->Vcc); + ret = -1; + break; + } + + if (state->Vpp != state->Vcc && state->Vpp != 0) { + printk(KERN_ERR "%s(): CF slot cannot support Vpp %u\n", + __func__, state->Vpp); + ret = -1; + break; + } + break; + + default: + ret = -1; + } + + if (ret == 0) + ret = sa1111_pcmcia_configure_socket(skt, state); + + if (ret == 0) { + lubbock_set_misc_wr(misc_mask, misc_set); + sa1111_set_io(s->dev, pa_dwr_mask, pa_dwr_set); + } + +#if 1 + if (ret == 0 && state->Vcc == 33) { + struct pcmcia_state new_state; + + /* + * HACK ALERT: + * We can't sense the voltage properly on Lubbock before + * actually applying some power to the socket (catch 22). + * Resense the socket Voltage Sense pins after applying + * socket power. + * + * Note: It takes about 2.5ms for the MAX1602 VCC output + * to rise. + */ + mdelay(3); + + sa1111_pcmcia_socket_state(skt, &new_state); + + if (!new_state.vs_3v && !new_state.vs_Xv) { + /* + * Switch to 5V, Configure socket with 5V voltage + */ + lubbock_set_misc_wr(misc_mask, 0); + sa1111_set_io(s->dev, pa_dwr_mask, 0); + + /* + * It takes about 100ms to turn off Vcc. + */ + mdelay(100); + + /* + * We need to hack around the const qualifier as + * well to keep this ugly workaround localized and + * not force it to the rest of the code. Barf bags + * 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, + .configure_socket = lubbock_pcmcia_configure_socket, + .first = 0, + .nr = 2, +}; + +#include "pxa2xx_base.h" + +int pcmcia_lubbock_init(struct sa1111_dev *sadev) +{ + /* + * Set GPIO_A<3:0> to be outputs for the MAX1600, + * and switch to standby mode. + */ + sa1111_set_io_dir(sadev, GPIO_A0|GPIO_A1|GPIO_A2|GPIO_A3, 0, 0); + sa1111_set_io(sadev, GPIO_A0|GPIO_A1|GPIO_A2|GPIO_A3, 0); + sa1111_set_sleep_io(sadev, GPIO_A0|GPIO_A1|GPIO_A2|GPIO_A3, 0); + + /* Set CF Socket 1 power to standby mode. */ + lubbock_set_misc_wr((1 << 15) | (1 << 14), 0); + + 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..0ccf05a28 --- /dev/null +++ b/drivers/pcmcia/sa1111_neponset.c @@ -0,0 +1,124 @@ +// 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 <mach/hardware.h> +#include <asm/mach-types.h> +#include <mach/neponset.h> +#include <asm/hardware/sa1111.h> + +#include "sa1111_generic.h" + +/* + * Neponset uses the Maxim MAX1600, with the following connections: + * + * MAX1600 Neponset + * + * A0VCC SA-1111 GPIO A<1> + * A1VCC SA-1111 GPIO A<0> + * A0VPP CPLD NCR A0VPP + * A1VPP CPLD NCR A1VPP + * B0VCC SA-1111 GPIO A<2> + * B1VCC SA-1111 GPIO A<3> + * B0VPP ground (slot B is CF) + * B1VPP ground (slot B is CF) + * + * VX VCC (5V) + * VY VCC3_3 (3.3V) + * 12INA 12V + * 12INB ground (slot B is CF) + * + * The MAX1600 CODE pin is tied to ground, placing the device in + * "Standard Intel code" mode. Refer to the Maxim data sheet for + * the corresponding truth table. + */ + +static int +neponset_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_t *state) +{ + struct sa1111_pcmcia_socket *s = to_skt(skt); + unsigned int ncr_mask, ncr_set, pa_dwr_mask, pa_dwr_set; + int ret; + + switch (skt->nr) { + case 0: + pa_dwr_mask = GPIO_A0 | GPIO_A1; + ncr_mask = NCR_A0VPP | NCR_A1VPP; + + if (state->Vpp == 0) + ncr_set = 0; + else if (state->Vpp == 120) + ncr_set = NCR_A1VPP; + else if (state->Vpp == state->Vcc) + ncr_set = NCR_A0VPP; + else { + printk(KERN_ERR "%s(): unrecognized VPP %u\n", + __func__, state->Vpp); + return -1; + } + break; + + case 1: + pa_dwr_mask = GPIO_A2 | GPIO_A3; + ncr_mask = 0; + ncr_set = 0; + + if (state->Vpp != state->Vcc && state->Vpp != 0) { + printk(KERN_ERR "%s(): CF slot cannot support VPP %u\n", + __func__, state->Vpp); + return -1; + } + break; + + default: + return -1; + } + + /* + * pa_dwr_set is the mask for selecting Vcc on both sockets. + * pa_dwr_mask selects which bits (and therefore socket) we change. + */ + switch (state->Vcc) { + default: + case 0: pa_dwr_set = 0; break; + case 33: pa_dwr_set = GPIO_A1|GPIO_A2; break; + case 50: pa_dwr_set = GPIO_A0|GPIO_A3; break; + } + + ret = sa1111_pcmcia_configure_socket(skt, state); + if (ret == 0) { + neponset_ncr_frob(ncr_mask, ncr_set); + sa1111_set_io(s->dev, pa_dwr_mask, pa_dwr_set); + } + + return ret; +} + +static struct pcmcia_low_level neponset_pcmcia_ops = { + .owner = THIS_MODULE, + .configure_socket = neponset_pcmcia_configure_socket, + .first = 0, + .nr = 2, +}; + +int pcmcia_neponset_init(struct sa1111_dev *sadev) +{ + /* + * Set GPIO_A<3:0> to be outputs for the MAX1600, + * and switch to standby mode. + */ + sa1111_set_io_dir(sadev, GPIO_A0|GPIO_A1|GPIO_A2|GPIO_A3, 0, 0); + sa1111_set_io(sadev, GPIO_A0|GPIO_A1|GPIO_A2|GPIO_A3, 0); + sa1111_set_sleep_io(sadev, GPIO_A0|GPIO_A1|GPIO_A2|GPIO_A3, 0); + 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..c5f2344c1 --- /dev/null +++ b/drivers/pcmcia/soc_common.c @@ -0,0 +1,893 @@ +/*====================================================================== + + 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]; + int values[2], n = 0; + + if (skt->gpio_reset) { + descs[n] = skt->gpio_reset; + values[n++] = !!(state->flags & SS_RESET); + } + if (skt->gpio_bus_enable) { + descs[n] = skt->gpio_bus_enable; + values[n++] = !!(state->flags & SS_OUTPUT_ENA); + } + + if (n) + gpiod_set_array_value_cansleep(n, descs, 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..b7f993f1b --- /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[0]; +}; + +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..d6881514d --- /dev/null +++ b/drivers/pcmcia/socket_sysfs.c @@ -0,0 +1,231 @@ +/* + * socket_sysfs.c -- most of socket-related sysfs output + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * (C) 2003 - 2004 Dominik Brodowski + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/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..1e5fa211f --- /dev/null +++ b/drivers/pcmcia/vrc4171_card.c @@ -0,0 +1,758 @@ +/* + * vrc4171_card.c, NEC VRC4171 Card Controller driver for Socket Services. + * + * Copyright (C) 2003-2005 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/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..b2a189507 --- /dev/null +++ b/drivers/pcmcia/xxs1500_ss.c @@ -0,0 +1,327 @@ +/* + * 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..ac6a3f46b --- /dev/null +++ b/drivers/pcmcia/yenta_socket.c @@ -0,0 +1,1446 @@ +/* + * 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 pci_dev *dev = to_pci_dev(yentadev); + struct yenta_socket *socket = pci_get_drvdata(dev); + int offset = 0, i; + + offset = snprintf(buf, PAGE_SIZE, "CB registers:"); + for (i = 0; i < 0x24; i += 4) { + unsigned val; + if (!(i & 15)) + offset += snprintf(buf + offset, PAGE_SIZE - offset, "\n%02x:", i); + val = cb_readl(socket, i); + offset += snprintf(buf + offset, PAGE_SIZE - offset, " %08x", val); + } + + offset += snprintf(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 += snprintf(buf + offset, PAGE_SIZE - offset, "\n%02x:", i); + } + val = exca_readb(socket, i); + offset += snprintf(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, ®ion, mem->res); + + map = mem->map; + start = region.start; + stop = region.end; + card_start = mem->card_start; + + if (map > 4 || start > stop || ((start ^ stop) >> 24) || + (card_start >> 26) || mem->speed > 1000) + return -EINVAL; + + enable = I365_ENA_MEM(map); + addr = exca_readb(socket, I365_ADDRWIN); + if (addr & enable) { + addr &= ~enable; + exca_writeb(socket, I365_ADDRWIN, addr); + } + + exca_writeb(socket, CB_MEM_PAGE(map), start >> 24); + + word = (start >> 12) & 0x0fff; + if (mem->flags & MAP_16BIT) + word |= I365_MEM_16BIT; + if (mem->flags & MAP_0WS) + word |= I365_MEM_0WS; + exca_writew(socket, I365_MEM(map) + I365_W_START, word); + + word = (stop >> 12) & 0x0fff; + switch (to_cycles(mem->speed)) { + case 0: + break; + case 1: + word |= I365_MEM_WS0; + break; + case 2: + word |= I365_MEM_WS1; + break; + default: + word |= I365_MEM_WS1 | I365_MEM_WS0; + break; + } + exca_writew(socket, I365_MEM(map) + I365_W_STOP, word); + + word = ((card_start - start) >> 12) & 0x3fff; + if (mem->flags & MAP_WRPROT) + word |= I365_MEM_WRPROT; + if (mem->flags & MAP_ATTRIB) + word |= I365_MEM_REG; + exca_writew(socket, I365_MEM(map) + I365_W_OFF, word); + + if (mem->flags & MAP_ACTIVE) + exca_writeb(socket, I365_ADDRWIN, addr | enable); + return 0; +} + + + +static 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 + PCI_BRIDGE_RESOURCES + 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, ®ion); + if (pci_claim_resource(dev, PCI_BRIDGE_RESOURCES + 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; +} + +/* + * Allocate the bridge mappings for the device.. + */ +static void yenta_allocate_resources(struct yenta_socket *socket) +{ + int program = 0; + program += yenta_allocate_res(socket, 0, IORESOURCE_IO, + PCI_CB_IO_BASE_0, PCI_CB_IO_LIMIT_0); + program += yenta_allocate_res(socket, 1, IORESOURCE_IO, + PCI_CB_IO_BASE_1, PCI_CB_IO_LIMIT_1); + program += yenta_allocate_res(socket, 2, IORESOURCE_MEM|IORESOURCE_PREFETCH, + PCI_CB_MEMORY_BASE_0, PCI_CB_MEMORY_LIMIT_0); + program += yenta_allocate_res(socket, 3, 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) +{ + int i; + for (i = 0; i < 4; i++) { + struct resource *res; + res = socket->dev->resource + PCI_BRIDGE_RESOURCES + i; + if (res->start != 0 && res->end != 0) + release_resource(res); + res->start = res->end = res->flags = 0; + } +} + + +/* + * Close it down - release our resources and go home.. + */ +static void yenta_close(struct pci_dev *dev) +{ + struct yenta_socket *sock = pci_get_drvdata(dev); + + /* 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, ®ion, &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 = ¥ta_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 (¥ta_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 |