diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1999-10-09 00:00:47 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 1999-10-09 00:00:47 +0000 |
commit | d6434e1042f3b0a6dfe1b1f615af369486f9b1fa (patch) | |
tree | e2be02f33984c48ec019c654051d27964e42c441 /drivers/pcmcia | |
parent | 609d1e803baf519487233b765eb487f9ec227a18 (diff) |
Merge with 2.3.19.
Diffstat (limited to 'drivers/pcmcia')
-rw-r--r-- | drivers/pcmcia/.cvsignore | 2 | ||||
-rw-r--r-- | drivers/pcmcia/Config.in | 14 | ||||
-rw-r--r-- | drivers/pcmcia/Makefile | 39 | ||||
-rw-r--r-- | drivers/pcmcia/bulkmem.c | 625 | ||||
-rw-r--r-- | drivers/pcmcia/cardbus.c | 641 | ||||
-rw-r--r-- | drivers/pcmcia/cb_enabler.c | 397 | ||||
-rw-r--r-- | drivers/pcmcia/cirrus.h | 157 | ||||
-rw-r--r-- | drivers/pcmcia/cistpl.c | 1394 | ||||
-rw-r--r-- | drivers/pcmcia/cs.c | 2243 | ||||
-rw-r--r-- | drivers/pcmcia/cs_internal.h | 280 | ||||
-rw-r--r-- | drivers/pcmcia/ds.c | 899 | ||||
-rw-r--r-- | drivers/pcmcia/i82365.c | 2804 | ||||
-rw-r--r-- | drivers/pcmcia/i82365.h | 135 | ||||
-rw-r--r-- | drivers/pcmcia/o2micro.h | 98 | ||||
-rw-r--r-- | drivers/pcmcia/ricoh.h | 132 | ||||
-rw-r--r-- | drivers/pcmcia/rsrc_mgr.c | 790 | ||||
-rw-r--r-- | drivers/pcmcia/rsrc_mgr.h | 39 | ||||
-rw-r--r-- | drivers/pcmcia/smc34c90.h | 52 | ||||
-rw-r--r-- | drivers/pcmcia/tcic.c | 966 | ||||
-rw-r--r-- | drivers/pcmcia/tcic.h | 266 | ||||
-rw-r--r-- | drivers/pcmcia/ti113x.h | 180 | ||||
-rw-r--r-- | drivers/pcmcia/topic.h | 96 | ||||
-rw-r--r-- | drivers/pcmcia/vg468.h | 106 | ||||
-rw-r--r-- | drivers/pcmcia/yenta.h | 153 |
24 files changed, 12508 insertions, 0 deletions
diff --git a/drivers/pcmcia/.cvsignore b/drivers/pcmcia/.cvsignore new file mode 100644 index 000000000..857dd22e9 --- /dev/null +++ b/drivers/pcmcia/.cvsignore @@ -0,0 +1,2 @@ +.depend +.*.flags diff --git a/drivers/pcmcia/Config.in b/drivers/pcmcia/Config.in new file mode 100644 index 000000000..2883046c1 --- /dev/null +++ b/drivers/pcmcia/Config.in @@ -0,0 +1,14 @@ +# +# PCMCIA bus subsystem configuration +# +mainmenu_option next_comment +comment 'PCMCIA/Cardbus support' + +tristate 'PCMCIA/Cardbus support' CONFIG_PCMCIA +if [ "$CONFIG_PCMCIA" != "n" ]; then + if [ "$CONFIG_PCI" != "n" ]; then + bool ' CardBus support' CONFIG_CARDBUS + fi +fi + +endmenu diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile new file mode 100644 index 000000000..71750544d --- /dev/null +++ b/drivers/pcmcia/Makefile @@ -0,0 +1,39 @@ +# +# Makefile for the kernel pcmcia subsystem (c/o David Hinds) +# +# Note! Dependencies are done automagically by 'make dep', which also +# removes any old dependencies. DON'T put your own dependencies here +# unless it's something special (ie not a .c file). +# +# Note 2! The CFLAGS definitions are now inherited from the +# parent makes.. + +SUB_DIRS := +MOD_SUB_DIRS := $(SUB_DIRS) +ALL_SUB_DIRS := $(SUB_DIRS) + +MOD_LIST_NAME := PCMCIA_MODULES + +ifeq ($(CONFIG_PCMCIA),y) + O_OBJS := i82365.o tcic.o cistpl.o rsrc_mgr.o bulkmem.o + OX_OBJS := ds.o cs.o + O_TARGET := pcmcia.o + ifeq ($(CONFIG_CARDBUS),y) + O_OBJS += cardbus.o + endif +else + ifeq ($(CONFIG_PCMCIA),m) + M_OBJS := i82365.o tcic.o pcmcia_core.o + MX_OBJS := ds.o + MIX_OBJS := cs.o + CORE_OBJS := cistpl.o rsrc_mgr.o bulkmem.o cs.o + ifeq ($(CONFIG_CARDBUS),y) + CORE_OBJS += cardbus.o + endif + endif +endif + +include $(TOPDIR)/Rules.make + +pcmcia_core.o: $(CORE_OBJS) + $(LD) $(LD_RFLAG) -r -o $@ $(CORE_OBJS) diff --git a/drivers/pcmcia/bulkmem.c b/drivers/pcmcia/bulkmem.c new file mode 100644 index 000000000..7eaad9626 --- /dev/null +++ b/drivers/pcmcia/bulkmem.c @@ -0,0 +1,625 @@ +/*====================================================================== + + PCMCIA Bulk Memory Services + + bulkmem.c 1.32 1999/09/15 15:32:19 + + 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 + <dhinds@hyper.stanford.edu>. 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 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. + +======================================================================*/ + +#define __NO_VERSION__ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/timer.h> + +#define IN_CARD_SERVICES +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" + +/*====================================================================== + + This function handles submitting an MTD request, and retrying + requests when an MTD is busy. + + An MTD request should never block. + +======================================================================*/ + +static int do_mtd_request(memory_handle_t handle, mtd_request_t *req, + caddr_t buf) +{ + int ret, tries; + client_t *mtd; + socket_info_t *s; + + mtd = handle->mtd; + if (mtd == NULL) + return CS_GENERAL_FAILURE; + s = SOCKET(mtd); + for (tries = 0; tries < 100; tries++) { + mtd->event_callback_args.mtdrequest = req; + mtd->event_callback_args.buffer = buf; + ret = EVENT(mtd, CS_EVENT_MTD_REQUEST, CS_EVENT_PRI_LOW); + if (ret != CS_BUSY) + break; + switch (req->Status) { + case MTD_WAITREQ: + /* Not that we should ever need this... */ + interruptible_sleep_on_timeout(&mtd->mtd_req, HZ); + break; + case MTD_WAITTIMER: + case MTD_WAITRDY: + interruptible_sleep_on_timeout(&mtd->mtd_req, req->Timeout*HZ/1000); + req->Function |= MTD_REQ_TIMEOUT; + break; + case MTD_WAITPOWER: + interruptible_sleep_on(&mtd->mtd_req); + break; + } + if (signal_pending(current)) + printk(KERN_NOTICE "cs: do_mtd_request interrupted!\n"); + } + if (tries == 20) { + printk(KERN_NOTICE "cs: MTD request timed out!\n"); + ret = CS_GENERAL_FAILURE; + } + wake_up_interruptible(&mtd->mtd_req); + retry_erase_list(&mtd->erase_busy, 0); + return ret; +} /* do_mtd_request */ + +/*====================================================================== + + This stuff is all for handling asynchronous erase requests. It + is complicated because all the retry stuff has to be dealt with + in timer interrupts or in the card status event handler. + +======================================================================*/ + +static void insert_queue(erase_busy_t *head, erase_busy_t *entry) +{ + DEBUG(2, "cs: adding 0x%p to queue 0x%p\n", entry, head); + entry->next = head; + entry->prev = head->prev; + head->prev->next = entry; + head->prev = entry; +} + +static void remove_queue(erase_busy_t *entry) +{ + DEBUG(2, "cs: unqueueing 0x%p\n", entry); + entry->next->prev = entry->prev; + entry->prev->next = entry->next; +} + +static void retry_erase(erase_busy_t *busy, u_int cause) +{ + eraseq_entry_t *erase = busy->erase; + mtd_request_t req; + client_t *mtd; + socket_info_t *s; + int ret; + + DEBUG(2, "cs: trying erase request 0x%p...\n", busy); + if (busy->next) + remove_queue(busy); + req.Function = MTD_REQ_ERASE | cause; + req.TransferLength = erase->Size; + req.DestCardOffset = erase->Offset + erase->Handle->info.CardOffset; + req.MediaID = erase->Handle->MediaID; + mtd = erase->Handle->mtd; + s = SOCKET(mtd); + mtd->event_callback_args.mtdrequest = &req; + ret = EVENT(mtd, CS_EVENT_MTD_REQUEST, CS_EVENT_PRI_LOW); + if (ret == CS_BUSY) { + DEBUG(2, " Status = %d, requeueing.\n", req.Status); + switch (req.Status) { + case MTD_WAITREQ: + case MTD_WAITPOWER: + insert_queue(&mtd->erase_busy, busy); + break; + case MTD_WAITTIMER: + case MTD_WAITRDY: + if (req.Status == MTD_WAITRDY) + insert_queue(&s->erase_busy, busy); + busy->timeout.expires = jiffies + req.Timeout*HZ/1000; + add_timer(&busy->timeout); + break; + } + } else { + /* update erase queue status */ + DEBUG(2, " Ret = %d\n", ret); + switch (ret) { + case CS_SUCCESS: + erase->State = ERASE_PASSED; break; + case CS_WRITE_PROTECTED: + erase->State = ERASE_MEDIA_WRPROT; break; + case CS_BAD_OFFSET: + erase->State = ERASE_BAD_OFFSET; break; + case CS_BAD_SIZE: + erase->State = ERASE_BAD_SIZE; break; + case CS_NO_CARD: + erase->State = ERASE_BAD_SOCKET; break; + default: + erase->State = ERASE_FAILED; break; + } + busy->client->event_callback_args.info = erase; + EVENT(busy->client, CS_EVENT_ERASE_COMPLETE, CS_EVENT_PRI_LOW); + kfree_s(busy, sizeof(*busy)); + /* Resubmit anything waiting for a request to finish */ + wake_up_interruptible(&mtd->mtd_req); + retry_erase_list(&mtd->erase_busy, 0); + } +} /* retry_erase */ + +void retry_erase_list(erase_busy_t *list, u_int cause) +{ + erase_busy_t tmp = *list; + + DEBUG(2, "cs: rescanning erase queue list 0x%p\n", list); + if (list->next == list) + return; + /* First, truncate the original list */ + list->prev->next = &tmp; + list->next->prev = &tmp; + list->prev = list->next = list; + tmp.prev->next = &tmp; + tmp.next->prev = &tmp; + + /* Now, retry each request, in order. */ + while (tmp.next != &tmp) + retry_erase(tmp.next, cause); +} /* retry_erase_list */ + +static void handle_erase_timeout(u_long arg) +{ + DEBUG(0, "cs: erase timeout for entry 0x%lx\n", arg); + retry_erase((erase_busy_t *)arg, MTD_REQ_TIMEOUT); +} + +static void setup_erase_request(client_handle_t handle, eraseq_entry_t *erase) +{ + erase_busy_t *busy; + region_info_t *info; + + if (CHECK_REGION(erase->Handle)) + erase->State = ERASE_BAD_SOCKET; + else { + info = &erase->Handle->info; + if ((erase->Offset >= info->RegionSize) || + (erase->Offset & (info->BlockSize-1))) + erase->State = ERASE_BAD_OFFSET; + else if ((erase->Offset+erase->Size > info->RegionSize) || + (erase->Size & (info->BlockSize-1))) + erase->State = ERASE_BAD_SIZE; + else { + erase->State = 1; + busy = kmalloc(sizeof(erase_busy_t), GFP_KERNEL); + busy->erase = erase; + busy->client = handle; + busy->timeout.prev = busy->timeout.next = NULL; + busy->timeout.data = (u_long)busy; + busy->timeout.function = &handle_erase_timeout; + busy->prev = busy->next = NULL; + retry_erase(busy, 0); + } + } +} /* setup_erase_request */ + +/*====================================================================== + + MTD helper functions + +======================================================================*/ + +static int mtd_modify_window(window_handle_t win, mtd_mod_win_t *req) +{ + if ((win == NULL) || (win->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + win->ctl.flags = MAP_16BIT | MAP_ACTIVE; + if (req->Attributes & WIN_USE_WAIT) + win->ctl.flags |= MAP_USE_WAIT; + if (req->Attributes & WIN_MEMORY_TYPE) + win->ctl.flags |= MAP_ATTRIB; + win->ctl.speed = req->AccessSpeed; + win->ctl.card_start = req->CardOffset; + win->sock->ss_entry(win->sock->sock, SS_SetMemMap, &win->ctl); + return CS_SUCCESS; +} + +static int mtd_set_vpp(client_handle_t handle, mtd_vpp_req_t *req) +{ + socket_info_t *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + if (req->Vpp1 != req->Vpp2) + return CS_BAD_VPP; + s = SOCKET(handle); + s->socket.Vpp = req->Vpp1; + if (s->ss_entry(s->sock, SS_SetSocket, &s->socket)) + return CS_BAD_VPP; + return CS_SUCCESS; +} + +static int mtd_rdy_mask(client_handle_t handle, mtd_rdy_req_t *req) +{ + socket_info_t *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (req->Mask & CS_EVENT_READY_CHANGE) + s->socket.csc_mask |= SS_READY; + else + s->socket.csc_mask &= ~SS_READY; + if (s->ss_entry(s->sock, SS_SetSocket, &s->socket)) + return CS_GENERAL_FAILURE; + return CS_SUCCESS; +} + +int MTDHelperEntry(int func, void *a1, void *a2) +{ + switch (func) { + case MTDRequestWindow: + return CardServices(RequestWindow, a1, a2, NULL); + case MTDReleaseWindow: + return CardServices(ReleaseWindow, a1, NULL, NULL); + case MTDModifyWindow: + return mtd_modify_window(a1, a2); break; + case MTDSetVpp: + return mtd_set_vpp(a1, a2); break; + case MTDRDYMask: + return mtd_rdy_mask(a1, a2); break; + default: + return CS_UNSUPPORTED_FUNCTION; break; + } +} /* MTDHelperEntry */ + +/*====================================================================== + + This stuff is used by Card Services to initialize the table of + region info used for subsequent calls to GetFirstRegion and + GetNextRegion. + +======================================================================*/ + +static void setup_regions(client_handle_t handle, int attr, + memory_handle_t *list) +{ + int i, code, has_jedec, has_geo; + u_int offset; + cistpl_device_t device; + cistpl_jedec_t jedec; + cistpl_device_geo_t geo; + memory_handle_t r; + + DEBUG(1, "cs: setup_regions(0x%p, %d, 0x%p)\n", + handle, attr, list); + + code = (attr) ? CISTPL_DEVICE_A : CISTPL_DEVICE; + if (read_tuple(handle, code, &device) != CS_SUCCESS) + return; + code = (attr) ? CISTPL_JEDEC_A : CISTPL_JEDEC_C; + has_jedec = (read_tuple(handle, code, &jedec) == CS_SUCCESS); + if (has_jedec && (device.ndev != jedec.nid)) { +#ifdef PCMCIA_DEBUG + printk(KERN_DEBUG "cs: Device info does not match JEDEC info.\n"); +#endif + has_jedec = 0; + } + code = (attr) ? CISTPL_DEVICE_GEO_A : CISTPL_DEVICE_GEO; + has_geo = (read_tuple(handle, code, &geo) == CS_SUCCESS); + if (has_geo && (device.ndev != geo.ngeo)) { +#ifdef PCMCIA_DEBUG + printk(KERN_DEBUG "cs: Device info does not match geometry tuple.\n"); +#endif + has_geo = 0; + } + + offset = 0; + for (i = 0; i < device.ndev; i++) { + if ((device.dev[i].type != CISTPL_DTYPE_NULL) && + (device.dev[i].size != 0)) { + r = kmalloc(sizeof(*r), GFP_KERNEL); + r->region_magic = REGION_MAGIC; + r->state = 0; + r->dev_info[0] = '\0'; + r->mtd = NULL; + r->info.Attributes = (attr) ? REGION_TYPE_AM : 0; + r->info.CardOffset = offset; + r->info.RegionSize = device.dev[i].size; + r->info.AccessSpeed = device.dev[i].speed; + if (has_jedec) { + r->info.JedecMfr = jedec.id[i].mfr; + r->info.JedecInfo = jedec.id[i].info; + } else + r->info.JedecMfr = r->info.JedecInfo = 0; + if (has_geo) { + r->info.BlockSize = geo.geo[i].buswidth * + geo.geo[i].erase_block * geo.geo[i].interleave; + r->info.PartMultiple = + r->info.BlockSize * geo.geo[i].partition; + } else + r->info.BlockSize = r->info.PartMultiple = 1; + r->info.next = *list; *list = r; + } + offset += device.dev[i].size; + } +} /* setup_regions */ + +/*====================================================================== + + This is tricky. When get_first_region() is called by Driver + Services, we initialize the region info table in the socket + structure. When it is called by an MTD, we can just scan the + table for matching entries. + +======================================================================*/ + +static int match_region(client_handle_t handle, memory_handle_t list, + region_info_t *match) +{ + while (list != NULL) { + if (!(handle->Attributes & INFO_MTD_CLIENT) || + (strcmp(handle->dev_info, list->dev_info) == 0)) { + *match = list->info; + return CS_SUCCESS; + } + list = list->info.next; + } + return CS_NO_MORE_ITEMS; +} /* match_region */ + +int get_first_region(client_handle_t handle, region_info_t *rgn) +{ + socket_info_t *s = SOCKET(handle); + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + + if ((handle->Attributes & INFO_MASTER_CLIENT) && + (!(s->state & SOCKET_REGION_INFO))) { + setup_regions(handle, 0, &s->c_region); + setup_regions(handle, 1, &s->a_region); + s->state |= SOCKET_REGION_INFO; + } + + if (rgn->Attributes & REGION_TYPE_AM) + return match_region(handle, s->a_region, rgn); + else + return match_region(handle, s->c_region, rgn); +} /* get_first_region */ + +int get_next_region(client_handle_t handle, region_info_t *rgn) +{ + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + return match_region(handle, rgn->next, rgn); +} /* get_next_region */ + +/*====================================================================== + + Connect an MTD with a memory region. + +======================================================================*/ + +int register_mtd(client_handle_t handle, mtd_reg_t *reg) +{ + memory_handle_t list; + socket_info_t *s; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (reg->Attributes & REGION_TYPE_AM) + list = s->a_region; + else + list = s->c_region; + DEBUG(1, "cs: register_mtd(0x%p, '%s', 0x%x)\n", + handle, handle->dev_info, reg->Offset); + while (list) { + if (list->info.CardOffset == reg->Offset) break; + list = list->info.next; + } + if (list && (list->mtd == NULL) && + (strcmp(handle->dev_info, list->dev_info) == 0)) { + list->info.Attributes = reg->Attributes; + list->MediaID = reg->MediaID; + list->mtd = handle; + handle->mtd_count++; + return CS_SUCCESS; + } else + return CS_BAD_OFFSET; +} /* register_mtd */ + +/*====================================================================== + + Erase queue management functions + +======================================================================*/ + +int register_erase_queue(client_handle_t *handle, eraseq_hdr_t *header) +{ + eraseq_t *queue; + + if ((handle == NULL) || CHECK_HANDLE(*handle)) + return CS_BAD_HANDLE; + queue = kmalloc(sizeof(*queue), GFP_KERNEL); + queue->eraseq_magic = ERASEQ_MAGIC; + queue->handle = *handle; + queue->count = header->QueueEntryCnt; + queue->entry = header->QueueEntryArray; + *handle = (client_handle_t)queue; + return CS_SUCCESS; +} /* register_erase_queue */ + +int deregister_erase_queue(eraseq_handle_t eraseq) +{ + int i; + if (CHECK_ERASEQ(eraseq)) + return CS_BAD_HANDLE; + for (i = 0; i < eraseq->count; i++) + if (ERASE_IN_PROGRESS(eraseq->entry[i].State)) break; + if (i < eraseq->count) + return CS_BUSY; + eraseq->eraseq_magic = 0; + kfree_s(eraseq, sizeof(*eraseq)); + return CS_SUCCESS; +} /* deregister_erase_queue */ + +int check_erase_queue(eraseq_handle_t eraseq) +{ + int i; + if (CHECK_ERASEQ(eraseq)) + return CS_BAD_HANDLE; + for (i = 0; i < eraseq->count; i++) + if (eraseq->entry[i].State == ERASE_QUEUED) + setup_erase_request(eraseq->handle, &eraseq->entry[i]); + return CS_SUCCESS; +} /* check_erase_queue */ + +/*====================================================================== + + Look up the memory region matching the request, and return a + memory handle. + +======================================================================*/ + +int open_memory(client_handle_t *handle, open_mem_t *open) +{ + socket_info_t *s; + memory_handle_t region; + + if ((handle == NULL) || CHECK_HANDLE(*handle)) + return CS_BAD_HANDLE; + s = SOCKET(*handle); + if (open->Attributes & MEMORY_TYPE_AM) + region = s->a_region; + else + region = s->c_region; + while (region) { + if (region->info.CardOffset == open->Offset) break; + region = region->info.next; + } + if (region && region->mtd) { + *handle = (client_handle_t)region; + DEBUG(1, "cs: open_memory(0x%p, 0x%x) = 0x%p\n", + handle, open->Offset, region); + return CS_SUCCESS; + } else + return CS_BAD_OFFSET; +} /* open_memory */ + +/*====================================================================== + + Close a memory handle from an earlier call to OpenMemory. + + For the moment, I don't think this needs to do anything. + +======================================================================*/ + +int close_memory(memory_handle_t handle) +{ + DEBUG(1, "cs: close_memory(0x%p)\n", handle); + if (CHECK_REGION(handle)) + return CS_BAD_HANDLE; + return CS_SUCCESS; +} /* close_memory */ + +/*====================================================================== + + Read from a memory device, using a handle previously returned + by a call to OpenMemory. + +======================================================================*/ + +int read_memory(memory_handle_t handle, mem_op_t *req, caddr_t buf) +{ + mtd_request_t mtd; + if (CHECK_REGION(handle)) + return CS_BAD_HANDLE; + if (req->Offset >= handle->info.RegionSize) + return CS_BAD_OFFSET; + if (req->Offset+req->Count > handle->info.RegionSize) + return CS_BAD_SIZE; + + mtd.SrcCardOffset = req->Offset + handle->info.CardOffset; + mtd.TransferLength = req->Count; + mtd.MediaID = handle->MediaID; + mtd.Function = MTD_REQ_READ; + if (req->Attributes & MEM_OP_BUFFER_KERNEL) + mtd.Function |= MTD_REQ_KERNEL; + return do_mtd_request(handle, &mtd, buf); +} /* read_memory */ + +/*====================================================================== + + Write to a memory device, using a handle previously returned by + a call to OpenMemory. + +======================================================================*/ + +int write_memory(memory_handle_t handle, mem_op_t *req, caddr_t buf) +{ + mtd_request_t mtd; + if (CHECK_REGION(handle)) + return CS_BAD_HANDLE; + if (req->Offset >= handle->info.RegionSize) + return CS_BAD_OFFSET; + if (req->Offset+req->Count > handle->info.RegionSize) + return CS_BAD_SIZE; + + mtd.DestCardOffset = req->Offset + handle->info.CardOffset; + mtd.TransferLength = req->Count; + mtd.MediaID = handle->MediaID; + mtd.Function = MTD_REQ_WRITE; + if (req->Attributes & MEM_OP_BUFFER_KERNEL) + mtd.Function |= MTD_REQ_KERNEL; + return do_mtd_request(handle, &mtd, buf); +} /* write_memory */ + +/*====================================================================== + + This isn't needed for anything I could think of. + +======================================================================*/ + +int copy_memory(memory_handle_t handle, copy_op_t *req) +{ + if (CHECK_REGION(handle)) + return CS_BAD_HANDLE; + return CS_UNSUPPORTED_FUNCTION; +} + diff --git a/drivers/pcmcia/cardbus.c b/drivers/pcmcia/cardbus.c new file mode 100644 index 000000000..de8b76da3 --- /dev/null +++ b/drivers/pcmcia/cardbus.c @@ -0,0 +1,641 @@ +/*====================================================================== + + Cardbus device configuration + + cardbus.c 1.59 1999/09/15 15:32:19 + + 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 + <dhinds@hyper.stanford.edu>. 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 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. + + These routines handle allocating resources for Cardbus cards, as + well as setting up and shutting down Cardbus sockets. They are + called from cs.c in response to Request/ReleaseConfiguration and + Request/ReleaseIO calls. + +======================================================================*/ + +#define __NO_VERSION__ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/config.h> +#include <linux/string.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/ioport.h> +#include <asm/irq.h> +#include <asm/io.h> + +#ifndef PCMCIA_DEBUG +#define PCMCIA_DEBUG 1 +#endif +static int pc_debug = PCMCIA_DEBUG; + +#define IN_CARD_SERVICES +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" +#include "rsrc_mgr.h" + +/*====================================================================*/ + +#define FIND_FIRST_BIT(n) ((n) - ((n) & ((n)-1))) + +#define pci_readb pcibios_read_config_byte +#define pci_writeb pcibios_write_config_byte +#define pci_readw pcibios_read_config_word +#define pci_writew pcibios_write_config_word +#define pci_readl pcibios_read_config_dword +#define pci_writel pcibios_write_config_dword + +#define CB_BAR(n) (PCI_BASE_ADDRESS_0+(4*(n))) +#define CB_ROM_BASE 0x0030 + +/* Offsets in the Expansion ROM Image Header */ +#define ROM_SIGNATURE 0x0000 /* 2 bytes */ +#define ROM_DATA_PTR 0x0018 /* 2 bytes */ + +/* Offsets in the CardBus PC Card Data Structure */ +#define PCDATA_SIGNATURE 0x0000 /* 4 bytes */ +#define PCDATA_VPD_PTR 0x0008 /* 2 bytes */ +#define PCDATA_LENGTH 0x000a /* 2 bytes */ +#define PCDATA_REVISION 0x000c +#define PCDATA_IMAGE_SZ 0x0010 /* 2 bytes */ +#define PCDATA_ROM_LEVEL 0x0012 /* 2 bytes */ +#define PCDATA_CODE_TYPE 0x0014 +#define PCDATA_INDICATOR 0x0015 + +typedef struct cb_config_t { + u_int size[7]; + struct pci_dev dev; +} cb_config_t; + +#define BASE(dev,n) ((dev).resource[n].start) +#define ROM(dev) ((dev).resource[6].start) + +/* There are three classes of bridge maps: IO ports, + non-prefetchable memory, and prefetchable memory */ +typedef enum { B_IO, B_M1, B_M2 } bridge_type; + +/*===================================================================== + + Expansion ROM's have a special layout, and pointers specify an + image number and an offset within that image. check_rom() + verifies that the expansion ROM exists and has the standard + layout. xlate_rom_addr() converts an image/offset address to an + absolute offset from the ROM's base address. + +=====================================================================*/ + +static int check_rom(u_char *b, u_long len) +{ + u_int img = 0, ofs = 0, sz; + u_short data; + DEBUG(0, "ROM image dump:\n"); + while ((readb(b) == 0x55) && (readb(b+1) == 0xaa)) { + data = readb(b+ROM_DATA_PTR) + + (readb(b+ROM_DATA_PTR+1) << 8); + sz = 512 * (readb(b+data+PCDATA_IMAGE_SZ) + + (readb(b+data+PCDATA_IMAGE_SZ+1) << 8)); + DEBUG(0, " image %d: 0x%06x-0x%06x, signature %c%c%c%c\n", + img, ofs, ofs+sz-1, + readb(b+data+PCDATA_SIGNATURE), + readb(b+data+PCDATA_SIGNATURE+1), + readb(b+data+PCDATA_SIGNATURE+2), + readb(b+data+PCDATA_SIGNATURE+3)); + ofs += sz; img++; + if ((readb(b+data+PCDATA_INDICATOR) & 0x80) || + (sz == 0) || (ofs >= len)) break; + b += sz; + } + return img; +} + +static u_int xlate_rom_addr(u_char *b, u_int addr) +{ + u_int img = 0, ofs = 0, sz; + u_short data; + while ((readb(b) == 0x55) && (readb(b+1) == 0xaa)) { + if (img == (addr >> 28)) + return (addr & 0x0fffffff) + ofs; + data = readb(b+ROM_DATA_PTR) + (readb(b+ROM_DATA_PTR+1) << 8); + sz = 512 * (readb(b+data+PCDATA_IMAGE_SZ) + + (readb(b+data+PCDATA_IMAGE_SZ+1) << 8)); + if ((sz == 0) || (readb(b+data+PCDATA_INDICATOR) & 0x80)) + break; + b += sz; ofs += sz; img++; + } + return 0; +} + +/*===================================================================== + + These are similar to setup_cis_mem and release_cis_mem for 16-bit + cards. The "result" that is used externally is the cb_cis_virt + pointer in the socket_info_t structure. + +=====================================================================*/ + +int cb_setup_cis_mem(socket_info_t *s, int space) +{ + cb_bridge_map *m = &s->cb_cis_map; + u_long base = 0; + u_int sz, br; + + if (space == s->cb_cis_space) + return CS_SUCCESS; + else if (s->cb_cis_space != 0) + cb_release_cis_mem(s); + DEBUG(1, "cs: cb_setup_cis_mem(space %d)\n", space); + /* If socket is configured, then use existing memory mapping */ + if (s->lock_count) { + s->cb_cis_virt = + ioremap(BASE(s->cb_config[0].dev, space-1), + s->cb_config[0].size[space-1] & ~3); + s->cb_cis_space = space; + return CS_SUCCESS; + } + + /* Not configured? Then set up temporary map */ + br = (space == 7) ? CB_ROM_BASE : CB_BAR(space-1); + pci_writel(s->cap.cardbus, 0, br, 0xffffffff); + pci_readl(s->cap.cardbus, 0, br, &sz); + sz &= PCI_BASE_ADDRESS_MEM_MASK; + sz = FIND_FIRST_BIT(sz); + if (sz < PAGE_SIZE) sz = PAGE_SIZE; + if (find_mem_region(&base, sz, "cb_enabler", sz, 0) != 0) { + printk(KERN_NOTICE "cs: could not allocate %dK memory for" + " CardBus socket %d\n", sz/1024, s->sock); + return CS_OUT_OF_RESOURCE; + } + s->cb_cis_space = space; + s->cb_cis_virt = ioremap(base, sz); + DEBUG(1, " phys 0x%08lx-0x%08lx, virt 0x%08lx\n", + base, base+sz-1, (u_long)s->cb_cis_virt); + pci_writel(s->cap.cardbus, 0, br, base | 1); + pci_writeb(s->cap.cardbus, 0, PCI_COMMAND, PCI_COMMAND_MEMORY); + m->map = 0; m->flags = MAP_ACTIVE; + m->start = base; m->stop = base+sz-1; + s->ss_entry(s->sock, SS_SetBridge, m); + if ((space == 7) && (check_rom(s->cb_cis_virt, sz) == 0)) { + printk(KERN_NOTICE "cs: no valid ROM images found!\n"); + return CS_READ_FAILURE; + } + return CS_SUCCESS; +} + +void cb_release_cis_mem(socket_info_t *s) +{ + cb_bridge_map *m = &s->cb_cis_map; + u_int br; + if (s->cb_cis_virt) { + DEBUG(1, "cs: cb_release_cis_mem()\n"); + iounmap(s->cb_cis_virt); + s->cb_cis_virt = NULL; + s->cb_cis_space = 0; + } + if (m->start) { + /* This is overkill: we probably only need to release the + memory region, but the rest should be safe */ + br = (s->cb_cis_space == 7) ? + CB_ROM_BASE : CB_BAR(s->cb_cis_space-1); + m->map = 0; m->flags = 0; + s->ss_entry(s->sock, SS_SetBridge, m); + pci_writeb(s->cap.cardbus, 0, PCI_COMMAND, 0); + pci_writel(s->cap.cardbus, 0, br, 0); + release_mem_region(m->start, m->stop - m->start + 1); + m->start = 0; + } +} + +/*===================================================================== + + This is used by the CIS processing code to read CIS information + from a CardBus device. + +=====================================================================*/ + +void read_cb_mem(socket_info_t *s, u_char fn, int space, + u_int addr, u_int len, void *ptr) +{ + DEBUG(3, "cs: read_cb_mem(%d, %#x, %u)\n", space, addr, len); + if (space == 0) { + if (addr+len > 0x100) goto fail; + for (; len; addr++, ptr++, len--) + pci_readb(s->cap.cardbus, fn, addr, (u_char *)ptr); + } else { + if (cb_setup_cis_mem(s, space) != 0) goto fail; + if (space == 7) { + addr = xlate_rom_addr(s->cb_cis_virt, addr); + if (addr == 0) goto fail; + } + if (addr+len > s->cb_cis_map.stop - s->cb_cis_map.start) + goto fail; + if (s->cb_cis_virt != NULL) + for (; len; addr++, ptr++, len--) + *(u_char *)ptr = readb(s->cb_cis_virt+addr); + } + return; + fail: + memset(ptr, 0xff, len); + return; +} + +/*===================================================================== + + cb_alloc() and cb_free() allocate and free the kernel data + structures for a Cardbus device, and handle the lowest level PCI + device setup issues. + +=====================================================================*/ + +int cb_alloc(socket_info_t *s) +{ + struct pci_dev tmp; + u_short vend, v, dev; + u_char i, hdr, fn, bus = s->cap.cardbus; + cb_config_t *c; + + tmp.bus = s->cap.cb_bus; tmp.devfn = 0; + tmp.next = pci_devices; pci_devices = &tmp; + + pci_readw(bus, 0, PCI_VENDOR_ID, &vend); + pci_readw(bus, 0, PCI_DEVICE_ID, &dev); + printk(KERN_INFO "cs: cb_alloc(bus %d): vendor 0x%04x, " + "device 0x%04x\n", bus, vend, dev); + + pci_readb(bus, 0, PCI_HEADER_TYPE, &hdr); + if (hdr & 0x80) { + /* Count functions */ + for (fn = 0; fn < 8; fn++) { + tmp.devfn = fn; + pci_readw(bus, fn, PCI_VENDOR_ID, &v); + if (v != vend) break; + } + } else fn = 1; + s->functions = fn; + + c = kmalloc(fn * sizeof(struct cb_config_t), GFP_ATOMIC); + if (!c) return CS_OUT_OF_RESOURCE; + memset(c, 0, fn * sizeof(struct cb_config_t)); + s->cb_config = c; + + pci_devices = tmp.next; + for (i = 0; i < fn; i++) { + c[i].dev.bus = s->cap.cb_bus; + c[i].dev.devfn = i; + if (i < fn-1) { + c[i].dev.sibling = c[i].dev.next = &c[i+1].dev; + } + } + s->cap.cb_bus->devices = &c[0].dev; + /* Link into PCI device chain */ + c[s->functions-1].dev.next = pci_devices; + pci_devices = &c[0].dev; + for (i = 0; i < fn; i++) { + c[i].dev.vendor = vend; + pci_readw(bus, i, PCI_DEVICE_ID, &c[i].dev.device); + pci_readl(bus, i, PCI_CLASS_REVISION, &c[i].dev.class); + c[i].dev.class >>= 8; + c[i].dev.hdr_type = hdr; +#ifdef CONFIG_PROC_FS + pci_proc_attach_device(&c[i].dev); +#endif + } + + return CS_SUCCESS; +} + +void cb_free(socket_info_t *s) +{ + cb_config_t *c = s->cb_config; + + if (c) { + struct pci_dev **p, *q; + /* Unlink from PCI device chain */ + for (p = &pci_devices; *p; p = &((*p)->next)) + if (*p == &c[0].dev) break; + for (q = *p; q; q = q->next) { + if (q->bus != (*p)->bus) break; +#ifdef CONFIG_PROC_FS + pci_proc_detach_device(q); +#endif + } + if (*p) *p = q; + s->cap.cb_bus->devices = NULL; + kfree(s->cb_config); + s->cb_config = NULL; + printk(KERN_INFO "cs: cb_free(bus %d)\n", s->cap.cardbus); + } +} + +/*===================================================================== + + cb_config() has the job of allocating all system resources that + a Cardbus card requires. Rather than using the CIS (which seems + to not always be present), it treats the card as an ordinary PCI + device, and probes the base address registers to determine each + function's IO and memory space needs. + + It is called from the RequestIO card service. + +======================================================================*/ + +int cb_config(socket_info_t *s) +{ + cb_config_t *c = s->cb_config; + u_char fn = s->functions; + u_char i, j, bus = s->cap.cardbus, *name; + u_int sz, align, m, mask[3], num[3], base[3]; + int irq, try, ret; + + printk(KERN_INFO "cs: cb_config(bus %d)\n", s->cap.cardbus); + + /* Determine IO and memory space needs */ + num[B_IO] = num[B_M1] = num[B_M2] = 0; + mask[B_IO] = mask[B_M1] = mask[B_M2] = 0; + for (i = 0; i < fn; i++) { + for (j = 0; j < 6; j++) { + pci_writel(bus, i, CB_BAR(j), 0xffffffff); + pci_readl(bus, i, CB_BAR(j), &sz); + if (sz == 0) continue; + if (sz & PCI_BASE_ADDRESS_SPACE) { + m = B_IO; + sz &= PCI_BASE_ADDRESS_IO_MASK; + } else { + m = (sz & PCI_BASE_ADDRESS_MEM_PREFETCH) ? B_M2 : B_M1; + sz &= PCI_BASE_ADDRESS_MEM_MASK; + } + sz = FIND_FIRST_BIT(sz); + c[i].size[j] = sz | m; + if (m && (sz < PAGE_SIZE)) sz = PAGE_SIZE; + num[m] += sz; mask[m] |= sz; + } + pci_writel(bus, i, CB_ROM_BASE, 0xffffffff); + pci_readl(bus, i, CB_ROM_BASE, &sz); + if (sz != 0) { + sz = FIND_FIRST_BIT(sz & ~0x00000001); + c[i].size[6] = sz | B_M1; + if (sz < PAGE_SIZE) sz = PAGE_SIZE; + num[B_M1] += sz; mask[B_M1] |= sz; + } + } + + /* Allocate system resources */ + name = "cb_enabler"; + s->io[0].NumPorts = num[B_IO]; + s->io[0].BasePort = 0; + if (num[B_IO]) { + if (find_io_region(&s->io[0].BasePort, num[B_IO], name) != 0) { + printk(KERN_NOTICE "cs: could not allocate %d IO ports for" + " CardBus socket %d\n", num[B_IO], s->sock); + goto failed; + } + base[B_IO] = s->io[0].BasePort + num[B_IO]; + } + s->win[0].size = num[B_M1]; + s->win[0].base = 0; + if (num[B_M1]) { + if (find_mem_region(&s->win[0].base, num[B_M1], + name, num[B_M1], 0) != 0) { + printk(KERN_NOTICE "cs: could not allocate %dK memory for" + " CardBus socket %d\n", num[B_M1]/1024, s->sock); + goto failed; + } + base[B_M1] = s->win[0].base + num[B_M1]; + } + s->win[1].size = num[B_M2]; + s->win[1].base = 0; + if (num[B_M2]) { + if (find_mem_region(&s->win[1].base, num[B_M2], + name, num[B_M2], 0) != 0) { + printk(KERN_NOTICE "cs: could not allocate %dK memory for" + " CardBus socket %d\n", num[B_M2]/1024, s->sock); + goto failed; + } + base[B_M2] = s->win[1].base + num[B_M2]; + } + + /* Set up base address registers */ + while (mask[B_IO] | mask[B_M1] | mask[B_M2]) { + num[B_IO] = FIND_FIRST_BIT(mask[B_IO]); mask[B_IO] -= num[B_IO]; + num[B_M1] = FIND_FIRST_BIT(mask[B_M1]); mask[B_M1] -= num[B_M1]; + num[B_M2] = FIND_FIRST_BIT(mask[B_M2]); mask[B_M2] -= num[B_M2]; + for (i = 0; i < fn; i++) { + for (j = 0; j < 7; j++) { + sz = c[i].size[j]; + m = sz & 3; sz &= ~3; + align = (m && (sz < PAGE_SIZE)) ? PAGE_SIZE : sz; + if (sz && (align == num[m])) { + base[m] -= align; + if (j < 6) + printk(KERN_INFO " fn %d bar %d: ", i, j+1); + else + printk(KERN_INFO " fn %d rom: ", i); + printk("%s 0x%x-0x%x\n", (m) ? "mem" : "io", + base[m], base[m]+sz-1); + BASE(c[i].dev, j) = base[m]; + } + } + } + } + + /* Allocate interrupt if needed */ + s->irq.AssignedIRQ = irq = 0; ret = -1; + for (i = 0; i < fn; i++) { + pci_readb(bus, i, PCI_INTERRUPT_PIN, &j); + if (j == 0) continue; + if (irq == 0) { + if (s->cap.irq_mask & (1 << s->cap.pci_irq)) { + irq = s->cap.pci_irq; + ret = 0; + } +#ifdef CONFIG_ISA + else + for (try = 0; try < 2; try++) { + for (irq = 0; irq < 32; irq++) + if ((s->cap.irq_mask >> irq) & 1) { + ret = try_irq(IRQ_TYPE_EXCLUSIVE, irq, try); + if (ret == 0) break; + } + if (ret == 0) break; + } + if (ret != 0) { + printk(KERN_NOTICE "cs: could not allocate interrupt" + " for CardBus socket %d\n", s->sock); + goto failed; + } +#endif + s->irq.AssignedIRQ = irq; + } + } + c[0].dev.irq = irq; + + return CS_SUCCESS; + +failed: + cb_release(s); + return CS_OUT_OF_RESOURCE; +} + +/*====================================================================== + + cb_release() releases all the system resources (IO and memory + space, and interrupt) committed for a Cardbus card by a prior call + to cb_config(). + + It is called from the ReleaseIO() service. + +======================================================================*/ + +void cb_release(socket_info_t *s) +{ + cb_config_t *c = s->cb_config; + + DEBUG(0, "cs: cb_release(bus %d)\n", s->cap.cardbus); + + if (s->win[0].size > 0) + release_mem_region(s->win[0].base, s->win[0].size); + if (s->win[1].size > 0) + release_mem_region(s->win[1].base, s->win[1].size); + if (s->io[0].NumPorts > 0) + release_region(s->io[0].BasePort, s->io[0].NumPorts); + s->io[0].NumPorts = 0; +#ifdef CONFIG_ISA + if ((c[0].dev.irq != 0) && (c[0].dev.irq != s->cap.pci_irq)) + undo_irq(IRQ_TYPE_EXCLUSIVE, c[0].dev.irq); +#endif +} + +/*===================================================================== + + cb_enable() has the job of configuring a socket for a Cardbus + card, and initializing the card's PCI configuration registers. + + It first sets up the Cardbus bridge windows, for IO and memory + accesses. Then, it initializes each card function's base address + registers, interrupt line register, and command register. + + It is called as part of the RequestConfiguration card service. + It should be called after a previous call to cb_config() (via the + RequestIO service). + +======================================================================*/ + +void cb_enable(socket_info_t *s) +{ + u_char i, j, bus = s->cap.cardbus; + cb_config_t *c = s->cb_config; + + DEBUG(0, "cs: cb_enable(bus %d)\n", bus); + + /* Configure bridge */ + if (s->cb_cis_map.start) + cb_release_cis_mem(s); + for (i = 0; i < 3; i++) { + cb_bridge_map m; + switch (i) { + case B_IO: + m.map = 0; m.flags = MAP_IOSPACE | MAP_ACTIVE; + m.start = s->io[0].BasePort; + m.stop = m.start + s->io[0].NumPorts - 1; + break; + case B_M1: + m.map = 0; m.flags = MAP_ACTIVE; + m.start = s->win[0].base; + m.stop = m.start + s->win[0].size - 1; + break; + case B_M2: + m.map = 1; m.flags = MAP_PREFETCH | MAP_ACTIVE; + m.start = s->win[1].base; + m.stop = m.start + s->win[1].size - 1; + break; + } + if (m.start == 0) continue; + DEBUG(0, " bridge %s map %d (flags 0x%x): 0x%x-0x%x\n", + (m.flags & MAP_IOSPACE) ? "io" : "mem", + m.map, m.flags, m.start, m.stop); + s->ss_entry(s->sock, SS_SetBridge, &m); + } + + /* Set up base address registers */ + for (i = 0; i < s->functions; i++) { + for (j = 0; j < 6; j++) { + if (BASE(c[i].dev, j) != 0) + pci_writel(bus, i, CB_BAR(j), BASE(c[i].dev, j)); + } + if (ROM(c[i].dev) != 0) + pci_writel(bus, i, CB_ROM_BASE, ROM(c[i].dev) | 1); + } + + /* Set up PCI interrupt and command registers */ + for (i = 0; i < s->functions; i++) { + pci_writeb(bus, i, PCI_COMMAND, PCI_COMMAND_MASTER | + PCI_COMMAND_IO | PCI_COMMAND_MEMORY); + pci_writeb(bus, i, PCI_CACHE_LINE_SIZE, 8); + } + + if (s->irq.AssignedIRQ) { + for (i = 0; i < s->functions; i++) + pci_writeb(bus, i, PCI_INTERRUPT_LINE, + s->irq.AssignedIRQ); + s->socket.io_irq = s->irq.AssignedIRQ; + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + } +} + +/*====================================================================== + + cb_disable() unconfigures a Cardbus card previously set up by + cb_enable(). + + It is called from the ReleaseConfiguration service. + +======================================================================*/ + +void cb_disable(socket_info_t *s) +{ + u_char i; + cb_bridge_map m = { 0, 0, 0, 0xffff }; + + DEBUG(0, "cs: cb_disable(bus %d)\n", s->cap.cardbus); + + /* Turn off bridge windows */ + if (s->cb_cis_map.start) + cb_release_cis_mem(s); + for (i = 0; i < 3; i++) { + switch (i) { + case B_IO: m.map = 0; m.flags = MAP_IOSPACE; break; + case B_M1: m.map = m.flags = 0; break; + case B_M2: m.map = 1; m.flags = 0; break; + } + s->ss_entry(s->sock, SS_SetBridge, &m); + } +} diff --git a/drivers/pcmcia/cb_enabler.c b/drivers/pcmcia/cb_enabler.c new file mode 100644 index 000000000..b0e75ceec --- /dev/null +++ b/drivers/pcmcia/cb_enabler.c @@ -0,0 +1,397 @@ +/*====================================================================== + + Cardbus device enabler + + cb_enabler.c 1.23 1999/09/15 15:32:19 + + 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 + <dhinds@hyper.stanford.edu>. 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 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. + + The general idea: + + A client driver registers using register_driver(). This module + then creates a Card Services pseudo-client and registers it, and + configures the socket if this is the first client. It then + invokes the appropriate PCI client routines in response to Card + Services events. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/malloc.h> +#include <linux/string.h> +#include <linux/timer.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> + +#ifdef PCMCIA_DEBUG +static int pc_debug = PCMCIA_DEBUG; +MODULE_PARM(pc_debug, "i"); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static char *version = +"cb_enabler.c 1.23 1999/09/15 15:32:19 (David Hinds)"; +#else +#define DEBUG(n, args...) do { } while (0) +#endif + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +/*====================================================================*/ + +typedef struct driver_info_t { + dev_link_t *(*attach)(void); + dev_info_t dev_info; + driver_operations *ops; + dev_link_t *dev_list; +} driver_info_t; + +static dev_link_t *cb_attach(int n); +#define MK_ENTRY(fn, n) \ +static dev_link_t *fn(void) { return cb_attach(n); } + +#define MAX_DRIVER 4 + +MK_ENTRY(attach_0, 0); +MK_ENTRY(attach_1, 1); +MK_ENTRY(attach_2, 2); +MK_ENTRY(attach_3, 3); + +static driver_info_t driver[4] = { + { attach_0 }, { attach_1 }, { attach_2 }, { attach_3 } +}; + +typedef struct bus_info_t { + u_char bus; + int flags, ncfg, nuse; + dev_link_t *owner; +} bus_info_t; + +#define DID_REQUEST 1 +#define DID_CONFIG 2 + +static void cb_release(u_long arg); +static int cb_event(event_t event, int priority, + event_callback_args_t *args); + +static void cb_detach(dev_link_t *); + +static bus_info_t bus_table[MAX_DRIVER]; + +/*====================================================================*/ + +static void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + CardServices(ReportError, handle, &err); +} + +/*====================================================================*/ + +struct dev_link_t *cb_attach(int n) +{ + client_reg_t client_reg; + dev_link_t *link; + int ret; + + DEBUG(0, "cb_attach(%d)\n", n); + + MOD_INC_USE_COUNT; + link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL); + memset(link, 0, sizeof(struct dev_link_t)); + + link->conf.IntType = INT_CARDBUS; + link->conf.Vcc = 33; + + /* Insert into instance chain for this driver */ + link->priv = &driver[n]; + link->next = driver[n].dev_list; + driver[n].dev_list = link; + + /* Register with Card Services */ + client_reg.dev_info = &driver[n].dev_info; + client_reg.Attributes = INFO_IO_CLIENT | INFO_CARD_SHARE; + client_reg.event_handler = &cb_event; + client_reg.EventMask = + CS_EVENT_RESET_REQUEST | CS_EVENT_CARD_RESET | + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.Version = 0x0210; + client_reg.event_callback_args.client_data = link; + ret = CardServices(RegisterClient, &link->handle, &client_reg); + if (ret != 0) { + cs_error(link->handle, RegisterClient, ret); + cb_detach(link); + return NULL; + } + return link; +} + +/*====================================================================*/ + +static void cb_detach(dev_link_t *link) +{ + driver_info_t *dev = link->priv; + dev_link_t **linkp; + bus_info_t *b = (void *)link->win; + + DEBUG(0, "cb_detach(0x%p)\n", link); + + /* Locate device structure */ + for (linkp = &dev->dev_list; *linkp; linkp = &(*linkp)->next) + if (*linkp == link) break; + if (*linkp == NULL) + return; + + if (link->state & DEV_CONFIG) + cb_release((u_long)link); + + /* Don't drop Card Services connection if we are the bus owner */ + if ((b->flags != 0) && (link == b->owner)) { + link->state |= DEV_STALE_LINK; + return; + } + + if (link->handle) + CardServices(DeregisterClient, link->handle); + + *linkp = link->next; + kfree_s(link, sizeof(struct dev_link_t)); + MOD_DEC_USE_COUNT; +} + +/*====================================================================*/ + +static void cb_config(dev_link_t *link) +{ + client_handle_t handle = link->handle; + driver_info_t *drv = link->priv; + dev_locator_t loc; + bus_info_t *b; + config_info_t config; + u_char bus, devfn; + int i; + + DEBUG(0, "cb_config(0x%p)\n", link); + link->state |= DEV_CONFIG; + + /* Get PCI bus info */ + CardServices(GetConfigurationInfo, handle, &config); + bus = config.Option; devfn = config.Function; + + /* Is this a new bus? */ + for (i = 0; i < MAX_DRIVER; i++) + if (bus == bus_table[i].bus) break; + if (i == MAX_DRIVER) { + for (i = 0; i < MAX_DRIVER; i++) + if (bus_table[i].bus == 0) break; + b = &bus_table[i]; link->win = (void *)b; + b->bus = bus; + b->flags = 0; + b->ncfg = b->nuse = 1; + + /* Special hook: CS know what to do... */ + i = CardServices(RequestIO, handle, NULL); + if (i != CS_SUCCESS) { + cs_error(handle, RequestIO, i); + return; + } + b->flags |= DID_REQUEST; + b->owner = link; + i = CardServices(RequestConfiguration, handle, &link->conf); + if (i != CS_SUCCESS) { + cs_error(handle, RequestConfiguration, i); + return; + } + b->flags |= DID_CONFIG; + } else { + b = &bus_table[i]; link->win = (void *)b; + if (b->flags & DID_CONFIG) { + b->ncfg++; b->nuse++; + } + } + loc.bus = LOC_PCI; + loc.b.pci.bus = bus; + loc.b.pci.devfn = devfn; + link->dev = drv->ops->attach(&loc); + + link->state &= ~DEV_CONFIG_PENDING; +} + +/*====================================================================*/ + +static void cb_release(u_long arg) +{ + dev_link_t *link = (dev_link_t *)arg; + driver_info_t *drv = link->priv; + bus_info_t *b = (void *)link->win; + + DEBUG(0, "cb_release(0x%p)\n", link); + + if (link->dev != NULL) { + drv->ops->detach(link->dev); + link->dev = NULL; + } + if (link->state & DEV_CONFIG) { + /* If we're suspended, config was already released */ + if (link->state & DEV_SUSPEND) + b->flags &= ~DID_CONFIG; + else if ((b->flags & DID_CONFIG) && (--b->ncfg == 0)) { + CardServices(ReleaseConfiguration, b->owner->handle, + &b->owner->conf); + b->flags &= ~DID_CONFIG; + } + if ((b->flags & DID_REQUEST) && (--b->nuse == 0)) { + CardServices(ReleaseIO, b->owner->handle, NULL); + b->flags &= ~DID_REQUEST; + } + if (b->flags == 0) { + if (b->owner && (b->owner->state & DEV_STALE_LINK)) + cb_detach(b->owner); + b->bus = 0; b->owner = NULL; + } + } + link->state &= ~DEV_CONFIG; +} + +/*====================================================================*/ + +static int cb_event(event_t event, int priority, + event_callback_args_t *args) +{ + dev_link_t *link = args->client_data; + driver_info_t *drv = link->priv; + bus_info_t *b = (void *)link->win; + + DEBUG(0, "cb_event(0x%06x)\n", event); + + switch (event) { + case CS_EVENT_CARD_REMOVAL: + link->state &= ~DEV_PRESENT; + if (link->state & DEV_CONFIG) + cb_release((u_long)link); + break; + case CS_EVENT_CARD_INSERTION: + link->state |= DEV_PRESENT | DEV_CONFIG_PENDING; + cb_config(link); + break; + case CS_EVENT_PM_SUSPEND: + link->state |= DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_RESET_PHYSICAL: + if (link->state & DEV_CONFIG) { + if (drv->ops->suspend != NULL) + drv->ops->suspend(link->dev); + b->ncfg--; + if (b->ncfg == 0) + CardServices(ReleaseConfiguration, link->handle, + &link->conf); + } + break; + case CS_EVENT_PM_RESUME: + link->state &= ~DEV_SUSPEND; + /* Fall through... */ + case CS_EVENT_CARD_RESET: + if (link->state & DEV_CONFIG) { + b->ncfg++; + if (b->ncfg == 1) + CardServices(RequestConfiguration, link->handle, + &link->conf); + if (drv->ops->resume != NULL) + drv->ops->resume(link->dev); + } + break; + } + return 0; +} + +/*====================================================================*/ + +int register_driver(struct driver_operations *ops) +{ + int i; + + DEBUG(0, "register_driver('%s')\n", ops->name); + + for (i = 0; i < MAX_DRIVER; i++) + if (driver[i].ops == NULL) break; + if (i == MAX_DRIVER) + return -1; + + MOD_INC_USE_COUNT; + driver[i].ops = ops; + strcpy(driver[i].dev_info, ops->name); + register_pccard_driver(&driver[i].dev_info, driver[i].attach, + &cb_detach); + return 0; +} + +void unregister_driver(struct driver_operations *ops) +{ + int i; + + DEBUG(0, "unregister_driver('%s')\n", ops->name); + for (i = 0; i < MAX_DRIVER; i++) + if (driver[i].ops == ops) break; + if (i < MAX_DRIVER) { + unregister_pccard_driver(&driver[i].dev_info); + driver[i].ops = NULL; + MOD_DEC_USE_COUNT; + } +} + +/*====================================================================*/ + +static int __init init_cb_enabler(void) +{ + servinfo_t serv; + DEBUG(0, "%s\n", version); + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "cb_enabler: Card Services release " + "does not match!\n"); + return -1; + } + return 0; +} + +static void __exit exit_cb_enabler(void) +{ + DEBUG(0, "cb_enabler: unloading\n"); +} + +module_init(init_cb_enabler); +module_exit(exit_cb_enabler); + +/*====================================================================*/ + diff --git a/drivers/pcmcia/cirrus.h b/drivers/pcmcia/cirrus.h new file mode 100644 index 000000000..43527010a --- /dev/null +++ b/drivers/pcmcia/cirrus.h @@ -0,0 +1,157 @@ +/* + * cirrus.h 1.3 1999/08/28 04:01:46 + * + * 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 + * <dhinds@hyper.stanford.edu>. 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 Public License version 2 (the "GPL"), in which + * case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_CIRRUS_H +#define _LINUX_CIRRUS_H + +#ifndef PCI_VENDOR_ID_CIRRUS +#define PCI_VENDOR_ID_CIRRUS 0x1013 +#endif +#ifndef PCI_DEVICE_ID_CIRRUS_6729 +#define PCI_DEVICE_ID_CIRRUS_6729 0x1100 +#endif +#ifndef PCI_DEVICE_ID_CIRRUS_6832 +#define PCI_DEVICE_ID_CIRRUS_6832 0x1110 +#endif + +#define PD67_MISC_CTL_1 0x16 /* Misc control 1 */ +#define PD67_FIFO_CTL 0x17 /* FIFO control */ +#define PD67_MISC_CTL_2 0x1E /* Misc control 2 */ +#define PD67_CHIP_INFO 0x1f /* Chip information */ +#define PD67_ATA_CTL 0x026 /* 6730: ATA control */ +#define PD67_EXT_INDEX 0x2e /* Extension index */ +#define PD67_EXT_DATA 0x2f /* Extension data */ + +/* PD6722 extension registers -- indexed in PD67_EXT_INDEX */ +#define PD67_DATA_MASK0 0x01 /* Data mask 0 */ +#define PD67_DATA_MASK1 0x02 /* Data mask 1 */ +#define PD67_DMA_CTL 0x03 /* DMA control */ + +/* PD6730 extension registers -- indexed in PD67_EXT_INDEX */ +#define PD67_EXT_CTL_1 0x03 /* Extension control 1 */ +#define PD67_MEM_PAGE(n) ((n)+5) /* PCI window bits 31:24 */ +#define PD67_EXTERN_DATA 0x0a +#define PD67_MISC_CTL_3 0x25 +#define PD67_SMB_PWR_CTL 0x26 + +/* I/O window address offset */ +#define PD67_IO_OFF(w) (0x36+((w)<<1)) + +/* Timing register sets */ +#define PD67_TIME_SETUP(n) (0x3a + 3*(n)) +#define PD67_TIME_CMD(n) (0x3b + 3*(n)) +#define PD67_TIME_RECOV(n) (0x3c + 3*(n)) + +/* Flags for PD67_MISC_CTL_1 */ +#define PD67_MC1_5V_DET 0x01 /* 5v detect */ +#define PD67_MC1_MEDIA_ENA 0x01 /* 6730: Multimedia enable */ +#define PD67_MC1_VCC_3V 0x02 /* 3.3v Vcc */ +#define PD67_MC1_PULSE_MGMT 0x04 +#define PD67_MC1_PULSE_IRQ 0x08 +#define PD67_MC1_SPKR_ENA 0x10 +#define PD67_MC1_INPACK_ENA 0x80 + +/* Flags for PD67_FIFO_CTL */ +#define PD67_FIFO_EMPTY 0x80 + +/* Flags for PD67_MISC_CTL_2 */ +#define PD67_MC2_FREQ_BYPASS 0x01 +#define PD67_MC2_DYNAMIC_MODE 0x02 +#define PD67_MC2_SUSPEND 0x04 +#define PD67_MC2_5V_CORE 0x08 +#define PD67_MC2_LED_ENA 0x10 /* IRQ 12 is LED enable */ +#define PD67_MC2_FAST_PCI 0x10 /* 6729: PCI bus > 25 MHz */ +#define PD67_MC2_3STATE_BIT7 0x20 /* Floppy change bit */ +#define PD67_MC2_DMA_MODE 0x40 +#define PD67_MC2_IRQ15_RI 0x80 /* IRQ 15 is ring enable */ + +/* Flags for PD67_CHIP_INFO */ +#define PD67_INFO_SLOTS 0x20 /* 0 = 1 slot, 1 = 2 slots */ +#define PD67_INFO_CHIP_ID 0xc0 +#define PD67_INFO_REV 0x1c + +/* Fields in PD67_TIME_* registers */ +#define PD67_TIME_SCALE 0xc0 +#define PD67_TIME_SCALE_1 0x00 +#define PD67_TIME_SCALE_16 0x40 +#define PD67_TIME_SCALE_256 0x80 +#define PD67_TIME_SCALE_4096 0xc0 +#define PD67_TIME_MULT 0x3f + +/* Fields in PD67_DMA_CTL */ +#define PD67_DMA_MODE 0xc0 +#define PD67_DMA_OFF 0x00 +#define PD67_DMA_DREQ_INPACK 0x40 +#define PD67_DMA_DREQ_WP 0x80 +#define PD67_DMA_DREQ_BVD2 0xc0 +#define PD67_DMA_PULLUP 0x20 /* Disable socket pullups? */ + +/* Fields in PD67_EXT_CTL_1 */ +#define PD67_EC1_VCC_PWR_LOCK 0x01 +#define PD67_EC1_AUTO_PWR_CLEAR 0x02 +#define PD67_EC1_LED_ENA 0x04 +#define PD67_EC1_INV_CARD_IRQ 0x08 +#define PD67_EC1_INV_MGMT_IRQ 0x10 +#define PD67_EC1_PULLUP_CTL 0x20 + +/* Fields in PD67_MISC_CTL_3 */ +#define PD67_MC3_IRQ_MASK 0x03 +#define PD67_MC3_IRQ_PCPCI 0x00 +#define PD67_MC3_IRQ_EXTERN 0x01 +#define PD67_MC3_IRQ_PCIWAY 0x02 +#define PD67_MC3_IRQ_PCI 0x03 +#define PD67_MC3_PWR_MASK 0x0c +#define PD67_MC3_PWR_SERIAL 0x00 +#define PD67_MC3_PWR_TI2202 0x08 +#define PD67_MC3_PWR_SMB 0x0c + +/* Register definitions for Cirrus PD6832 PCI-to-CardBus bridge */ + +/* PD6832 extension registers -- indexed in PD67_EXT_INDEX */ +#define PD68_EXT_CTL_2 0x0b +#define PD68_PCI_SPACE 0x22 +#define PD68_PCCARD_SPACE 0x23 +#define PD68_WINDOW_TYPE 0x24 +#define PD68_EXT_CSC 0x2e +#define PD68_MISC_CTL_4 0x2f +#define PD68_MISC_CTL_5 0x30 +#define PD68_MISC_CTL_6 0x31 + +/* Extra flags in PD67_MISC_CTL_3 */ +#define PD68_MC3_HW_SUSP 0x10 +#define PD68_MC3_MM_EXPAND 0x40 +#define PD68_MC3_MM_ARM 0x80 + +/* Bridge Control Register */ +#define PD6832_BCR_MGMT_IRQ_ENA 0x0800 + +/* Socket Number Register */ +#define PD6832_SOCKET_NUMBER 0x004c /* 8 bit */ + +#endif /* _LINUX_CIRRUS_H */ diff --git a/drivers/pcmcia/cistpl.c b/drivers/pcmcia/cistpl.c new file mode 100644 index 000000000..690ea894f --- /dev/null +++ b/drivers/pcmcia/cistpl.c @@ -0,0 +1,1394 @@ +/*====================================================================== + + PCMCIA Card Information Structure parser + + cistpl.c 1.72 1999/09/15 15:32:19 + + 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 + <dhinds@hyper.stanford.edu>. 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 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. + +======================================================================*/ + +#define __NO_VERSION__ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/major.h> +#include <linux/errno.h> +#include <linux/timer.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/pci.h> +#include <linux/ioport.h> +#include <asm/io.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/bus_ops.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" +#include "rsrc_mgr.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 + +/*====================================================================== + + Low-level functions to read and write CIS memory. I think the + write routine is only useful for writing one-byte registers. + +======================================================================*/ + +void read_cis_mem(socket_info_t *s, int attr, u_int addr, + u_int len, void *ptr) +{ + pccard_mem_map *mem = &s->cis_mem; + u_char *sys; + u_int inc = 1; + + DEBUG(3, "cs: read_cis_mem(%d, %#x, %u)\n", attr, addr, len); + if (setup_cis_mem(s) != 0) { + memset(ptr, 0xff, len); + return; + } + mem->flags |= MAP_ACTIVE; mem->flags &= ~MAP_ATTRIB; + if (attr) { mem->flags |= MAP_ATTRIB; inc++; addr *= 2; } + sys = s->cis_virt + (addr & (s->cap.map_size-1)); + mem->card_start = addr & ~(s->cap.map_size-1); + + for (; len > 0; sys = s->cis_virt) { + s->ss_entry(s->sock, SS_SetMemMap, mem); + DEBUG(3, "cs: %#2.2x %#2.2x %#2.2x %#2.2x %#2.2x ...\n", + bus_readb(s->cap.bus, sys), + bus_readb(s->cap.bus, sys+inc), + bus_readb(s->cap.bus, sys+2*inc), + bus_readb(s->cap.bus, sys+3*inc), + bus_readb(s->cap.bus, sys+4*inc)); + for ( ; len > 0; len--, ((u_char *)ptr)++, sys += inc) { + if (sys == s->cis_virt+s->cap.map_size) break; + *(u_char *)ptr = bus_readb(s->cap.bus, sys); + } + mem->card_start += s->cap.map_size; + } +} + +void write_cis_mem(socket_info_t *s, int attr, u_int addr, + u_int len, void *ptr) +{ + pccard_mem_map *mem = &s->cis_mem; + u_char *sys; + int inc = 1; + + DEBUG(3, "cs: write_cis_mem(%d, %#x, %u)\n", attr, addr, len); + if (setup_cis_mem(s) != 0) return; + mem->flags &= ~MAP_ATTRIB; + if (attr) { mem->flags |= MAP_ATTRIB; inc++; addr *= 2; } + sys = s->cis_virt + (addr & (s->cap.map_size-1)); + mem->card_start = addr & ~(s->cap.map_size-1); + + for (; len > 0; sys = s->cis_virt) { + s->ss_entry(s->sock, SS_SetMemMap, mem); + for ( ; len > 0; len--, ((u_char *)ptr)++, sys += inc) { + if (sys == s->cis_virt+s->cap.map_size) break; + bus_writeb(s->cap.bus, *(u_char *)ptr, sys); + } + mem->card_start += s->cap.map_size; + } +} + +/*====================================================================== + + This is tricky... when we set up CIS memory, we try to validate + the memory window space allocations. + +======================================================================*/ + +/* Scratch pointer to the socket we use for validation */ +static socket_info_t *vs = NULL; + +/* Validation function for cards with a valid CIS */ +static int cis_readable(u_long base) +{ + cisinfo_t info1, info2; + int ret; + vs->cis_mem.sys_start = base; + vs->cis_mem.sys_stop = base+vs->cap.map_size-1; + vs->cis_virt = bus_ioremap(vs->cap.bus, base, vs->cap.map_size); + ret = validate_cis(vs->clients, &info1); + /* invalidate mapping and CIS cache */ + bus_iounmap(vs->cap.bus, vs->cis_virt); vs->cis_used = 0; + if ((ret != 0) || (info1.Chains == 0)) + return 0; + vs->cis_mem.sys_start = base+vs->cap.map_size; + vs->cis_mem.sys_stop = base+2*vs->cap.map_size-1; + vs->cis_virt = bus_ioremap(vs->cap.bus, base+vs->cap.map_size, + vs->cap.map_size); + ret = validate_cis(vs->clients, &info2); + bus_iounmap(vs->cap.bus, vs->cis_virt); vs->cis_used = 0; + return ((ret == 0) && (info1.Chains == info2.Chains)); +} + +/* Validation function for simple memory cards */ +static int checksum(u_long base) +{ + int i, a, b, d; + vs->cis_mem.sys_start = base; + vs->cis_mem.sys_stop = base+vs->cap.map_size-1; + vs->cis_virt = bus_ioremap(vs->cap.bus, base, vs->cap.map_size); + vs->cis_mem.card_start = 0; + vs->cis_mem.flags = MAP_ACTIVE; + vs->ss_entry(vs->sock, SS_SetMemMap, &vs->cis_mem); + /* Don't bother checking every word... */ + a = 0; b = -1; + for (i = 0; i < vs->cap.map_size; i += 44) { + d = bus_readl(vs->cap.bus, vs->cis_virt+i); + a += d; b &= d; + } + bus_iounmap(vs->cap.bus, vs->cis_virt); + return (b == -1) ? -1 : (a>>1); +} + +static int checksum_match(u_long base) +{ + int a = checksum(base), b = checksum(base+vs->cap.map_size); + return ((a == b) && (a >= 0)); +} + +int setup_cis_mem(socket_info_t *s) +{ + if (s->cis_mem.sys_start == 0) { + int low = !(s->cap.features & SS_CAP_PAGE_REGS); + vs = s; + validate_mem(cis_readable, checksum_match, low); + s->cis_mem.sys_start = 0; + vs = NULL; + if (find_mem_region(&s->cis_mem.sys_start, s->cap.map_size, + "card services", s->cap.map_size, low)) { + printk(KERN_NOTICE "cs: unable to map card memory!\n"); + return CS_OUT_OF_RESOURCE; + } + s->cis_mem.sys_stop = s->cis_mem.sys_start+s->cap.map_size-1; + s->cis_mem.flags |= MAP_ACTIVE; + s->cis_virt = bus_ioremap(s->cap.bus, s->cis_mem.sys_start, + s->cap.map_size); + } + return 0; +} + +void release_cis_mem(socket_info_t *s) +{ + if (s->cis_mem.sys_start != 0) { + s->cis_mem.flags &= ~MAP_ACTIVE; + s->ss_entry(s->sock, SS_SetMemMap, &s->cis_mem); + release_mem_region(s->cis_mem.sys_start, s->cap.map_size); + bus_iounmap(s->cap.bus, s->cis_virt); + s->cis_mem.sys_start = 0; + } +} + +/*====================================================================== + + This is a wrapper around read_cis_mem, with the same interface, + but which caches information, for cards whose CIS may not be + readable all the time. + +======================================================================*/ + +static void read_cis_cache(socket_info_t *s, int attr, u_int addr, + u_int len, void *ptr) +{ + int i; + char *caddr; + + if (s->fake_cis) { + if (s->fake_cis_len > addr+len) + memcpy(ptr, s->fake_cis+addr, len); + else + memset(ptr, 0xff, len); + return; + } + caddr = s->cis_cache; + for (i = 0; i < s->cis_used; i++) { + if ((s->cis_table[i].addr == addr) && + (s->cis_table[i].len == len) && + (s->cis_table[i].attr == attr)) break; + caddr += s->cis_table[i].len; + } + if (i < s->cis_used) { + memcpy(ptr, caddr, len); + return; + } +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) + read_cb_mem(s, 0, attr, addr, len, ptr); + else +#endif + read_cis_mem(s, attr, addr, len, ptr); + /* Copy data into the cache, if there is room */ + if ((i < MAX_CIS_TABLE) && + (caddr+len < s->cis_cache+MAX_CIS_DATA)) { + s->cis_table[i].addr = addr; + s->cis_table[i].len = len; + s->cis_table[i].attr = attr; + s->cis_used++; + memcpy(caddr, ptr, len); + } +} + +/*====================================================================== + + This verifies if the CIS of a card matches what is in the CIS + cache. + +======================================================================*/ + +int verify_cis_cache(socket_info_t *s) +{ + char buf[256], *caddr; + int i; + + caddr = s->cis_cache; + for (i = 0; i < s->cis_used; i++) { +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) + read_cb_mem(s, 0, s->cis_table[i].attr, s->cis_table[i].addr, + s->cis_table[i].len, buf); + else +#endif + read_cis_mem(s, s->cis_table[i].attr, s->cis_table[i].addr, + s->cis_table[i].len, buf); + if (memcmp(buf, caddr, s->cis_table[i].len) != 0) + break; + caddr += s->cis_table[i].len; + } + return (i < s->cis_used); +} + +/*====================================================================== + + For really bad cards, we provide a facility for uploading a + replacement CIS. + +======================================================================*/ + +int replace_cis(client_handle_t handle, cisdump_t *cis) +{ + socket_info_t *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (s->fake_cis != NULL) { + kfree(s->fake_cis); + s->fake_cis = NULL; + } + if (cis->Length > CISTPL_MAX_CIS_SIZE) + return CS_BAD_SIZE; + s->fake_cis = kmalloc(cis->Length, GFP_KERNEL); + if (s->fake_cis == NULL) + return CS_OUT_OF_RESOURCE; + s->fake_cis_len = cis->Length; + memcpy(s->fake_cis, cis->Data, cis->Length); + return CS_SUCCESS; +} + +/*====================================================================== + + The high-level CIS tuple services + +======================================================================*/ + +typedef struct tuple_flags { + u_int link_space:3; + u_int has_link:1; + u_int mfc_fn:3; + u_int space:3; +} tuple_flags; + +#define LINK_SPACE(f) (((tuple_flags *)(&(f)))->link_space) +#define HAS_LINK(f) (((tuple_flags *)(&(f)))->has_link) +#define MFC_FN(f) (((tuple_flags *)(&(f)))->mfc_fn) +#define SPACE(f) (((tuple_flags *)(&(f)))->space) + +int get_next_tuple(client_handle_t handle, tuple_t *tuple); + +int get_first_tuple(client_handle_t handle, tuple_t *tuple) +{ + socket_info_t *s; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + tuple->TupleLink = tuple->Flags = 0; +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) { + u_int ptr; + pcibios_read_config_dword(s->cap.cardbus, 0, 0x28, &ptr); + tuple->CISOffset = ptr & ~7; + SPACE(tuple->Flags) = (ptr & 7); + } else +#endif + { + /* Assume presence of a LONGLINK_C to address 0 */ + tuple->CISOffset = tuple->LinkOffset = 0; + SPACE(tuple->Flags) = HAS_LINK(tuple->Flags) = 1; + } + if (!(s->state & SOCKET_CARDBUS) && (s->functions > 1) && + !(tuple->Attributes & TUPLE_RETURN_COMMON)) { + cisdata_t req = tuple->DesiredTuple; + tuple->DesiredTuple = CISTPL_LONGLINK_MFC; + if (get_next_tuple(handle, tuple) == CS_SUCCESS) { + tuple->DesiredTuple = CISTPL_LINKTARGET; + if (get_next_tuple(handle, tuple) != CS_SUCCESS) + return CS_NO_MORE_ITEMS; + } else + tuple->CISOffset = tuple->TupleLink = 0; + tuple->DesiredTuple = req; + } + return get_next_tuple(handle, tuple); +} + +static int follow_link(socket_info_t *s, tuple_t *tuple) +{ + u_char link[5]; + u_int ofs; + + if (MFC_FN(tuple->Flags)) { + /* Get indirect link from the MFC tuple */ + read_cis_cache(s, LINK_SPACE(tuple->Flags), + tuple->LinkOffset, 5, link); + ofs = le32_to_cpu(*(u_int *)(link+1)); + SPACE(tuple->Flags) = (link[0] == CISTPL_MFC_ATTR); + /* Move to the next indirect link */ + tuple->LinkOffset += 5; + MFC_FN(tuple->Flags)--; + } else if (HAS_LINK(tuple->Flags)) { + ofs = tuple->LinkOffset; + SPACE(tuple->Flags) = LINK_SPACE(tuple->Flags); + HAS_LINK(tuple->Flags) = 0; + } else { + return -1; + } + if (!(s->state & SOCKET_CARDBUS) && SPACE(tuple->Flags)) { + /* This is ugly, but a common CIS error is to code the long + link offset incorrectly, so we check the right spot... */ + read_cis_cache(s, SPACE(tuple->Flags), ofs, 5, link); + if ((link[0] == CISTPL_LINKTARGET) && (link[1] >= 3) && + (strncmp(link+2, "CIS", 3) == 0)) + return ofs; + /* Then, we try the wrong spot... */ + ofs = ofs >> 1; + } + read_cis_cache(s, SPACE(tuple->Flags), ofs, 5, link); + if ((link[0] != CISTPL_LINKTARGET) || (link[1] < 3) || + (strncmp(link+2, "CIS", 3) != 0)) + return -1; + return ofs; +} + +int get_next_tuple(client_handle_t handle, tuple_t *tuple) +{ + socket_info_t *s; + u_char link[2], tmp; + int ofs, i, attr; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + + link[1] = tuple->TupleLink; + ofs = tuple->CISOffset + tuple->TupleLink; + attr = SPACE(tuple->Flags); + + for (i = 0; i < MAX_TUPLES; i++) { + if (link[1] == 0xff) { + link[0] = CISTPL_END; + } else { + read_cis_cache(s, attr, ofs, 2, link); + if (link[0] == CISTPL_NULL) { + ofs++; continue; + } + } + + /* End of chain? Follow long link if possible */ + if (link[0] == CISTPL_END) { + if ((ofs = follow_link(s, tuple)) < 0) + return CS_NO_MORE_ITEMS; + attr = SPACE(tuple->Flags); + read_cis_cache(s, attr, ofs, 2, link); + } + + /* Is this a link tuple? Make a note of it */ + if ((link[0] == CISTPL_LONGLINK_A) || + (link[0] == CISTPL_LONGLINK_C) || + (link[0] == CISTPL_LONGLINK_MFC) || + (link[0] == CISTPL_LINKTARGET) || + (link[0] == CISTPL_NO_LINK)) { + switch (link[0]) { + case CISTPL_LONGLINK_A: + HAS_LINK(tuple->Flags) = 1; + LINK_SPACE(tuple->Flags) = 1; + read_cis_cache(s, attr, ofs+2, 4, &tuple->LinkOffset); + break; + case CISTPL_LONGLINK_C: + HAS_LINK(tuple->Flags) = 1; + LINK_SPACE(tuple->Flags) = 0; + read_cis_cache(s, attr, ofs+2, 4, &tuple->LinkOffset); + break; + case CISTPL_LONGLINK_MFC: + tuple->LinkOffset = ofs + 3; + LINK_SPACE(tuple->Flags) = attr; + if (handle->Function == BIND_FN_ALL) { + /* Follow all the MFC links */ + read_cis_cache(s, attr, ofs+2, 1, &tmp); + MFC_FN(tuple->Flags) = tmp; + } else { + /* Follow exactly one of the links */ + MFC_FN(tuple->Flags) = 1; + tuple->LinkOffset += handle->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) { + DEBUG(1, "cs: overrun in get_next_tuple for socket %d\n", + handle->Socket); + return CS_NO_MORE_ITEMS; + } + + tuple->TupleCode = link[0]; + tuple->TupleLink = link[1]; + tuple->CISOffset = ofs + 2; + return CS_SUCCESS; +} + +/*====================================================================*/ + +#define _MIN(a, b) (((a) < (b)) ? (a) : (b)) + +int get_tuple_data(client_handle_t handle, tuple_t *tuple) +{ + socket_info_t *s; + u_int len; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + + s = SOCKET(handle); + + if (tuple->TupleLink < tuple->TupleOffset) + return CS_NO_MORE_ITEMS; + len = tuple->TupleLink - tuple->TupleOffset; + tuple->TupleDataLen = tuple->TupleLink; + if (len == 0) + return CS_SUCCESS; + read_cis_cache(s, SPACE(tuple->Flags), + tuple->CISOffset + tuple->TupleOffset, + _MIN(len, tuple->TupleDataMax), tuple->TupleData); + return CS_SUCCESS; +} + +/*====================================================================== + + Parsing routines for individual tuples + +======================================================================*/ + +static int parse_device(tuple_t *tuple, cistpl_device_t *device) +{ + int i; + u_char scale; + u_char *p, *q; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + device->ndev = 0; + for (i = 0; i < CISTPL_MAX_DEVICES; i++) { + + if (*p == 0xff) break; + device->dev[i].type = (*p >> 4); + device->dev[i].wp = (*p & 0x08) ? 1 : 0; + switch (*p & 0x07) { + case 0: device->dev[i].speed = 0; break; + case 1: device->dev[i].speed = 250; break; + case 2: device->dev[i].speed = 200; break; + case 3: device->dev[i].speed = 150; break; + case 4: device->dev[i].speed = 100; break; + case 7: + if (++p == q) return CS_BAD_TUPLE; + if (p == q) + return CS_BAD_TUPLE; + device->dev[i].speed = SPEED_CVT(*p); + while (*p & 0x80) + if (++p == q) return CS_BAD_TUPLE; + break; + default: + return CS_BAD_TUPLE; + } + + if (++p == q) return CS_BAD_TUPLE; + if (*p == 0xff) break; + scale = *p & 7; + if (scale == 7) return CS_BAD_TUPLE; + device->dev[i].size = ((*p >> 3) + 1) * (512 << (scale*2)); + device->ndev++; + if (++p == q) break; + } + + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_checksum(tuple_t *tuple, cistpl_checksum_t *csum) +{ + u_char *p; + if (tuple->TupleDataLen < 5) + return CS_BAD_TUPLE; + p = (u_char *)tuple->TupleData; + csum->addr = tuple->CISOffset+(short)le16_to_cpu(*(u_short *)p)-2; + csum->len = le16_to_cpu(*(u_short *)(p + 2)); + csum->sum = *(p+4); + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_longlink(tuple_t *tuple, cistpl_longlink_t *link) +{ + if (tuple->TupleDataLen < 4) + return CS_BAD_TUPLE; + link->addr = le32_to_cpu(*(u_int *)tuple->TupleData); + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_longlink_mfc(tuple_t *tuple, + cistpl_longlink_mfc_t *link) +{ + u_char *p; + int i; + + p = (u_char *)tuple->TupleData; + + link->nfn = *p; p++; + if (tuple->TupleDataLen <= link->nfn*5) + return CS_BAD_TUPLE; + for (i = 0; i < link->nfn; i++) { + link->fn[i].space = *p; p++; + link->fn[i].addr = le32_to_cpu(*(u_int *)p); p += 4; + } + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_strings(u_char *p, u_char *q, int max, + char *s, u_char *ofs, u_char *found) +{ + int i, j, ns; + + if (p == q) return CS_BAD_TUPLE; + ns = 0; j = 0; + for (i = 0; i < max; i++) { + if (*p == 0xff) break; + ofs[i] = j; + ns++; + for (;;) { + s[j++] = (*p == 0xff) ? '\0' : *p; + if ((*p == '\0') || (*p == 0xff)) break; + if (++p == q) return CS_BAD_TUPLE; + } + if ((*p == 0xff) || (++p == q)) break; + } + if (found) { + *found = ns; + return CS_SUCCESS; + } else { + return (ns == max) ? CS_SUCCESS : CS_BAD_TUPLE; + } +} + +/*====================================================================*/ + +static int parse_vers_1(tuple_t *tuple, cistpl_vers_1_t *vers_1) +{ + u_char *p, *q; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + vers_1->major = *p; p++; + vers_1->minor = *p; p++; + if (p >= q) return CS_BAD_TUPLE; + + return parse_strings(p, q, CISTPL_VERS_1_MAX_PROD_STRINGS, + vers_1->str, vers_1->ofs, &vers_1->ns); +} + +/*====================================================================*/ + +static int parse_altstr(tuple_t *tuple, cistpl_altstr_t *altstr) +{ + u_char *p, *q; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + return parse_strings(p, q, CISTPL_MAX_ALTSTR_STRINGS, + altstr->str, altstr->ofs, &altstr->ns); +} + +/*====================================================================*/ + +static int parse_jedec(tuple_t *tuple, cistpl_jedec_t *jedec) +{ + u_char *p, *q; + int nid; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + for (nid = 0; nid < CISTPL_MAX_DEVICES; nid++) { + if (p > q-2) break; + jedec->id[nid].mfr = p[0]; + jedec->id[nid].info = p[1]; + p += 2; + } + jedec->nid = nid; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_manfid(tuple_t *tuple, cistpl_manfid_t *m) +{ + u_short *p; + if (tuple->TupleDataLen < 4) + return CS_BAD_TUPLE; + p = (u_short *)tuple->TupleData; + m->manf = le16_to_cpu(p[0]); + m->card = le16_to_cpu(p[1]); + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_funcid(tuple_t *tuple, cistpl_funcid_t *f) +{ + u_char *p; + if (tuple->TupleDataLen < 2) + return CS_BAD_TUPLE; + p = (u_char *)tuple->TupleData; + f->func = p[0]; + f->sysinit = p[1]; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_funce(tuple_t *tuple, cistpl_funce_t *f) +{ + u_char *p; + int i; + if (tuple->TupleDataLen < 1) + return CS_BAD_TUPLE; + p = (u_char *)tuple->TupleData; + f->type = p[0]; + for (i = 1; i < tuple->TupleDataLen; i++) + f->data[i-1] = p[i]; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_config(tuple_t *tuple, cistpl_config_t *config) +{ + int rasz, rmsz, i; + u_char *p; + + p = (u_char *)tuple->TupleData; + rasz = *p & 0x03; + rmsz = (*p & 0x3c) >> 2; + if (tuple->TupleDataLen < rasz+rmsz+4) + return CS_BAD_TUPLE; + config->last_idx = *(++p); + p++; + config->base = 0; + for (i = 0; i <= rasz; i++) + config->base += p[i] << (8*i); + p += rasz+1; + for (i = 0; i < 4; i++) + config->rmask[i] = 0; + for (i = 0; i <= rmsz; i++) + config->rmask[i>>2] += p[i] << (8*(i%4)); + config->subtuples = tuple->TupleDataLen - (rasz+rmsz+4); + return CS_SUCCESS; +} + +/*====================================================================== + + The following routines are all used to parse the nightmarish + config table entries. + +======================================================================*/ + +static u_char *parse_power(u_char *p, u_char *q, + cistpl_power_t *pwr) +{ + int i; + u_int scale; + + if (p == q) return NULL; + pwr->present = *p; + pwr->flags = 0; + p++; + for (i = 0; i < 7; i++) + if (pwr->present & (1<<i)) { + if (p == q) return NULL; + pwr->param[i] = POWER_CVT(*p); + scale = POWER_SCALE(*p); + while (*p & 0x80) { + if (++p == q) return NULL; + if ((*p & 0x7f) < 100) + pwr->param[i] += (*p & 0x7f) * scale / 100; + else if (*p == 0x7d) + pwr->flags |= CISTPL_POWER_HIGHZ_OK; + else if (*p == 0x7e) + pwr->param[i] = 0; + else if (*p == 0x7f) + pwr->flags |= CISTPL_POWER_HIGHZ_REQ; + else + return NULL; + } + p++; + } + return p; +} + +/*====================================================================*/ + +static u_char *parse_timing(u_char *p, u_char *q, + cistpl_timing_t *timing) +{ + u_char scale; + + if (p == q) return NULL; + scale = *p; + if ((scale & 3) != 3) { + if (++p == q) return NULL; + timing->wait = SPEED_CVT(*p); + timing->waitscale = exponent[scale & 3]; + } else + timing->wait = 0; + scale >>= 2; + if ((scale & 7) != 7) { + if (++p == q) return NULL; + timing->ready = SPEED_CVT(*p); + timing->rdyscale = exponent[scale & 7]; + } else + timing->ready = 0; + scale >>= 3; + if (scale != 7) { + if (++p == q) return NULL; + timing->reserved = SPEED_CVT(*p); + timing->rsvscale = exponent[scale]; + } else + timing->reserved = 0; + p++; + return p; +} + +/*====================================================================*/ + +static u_char *parse_io(u_char *p, u_char *q, cistpl_io_t *io) +{ + int i, j, bsz, lsz; + + if (p == q) return NULL; + io->flags = *p; + + if (!(*p & 0x80)) { + io->nwin = 1; + io->win[0].base = 0; + io->win[0].len = (1 << (io->flags & CISTPL_IO_LINES_MASK)); + return p+1; + } + + if (++p == q) return NULL; + io->nwin = (*p & 0x0f) + 1; + bsz = (*p & 0x30) >> 4; + if (bsz == 3) bsz++; + lsz = (*p & 0xc0) >> 6; + if (lsz == 3) lsz++; + p++; + + for (i = 0; i < io->nwin; i++) { + io->win[i].base = 0; + io->win[i].len = 1; + for (j = 0; j < bsz; j++, p++) { + if (p == q) return NULL; + io->win[i].base += *p << (j*8); + } + for (j = 0; j < lsz; j++, p++) { + if (p == q) return NULL; + io->win[i].len += *p << (j*8); + } + } + return p; +} + +/*====================================================================*/ + +static u_char *parse_mem(u_char *p, u_char *q, cistpl_mem_t *mem) +{ + int i, j, asz, lsz, has_ha; + u_int len, ca, ha; + + if (p == q) return NULL; + + mem->nwin = (*p & 0x07) + 1; + lsz = (*p & 0x18) >> 3; + asz = (*p & 0x60) >> 5; + has_ha = (*p & 0x80); + if (++p == q) return NULL; + + for (i = 0; i < mem->nwin; i++) { + len = ca = ha = 0; + for (j = 0; j < lsz; j++, p++) { + if (p == q) return NULL; + len += *p << (j*8); + } + for (j = 0; j < asz; j++, p++) { + if (p == q) return NULL; + ca += *p << (j*8); + } + if (has_ha) + for (j = 0; j < asz; j++, p++) { + if (p == q) return NULL; + ha += *p << (j*8); + } + mem->win[i].len = len << 8; + mem->win[i].card_addr = ca << 8; + mem->win[i].host_addr = ha << 8; + } + return p; +} + +/*====================================================================*/ + +static u_char *parse_irq(u_char *p, u_char *q, cistpl_irq_t *irq) +{ + if (p == q) return NULL; + irq->IRQInfo1 = *p; p++; + if (irq->IRQInfo1 & IRQ_INFO2_VALID) { + if (p+2 > q) return NULL; + irq->IRQInfo2 = (p[1]<<8) + p[0]; + p += 2; + } + return p; +} + +/*====================================================================*/ + +static int parse_cftable_entry(tuple_t *tuple, + cistpl_cftable_entry_t *entry) +{ + u_char *p, *q, features; + + p = tuple->TupleData; + q = p + tuple->TupleDataLen; + entry->index = *p & 0x3f; + entry->flags = 0; + if (*p & 0x40) + entry->flags |= CISTPL_CFTABLE_DEFAULT; + if (*p & 0x80) { + if (++p == q) return CS_BAD_TUPLE; + if (*p & 0x10) + entry->flags |= CISTPL_CFTABLE_BVDS; + if (*p & 0x20) + entry->flags |= CISTPL_CFTABLE_WP; + if (*p & 0x40) + entry->flags |= CISTPL_CFTABLE_RDYBSY; + if (*p & 0x80) + entry->flags |= CISTPL_CFTABLE_MWAIT; + entry->interface = *p & 0x0f; + } else + entry->interface = 0; + + /* Process optional features */ + if (++p == q) return CS_BAD_TUPLE; + features = *p; p++; + + /* Power options */ + if ((features & 3) > 0) { + p = parse_power(p, q, &entry->vcc); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vcc.present = 0; + if ((features & 3) > 1) { + p = parse_power(p, q, &entry->vpp1); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vpp1.present = 0; + if ((features & 3) > 2) { + p = parse_power(p, q, &entry->vpp2); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vpp2.present = 0; + + /* Timing options */ + if (features & 0x04) { + p = parse_timing(p, q, &entry->timing); + if (p == NULL) return CS_BAD_TUPLE; + } else { + entry->timing.wait = 0; + entry->timing.ready = 0; + entry->timing.reserved = 0; + } + + /* I/O window options */ + if (features & 0x08) { + p = parse_io(p, q, &entry->io); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->io.nwin = 0; + + /* Interrupt options */ + if (features & 0x10) { + p = parse_irq(p, q, &entry->irq); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->irq.IRQInfo1 = 0; + + switch (features & 0x60) { + case 0x00: + entry->mem.nwin = 0; + break; + case 0x20: + entry->mem.nwin = 1; + entry->mem.win[0].len = le16_to_cpu(*(u_short *)p) << 8; + entry->mem.win[0].card_addr = 0; + entry->mem.win[0].host_addr = 0; + p += 2; + if (p > q) return CS_BAD_TUPLE; + break; + case 0x40: + entry->mem.nwin = 1; + entry->mem.win[0].len = le16_to_cpu(*(u_short *)p) << 8; + entry->mem.win[0].card_addr = + le16_to_cpu(*(u_short *)(p+2)) << 8; + entry->mem.win[0].host_addr = 0; + p += 4; + if (p > q) return CS_BAD_TUPLE; + break; + case 0x60: + p = parse_mem(p, q, &entry->mem); + if (p == NULL) return CS_BAD_TUPLE; + break; + } + + /* Misc features */ + if (features & 0x80) { + if (p == q) return CS_BAD_TUPLE; + entry->flags |= (*p << 8); + while (*p & 0x80) + if (++p == q) return CS_BAD_TUPLE; + p++; + } + + entry->subtuples = q-p; + + return CS_SUCCESS; +} + +/*====================================================================*/ + +#ifdef CONFIG_CARDBUS + +static int parse_bar(tuple_t *tuple, cistpl_bar_t *bar) +{ + u_char *p; + if (tuple->TupleDataLen < 6) + return CS_BAD_TUPLE; + p = (u_char *)tuple->TupleData; + bar->attr = *p; + p += 2; + bar->size = le32_to_cpu(*(u_int *)p); + return CS_SUCCESS; +} + +static int parse_config_cb(tuple_t *tuple, cistpl_config_t *config) +{ + u_char *p; + + p = (u_char *)tuple->TupleData; + if ((*p != 3) || (tuple->TupleDataLen < 6)) + return CS_BAD_TUPLE; + config->last_idx = *(++p); + p++; + config->base = le32_to_cpu(*(u_int *)p); + config->subtuples = tuple->TupleDataLen - 6; + return CS_SUCCESS; +} + +static int parse_cftable_entry_cb(tuple_t *tuple, + cistpl_cftable_entry_cb_t *entry) +{ + u_char *p, *q, features; + + p = tuple->TupleData; + q = p + tuple->TupleDataLen; + entry->index = *p & 0x3f; + entry->flags = 0; + if (*p & 0x40) + entry->flags |= CISTPL_CFTABLE_DEFAULT; + + /* Process optional features */ + if (++p == q) return CS_BAD_TUPLE; + features = *p; p++; + + /* Power options */ + if ((features & 3) > 0) { + p = parse_power(p, q, &entry->vcc); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vcc.present = 0; + if ((features & 3) > 1) { + p = parse_power(p, q, &entry->vpp1); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vpp1.present = 0; + if ((features & 3) > 2) { + p = parse_power(p, q, &entry->vpp2); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->vpp2.present = 0; + + /* I/O window options */ + if (features & 0x08) { + if (p == q) return CS_BAD_TUPLE; + entry->io = *p; p++; + } else + entry->io = 0; + + /* Interrupt options */ + if (features & 0x10) { + p = parse_irq(p, q, &entry->irq); + if (p == NULL) return CS_BAD_TUPLE; + } else + entry->irq.IRQInfo1 = 0; + + if (features & 0x20) { + if (p == q) return CS_BAD_TUPLE; + entry->mem = *p; p++; + } else + entry->mem = 0; + + /* Misc features */ + if (features & 0x80) { + if (p == q) return CS_BAD_TUPLE; + entry->flags |= (*p << 8); + if (*p & 0x80) { + if (++p == q) return CS_BAD_TUPLE; + entry->flags |= (*p << 16); + } + while (*p & 0x80) + if (++p == q) return CS_BAD_TUPLE; + p++; + } + + entry->subtuples = q-p; + + return CS_SUCCESS; +} + +#endif + +/*====================================================================*/ + +static int parse_device_geo(tuple_t *tuple, cistpl_device_geo_t *geo) +{ + u_char *p, *q; + int n; + + p = (u_char *)tuple->TupleData; + q = p + tuple->TupleDataLen; + + for (n = 0; n < CISTPL_MAX_DEVICES; n++) { + if (p > q-6) break; + geo->geo[n].buswidth = p[0]; + geo->geo[n].erase_block = 1 << (p[1]-1); + geo->geo[n].read_block = 1 << (p[2]-1); + geo->geo[n].write_block = 1 << (p[3]-1); + geo->geo[n].partition = 1 << (p[4]-1); + geo->geo[n].interleave = 1 << (p[5]-1); + p += 6; + } + geo->ngeo = n; + return CS_SUCCESS; +} + +/*====================================================================*/ + +static int parse_vers_2(tuple_t *tuple, cistpl_vers_2_t *v2) +{ + u_char *p, *q; + + if (tuple->TupleDataLen < 10) + return CS_BAD_TUPLE; + + p = tuple->TupleData; + q = p + tuple->TupleDataLen; + + v2->vers = p[0]; + v2->comply = p[1]; + v2->dindex = le16_to_cpu(*(u_short *)(p+2)); + v2->vspec8 = p[6]; + v2->vspec9 = p[7]; + v2->nhdr = p[8]; + p += 9; + return parse_strings(p, q, 2, v2->str, &v2->vendor, NULL); +} + +/*====================================================================*/ + +static int parse_org(tuple_t *tuple, cistpl_org_t *org) +{ + u_char *p, *q; + int i; + + p = tuple->TupleData; + q = p + tuple->TupleDataLen; + if (p == q) return CS_BAD_TUPLE; + org->data_org = *p; + if (++p == q) return CS_BAD_TUPLE; + for (i = 0; i < 30; i++) { + org->desc[i] = *p; + if (*p == '\0') break; + if (++p == q) return CS_BAD_TUPLE; + } + return CS_SUCCESS; +} + +/*====================================================================*/ + +int parse_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse) +{ + int ret = CS_SUCCESS; + + if (tuple->TupleDataLen > tuple->TupleDataMax) + return CS_BAD_TUPLE; + switch (tuple->TupleCode) { + case CISTPL_DEVICE: + case CISTPL_DEVICE_A: + ret = parse_device(tuple, &parse->device); + break; +#ifdef CONFIG_CARDBUS + case CISTPL_BAR: + ret = parse_bar(tuple, &parse->bar); + break; + case CISTPL_CONFIG_CB: + ret = parse_config_cb(tuple, &parse->config); + break; + case CISTPL_CFTABLE_ENTRY_CB: + ret = parse_cftable_entry_cb(tuple, &parse->cftable_entry_cb); + break; +#endif + case CISTPL_CHECKSUM: + ret = parse_checksum(tuple, &parse->checksum); + break; + case CISTPL_LONGLINK_A: + case CISTPL_LONGLINK_C: + ret = parse_longlink(tuple, &parse->longlink); + break; + case CISTPL_LONGLINK_MFC: + ret = parse_longlink_mfc(tuple, &parse->longlink_mfc); + break; + case CISTPL_VERS_1: + ret = parse_vers_1(tuple, &parse->version_1); + break; + case CISTPL_ALTSTR: + ret = parse_altstr(tuple, &parse->altstr); + break; + case CISTPL_JEDEC_A: + case CISTPL_JEDEC_C: + ret = parse_jedec(tuple, &parse->jedec); + break; + case CISTPL_MANFID: + ret = parse_manfid(tuple, &parse->manfid); + break; + case CISTPL_FUNCID: + ret = parse_funcid(tuple, &parse->funcid); + break; + case CISTPL_FUNCE: + ret = parse_funce(tuple, &parse->funce); + break; + case CISTPL_CONFIG: + ret = parse_config(tuple, &parse->config); + break; + case CISTPL_CFTABLE_ENTRY: + ret = parse_cftable_entry(tuple, &parse->cftable_entry); + break; + case CISTPL_DEVICE_GEO: + case CISTPL_DEVICE_GEO_A: + ret = parse_device_geo(tuple, &parse->device_geo); + break; + case CISTPL_VERS_2: + ret = parse_vers_2(tuple, &parse->vers_2); + break; + case CISTPL_ORG: + ret = parse_org(tuple, &parse->org); + break; + case CISTPL_NO_LINK: + case CISTPL_LINKTARGET: + ret = CS_SUCCESS; + break; + default: + ret = CS_UNSUPPORTED_FUNCTION; + break; + } + return ret; +} + +/*====================================================================== + + This is used internally by Card Services to look up CIS stuff. + +======================================================================*/ + +int read_tuple(client_handle_t handle, cisdata_t code, void *parse) +{ + tuple_t tuple; + cisdata_t buf[255]; + int ret; + + tuple.DesiredTuple = code; + tuple.Attributes = TUPLE_RETURN_COMMON; + ret = CardServices(GetFirstTuple, handle, &tuple, NULL); + if (ret != CS_SUCCESS) return ret; + tuple.TupleData = buf; + tuple.TupleOffset = 0; + tuple.TupleDataMax = sizeof(buf); + ret = CardServices(GetTupleData, handle, &tuple, NULL); + if (ret != CS_SUCCESS) return ret; + ret = CardServices(ParseTuple, handle, &tuple, parse); + return ret; +} + +/*====================================================================== + + This tries to determine if a card has a sensible CIS. It returns + the number of tuples in the CIS, or 0 if the CIS looks bad. The + checks include making sure several critical tuples are present and + valid; seeing if the total number of tuples is reasonable; and + looking for tuples that use reserved codes. + +======================================================================*/ + +int validate_cis(client_handle_t handle, cisinfo_t *info) +{ + tuple_t tuple; + cisparse_t p; + int ret, reserved, errors; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + + info->Chains = reserved = errors = 0; + tuple.DesiredTuple = RETURN_FIRST_TUPLE; + tuple.Attributes = TUPLE_RETURN_COMMON; + ret = get_first_tuple(handle, &tuple); + if (ret != CS_SUCCESS) + return CS_SUCCESS; + + /* First tuple should be DEVICE */ + if (tuple.TupleCode != CISTPL_DEVICE) + errors++; + /* All cards should have a MANFID tuple */ + if (read_tuple(handle, CISTPL_MANFID, &p) != CS_SUCCESS) + errors++; + /* All cards should have either a VERS_1 or a VERS_2 tuple. But + at worst, we'll accept a CFTABLE_ENTRY that parses. */ + if ((read_tuple(handle, CISTPL_VERS_1, &p) != CS_SUCCESS) && + (read_tuple(handle, CISTPL_VERS_2, &p) != CS_SUCCESS) && + (read_tuple(handle, CISTPL_CFTABLE_ENTRY, &p) != CS_SUCCESS) && + (read_tuple(handle, CISTPL_CFTABLE_ENTRY_CB, &p) != CS_SUCCESS)) + errors++; + if (errors > 1) + return CS_SUCCESS; + + for (info->Chains = 1; info->Chains < MAX_TUPLES; info->Chains++) { + ret = get_next_tuple(handle, &tuple); + if (ret != CS_SUCCESS) break; + if (((tuple.TupleCode > 0x23) && (tuple.TupleCode < 0x40)) || + ((tuple.TupleCode > 0x47) && (tuple.TupleCode < 0x80)) || + ((tuple.TupleCode > 0x90) && (tuple.TupleCode < 0xff))) + reserved++; + } + if ((info->Chains == MAX_TUPLES) || (reserved > 5)) + info->Chains = 0; + + return CS_SUCCESS; +} + diff --git a/drivers/pcmcia/cs.c b/drivers/pcmcia/cs.c new file mode 100644 index 000000000..470514c55 --- /dev/null +++ b/drivers/pcmcia/cs.c @@ -0,0 +1,2243 @@ +/*====================================================================== + + PCMCIA Card Services -- core services + + cs.c 1.228 1999/09/15 15:32:19 + + 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 + <dhinds@hyper.stanford.edu>. 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 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/kernel.h> +#include <linux/config.h> +#include <linux/string.h> +#include <linux/major.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/proc_fs.h> +#include <linux/compile.h> +#include <asm/system.h> +#include <asm/irq.h> + +#define IN_CARD_SERVICES +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/cisreg.h> +#include <pcmcia/bus_ops.h> +#include "cs_internal.h" +#include "rsrc_mgr.h" + +#ifdef CONFIG_APM +#include <linux/apm_bios.h> +static int handle_apm_event(apm_event_t event); +#endif + +#ifdef PCMCIA_DEBUG +int pc_debug = PCMCIA_DEBUG; +MODULE_PARM(pc_debug, "i"); +static const char *version = +"cs.c 1.228 1999/09/15 15:32:19 (David Hinds)"; +#endif + +static const char *release = "Linux PCMCIA Card Services " CS_RELEASE; +#ifdef MODULE +static const char *kernel = "kernel build: " UTS_RELEASE " " UTS_VERSION; +#endif +static const char *options = "options: " +#ifdef CONFIG_PCI +" [pci]" +#endif +#ifdef CONFIG_CARDBUS +" [cardbus]" +#endif +#ifdef CONFIG_APM +" [apm]" +#endif +#if !defined(CONFIG_CARDBUS) && !defined(CONFIG_PCI) && \ + !defined(CONFIG_APM) && !defined(CONFIG_PNP_BIOS) +" none" +#endif +; + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +static int setup_delay = HZ/20; /* ticks */ +static int resume_delay = HZ/5; /* ticks */ +static int shutdown_delay = HZ/40; /* ticks */ +static int vcc_settle = HZ*3/10; /* ticks */ +static int reset_time = 10; /* usecs */ +static int unreset_delay = HZ/10; /* ticks */ +static int unreset_check = HZ/10; /* ticks */ +static int unreset_limit = 30; /* unreset_check's */ + +/* Access speed for attribute memory windows */ +static int cis_speed = 300; /* ns */ + +/* Access speed for IO windows */ +static int io_speed = 0; /* ns */ + +/* Optional features */ +#ifdef CONFIG_APM +static int do_apm = 1; +MODULE_PARM(do_apm, "i"); +#endif + +MODULE_PARM(setup_delay, "i"); +MODULE_PARM(resume_delay, "i"); +MODULE_PARM(shutdown_delay, "i"); +MODULE_PARM(vcc_settle, "i"); +MODULE_PARM(reset_time, "i"); +MODULE_PARM(unreset_delay, "i"); +MODULE_PARM(unreset_check, "i"); +MODULE_PARM(unreset_limit, "i"); +MODULE_PARM(cis_speed, "i"); +MODULE_PARM(io_speed, "i"); + +/*====================================================================*/ + +static socket_state_t dead_socket = { + 0, SS_DETECT, 0, 0, 0 +}; + +/* Table of sockets */ +socket_t sockets = 0; +socket_info_t *socket_table[MAX_SOCK]; + +#ifdef CONFIG_PROC_FS +struct proc_dir_entry *proc_pccard = NULL; +#endif + +/*====================================================================*/ + +/* String tables for error messages */ + +typedef struct lookup_t { + int key; + char *msg; +} lookup_t; + +static const lookup_t error_table[] = { + { CS_SUCCESS, "Operation succeeded" }, + { CS_BAD_ADAPTER, "Bad adapter" }, + { CS_BAD_ATTRIBUTE, "Bad attribute", }, + { CS_BAD_BASE, "Bad base address" }, + { CS_BAD_EDC, "Bad EDC" }, + { CS_BAD_IRQ, "Bad IRQ" }, + { CS_BAD_OFFSET, "Bad offset" }, + { CS_BAD_PAGE, "Bad page number" }, + { CS_READ_FAILURE, "Read failure" }, + { CS_BAD_SIZE, "Bad size" }, + { CS_BAD_SOCKET, "Bad socket" }, + { CS_BAD_TYPE, "Bad type" }, + { CS_BAD_VCC, "Bad Vcc" }, + { CS_BAD_VPP, "Bad Vpp" }, + { CS_BAD_WINDOW, "Bad window" }, + { CS_WRITE_FAILURE, "Write failure" }, + { CS_NO_CARD, "No card present" }, + { CS_UNSUPPORTED_FUNCTION, "Usupported function" }, + { CS_UNSUPPORTED_MODE, "Unsupported mode" }, + { CS_BAD_SPEED, "Bad speed" }, + { CS_BUSY, "Resource busy" }, + { CS_GENERAL_FAILURE, "General failure" }, + { CS_WRITE_PROTECTED, "Write protected" }, + { CS_BAD_ARG_LENGTH, "Bad argument length" }, + { CS_BAD_ARGS, "Bad arguments" }, + { CS_CONFIGURATION_LOCKED, "Configuration locked" }, + { CS_IN_USE, "Resource in use" }, + { CS_NO_MORE_ITEMS, "No more items" }, + { CS_OUT_OF_RESOURCE, "Out of resource" }, + { CS_BAD_HANDLE, "Bad handle" }, + { CS_BAD_TUPLE, "Bad CIS tuple" } +}; +#define ERROR_COUNT (sizeof(error_table)/sizeof(lookup_t)) + +static const lookup_t service_table[] = { + { AccessConfigurationRegister, "AccessConfigurationRegister" }, + { AddSocketServices, "AddSocketServices" }, + { AdjustResourceInfo, "AdjustResourceInfo" }, + { CheckEraseQueue, "CheckEraseQueue" }, + { CloseMemory, "CloseMemory" }, + { DeregisterClient, "DeregisterClient" }, + { DeregisterEraseQueue, "DeregisterEraseQueue" }, + { GetCardServicesInfo, "GetCardServicesInfo" }, + { GetClientInfo, "GetClientInfo" }, + { GetConfigurationInfo, "GetConfigurationInfo" }, + { GetEventMask, "GetEventMask" }, + { GetFirstClient, "GetFirstClient" }, + { GetFirstRegion, "GetFirstRegion" }, + { GetFirstTuple, "GetFirstTuple" }, + { GetNextClient, "GetNextClient" }, + { GetNextRegion, "GetNextRegion" }, + { GetNextTuple, "GetNextTuple" }, + { GetStatus, "GetStatus" }, + { GetTupleData, "GetTupleData" }, + { MapMemPage, "MapMemPage" }, + { ModifyConfiguration, "ModifyConfiguration" }, + { ModifyWindow, "ModifyWindow" }, + { OpenMemory, "OpenMemory" }, + { ParseTuple, "ParseTuple" }, + { ReadMemory, "ReadMemory" }, + { RegisterClient, "RegisterClient" }, + { RegisterEraseQueue, "RegisterEraseQueue" }, + { RegisterMTD, "RegisterMTD" }, + { ReleaseConfiguration, "ReleaseConfiguration" }, + { ReleaseIO, "ReleaseIO" }, + { ReleaseIRQ, "ReleaseIRQ" }, + { ReleaseWindow, "ReleaseWindow" }, + { RequestConfiguration, "RequestConfiguration" }, + { RequestIO, "RequestIO" }, + { RequestIRQ, "RequestIRQ" }, + { RequestSocketMask, "RequestSocketMask" }, + { RequestWindow, "RequestWindow" }, + { ResetCard, "ResetCard" }, + { SetEventMask, "SetEventMask" }, + { ValidateCIS, "ValidateCIS" }, + { WriteMemory, "WriteMemory" }, + { BindDevice, "BindDevice" }, + { BindMTD, "BindMTD" }, + { ReportError, "ReportError" }, + { SuspendCard, "SuspendCard" }, + { ResumeCard, "ResumeCard" }, + { EjectCard, "EjectCard" }, + { InsertCard, "InsertCard" }, + { ReplaceCIS, "ReplaceCIS" } +}; +#define SERVICE_COUNT (sizeof(service_table)/sizeof(lookup_t)) + +/*====================================================================== + + Reset a socket to the default state + +======================================================================*/ + +static void init_socket(socket_info_t *s) +{ + int i; + pccard_io_map io = { 0, 0, 0, 0, 1 }; + pccard_mem_map mem = { 0, 0, 0, 0, 0, 0 }; + + mem.sys_stop = s->cap.map_size; + s->socket = dead_socket; + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + for (i = 0; i < 2; i++) { + io.map = i; + s->ss_entry(s->sock, SS_SetIOMap, &io); + } + for (i = 0; i < 5; i++) { + mem.map = i; + s->ss_entry(s->sock, SS_SetMemMap, &mem); + } +} + +/*====================================================================*/ + +#if defined(CONFIG_PROC_FS) && defined(PCMCIA_DEBUG) +int proc_read_clients(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + socket_info_t *s = data; + client_handle_t c; + char *p = buf; + + for (c = s->clients; c; c = c->next) + p += sprintf(p, "fn %x: '%s' [attr 0x%04x] [state 0x%04x]\n", + c->Function, c->dev_info, c->Attributes, c->state); + return (p - buf); +} +#endif + +/*====================================================================== + + Low-level PC Card interface drivers need to register with Card + Services using these calls. + +======================================================================*/ + +static void setup_socket(u_long i); +static void shutdown_socket(u_long i); +static void reset_socket(u_long i); +static void unreset_socket(u_long i); +static void parse_events(void *info, u_int events); + +int register_ss_entry(int nsock, ss_entry_t ss_entry) +{ + int i, ns; + socket_info_t *s; + + DEBUG(0, "cs: register_ss_entry(%d, 0x%p)\n", nsock, ss_entry); + + for (ns = 0; ns < nsock; ns++) { + s = kmalloc(sizeof(struct socket_info_t), GFP_KERNEL); + memset(s, 0, sizeof(socket_info_t)); + + s->ss_entry = ss_entry; + s->sock = ns; + s->setup.data = sockets; + s->setup.function = &setup_socket; + s->shutdown.data = sockets; + s->shutdown.function = &shutdown_socket; + /* base address = 0, map = 0 */ + s->cis_mem.flags = 0; + s->cis_mem.speed = cis_speed; + s->erase_busy.next = s->erase_busy.prev = &s->erase_busy; + + for (i = 0; i < sockets; i++) + if (socket_table[i] == NULL) break; + socket_table[i] = s; + if (i == sockets) sockets++; + + init_socket(s); + ss_entry(ns, SS_InquireSocket, &s->cap); +#ifdef CONFIG_PROC_FS + if (proc_pccard) { + char name[3]; +#ifdef PCMCIA_DEBUG + struct proc_dir_entry *ent; +#endif + sprintf(name, "%02d", i); + s->proc = create_proc_entry(name, S_IFDIR, proc_pccard); +#ifdef PCMCIA_DEBUG + ent = create_proc_entry("clients", 0, s->proc); + ent->read_proc = proc_read_clients; + ent->data = s; +#endif + ss_entry(ns, SS_ProcSetup, s->proc); + } +#endif + } + + return 0; +} /* register_ss_entry */ + +/*====================================================================*/ + +void unregister_ss_entry(ss_entry_t ss_entry) +{ + int i, j; + socket_info_t *s = NULL; + client_t *client; + + for (;;) { + for (i = 0; i < sockets; i++) { + s = socket_table[i]; + if (s->ss_entry == ss_entry) break; + } + if (i == sockets) { + break; + } else { +#ifdef CONFIG_PROC_FS + if (proc_pccard) { + char name[3]; + sprintf(name, "%02d", i); +#ifdef PCMCIA_DEBUG + remove_proc_entry("clients", s->proc); +#endif + remove_proc_entry(name, proc_pccard); + } +#endif + while (s->clients) { + client = s->clients; + s->clients = s->clients->next; + kfree(client); + } + init_socket(s); + release_cis_mem(s); +#ifdef CONFIG_CARDBUS + cb_release_cis_mem(s); +#endif + s->ss_entry = NULL; + kfree(s); + socket_table[i] = NULL; + for (j = i; j < sockets-1; j++) + socket_table[j] = socket_table[j+1]; + sockets--; + } + } + +} /* unregister_ss_entry */ + +/*====================================================================== + + Shutdown_Socket() and setup_socket() are scheduled using add_timer + calls by the main event handler when card insertion and removal + events are received. Shutdown_Socket() unconfigures a socket and + turns off socket power. Setup_socket() turns on socket power + and resets the socket, in two stages. + +======================================================================*/ + +static void free_regions(memory_handle_t *list) +{ + memory_handle_t tmp; + while (*list != NULL) { + tmp = *list; + *list = tmp->info.next; + tmp->region_magic = 0; + kfree(tmp); + } +} + +static int send_event(socket_info_t *s, event_t event, int priority); + +static void shutdown_socket(u_long i) +{ + socket_info_t *s = socket_table[i]; + client_t **c; + + DEBUG(1, "cs: shutdown_socket(%ld)\n", i); + + /* Blank out the socket state */ + s->state &= SOCKET_PRESENT|SOCKET_SETUP_PENDING; + init_socket(s); + s->irq.AssignedIRQ = s->irq.Config = 0; + s->functions = 0; + s->lock_count = 0; + s->cis_used = 0; + if (s->fake_cis) { + kfree(s->fake_cis); + s->fake_cis = NULL; + } +#ifdef CONFIG_CARDBUS + cb_release_cis_mem(s); + cb_free(s); +#endif + if (s->config) { + kfree(s->config); + s->config = NULL; + } + for (c = &s->clients; *c; ) { + if ((*c)->state & CLIENT_UNBOUND) { + client_t *d = *c; + *c = (*c)->next; + kfree(d); + } else { + c = &((*c)->next); + } + } + free_regions(&s->a_region); + free_regions(&s->c_region); +} /* shutdown_socket */ + +static void setup_socket(u_long i) +{ + int val; + socket_info_t *s = socket_table[i]; + + s->ss_entry(s->sock, SS_GetStatus, &val); + if (val & SS_DETECT) { + DEBUG(1, "cs: setup_socket(%ld): applying power\n", i); + s->state |= SOCKET_PRESENT; + s->socket.flags = 0; + if (val & SS_3VCARD) + s->socket.Vcc = s->socket.Vpp = 33; + else if (!(val & SS_XVCARD)) + s->socket.Vcc = s->socket.Vpp = 50; + else { + printk(KERN_NOTICE "cs: socket %ld: unsupported " + "voltage key\n", i); + s->socket.Vcc = 0; + } + if (val & SS_CARDBUS) { + s->state |= SOCKET_CARDBUS; +#ifndef CONFIG_CARDBUS + printk(KERN_NOTICE "cs: unsupported card type detected!\n"); +#endif + } + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + s->setup.function = &reset_socket; + s->setup.expires = jiffies + vcc_settle; + add_timer(&s->setup); + } else + DEBUG(0, "cs: setup_socket(%ld): no card!\n", i); +} /* setup_socket */ + +/*====================================================================== + + Reset_socket() and unreset_socket() handle hard resets. Resets + have several causes: card insertion, a call to reset_socket, or + recovery from a suspend/resume cycle. Unreset_socket() sends + a CS event that matches the cause of the reset. + +======================================================================*/ + +static void reset_socket(u_long i) +{ + socket_info_t *s = socket_table[i]; + + DEBUG(1, "cs: resetting socket %ld\n", i); + s->socket.flags |= SS_OUTPUT_ENA | SS_RESET; + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + udelay((long)reset_time); + s->socket.flags &= ~SS_RESET; + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + s->unreset_timeout = 0; + s->setup.expires = jiffies + unreset_delay; + s->setup.function = &unreset_socket; + add_timer(&s->setup); +} /* reset_socket */ + +#define EVENT_MASK \ +(SOCKET_SETUP_PENDING|SOCKET_SUSPEND|SOCKET_RESET_PENDING) + +static void unreset_socket(u_long i) +{ + socket_info_t *s = socket_table[i]; + int val; + + s->ss_entry(s->sock, SS_GetStatus, &val); + if (val & SS_READY) { + DEBUG(1, "cs: reset done on socket %ld\n", i); + if (s->state & SOCKET_SUSPEND) { + s->state &= ~EVENT_MASK; + if (verify_cis_cache(s) != 0) + parse_events(s, SS_DETECT); + else + send_event(s, CS_EVENT_PM_RESUME, CS_EVENT_PRI_LOW); + } else if (s->state & SOCKET_SETUP_PENDING) { +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) + cb_alloc(s); +#endif + send_event(s, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW); + s->state &= ~SOCKET_SETUP_PENDING; + } else { + send_event(s, CS_EVENT_CARD_RESET, CS_EVENT_PRI_LOW); + s->reset_handle->event_callback_args.info = NULL; + EVENT(s->reset_handle, CS_EVENT_RESET_COMPLETE, + CS_EVENT_PRI_LOW); + s->state &= ~EVENT_MASK; + } + } else { + DEBUG(2, "cs: socket %ld not ready yet\n", i); + if (s->unreset_timeout > unreset_limit) { + printk(KERN_NOTICE "cs: socket %ld timed out during" + " reset\n", i); + s->state &= ~EVENT_MASK; + } else { + s->unreset_timeout++; + s->setup.expires = jiffies + unreset_check; + add_timer(&s->setup); + } + } +} /* unreset_socket */ + +/*====================================================================== + + The central event handler. Send_event() sends an event to all + valid clients. Parse_events() interprets the event bits from + a card status change report. Do_shotdown() handles the high + priority stuff associated with a card removal. + +======================================================================*/ + +static int send_event(socket_info_t *s, event_t event, int priority) +{ + client_t *client = s->clients; + int ret; + DEBUG(1, "cs: send_event(sock %d, event %d, pri %d)\n", + s->sock, event, priority); + ret = 0; + for (; client; client = client->next) { + if (client->state & (CLIENT_UNBOUND|CLIENT_STALE)) + continue; + if (client->EventMask & event) { + ret = EVENT(client, event, priority); + if (ret != 0) + return ret; + } + } + return ret; +} /* send_event */ + +static void do_shutdown(socket_info_t *s) +{ + client_t *client; + if (s->state & SOCKET_SHUTDOWN_PENDING) + return; + s->state |= SOCKET_SHUTDOWN_PENDING; + send_event(s, CS_EVENT_CARD_REMOVAL, CS_EVENT_PRI_HIGH); + for (client = s->clients; client; client = client->next) + if (!(client->Attributes & INFO_MASTER_CLIENT)) + client->state |= CLIENT_STALE; + if (s->state & (SOCKET_SETUP_PENDING|SOCKET_RESET_PENDING)) { + DEBUG(0, "cs: flushing pending setup\n"); + del_timer(&s->setup); + s->state &= ~EVENT_MASK; + } + s->shutdown.expires = jiffies + shutdown_delay; + add_timer(&s->shutdown); + s->state &= ~SOCKET_PRESENT; +} + +static void parse_events(void *info, u_int events) +{ + socket_info_t *s = info; + if (events & SS_DETECT) { + int status; + u_long flags; + spin_lock_irqsave(&s->lock, flags); + s->ss_entry(s->sock, SS_GetStatus, &status); + if ((s->state & SOCKET_PRESENT) && + (!(s->state & SOCKET_SUSPEND) || + !(status & SS_DETECT))) + do_shutdown(s); + if (status & SS_DETECT) { + if (s->state & SOCKET_SETUP_PENDING) { + del_timer(&s->setup); + DEBUG(1, "cs: delaying pending setup\n"); + } + s->state |= SOCKET_SETUP_PENDING; + s->setup.function = &setup_socket; + if (s->state & SOCKET_SUSPEND) + s->setup.expires = jiffies + resume_delay; + else + s->setup.expires = jiffies + setup_delay; + add_timer(&s->setup); + } + spin_unlock_irqrestore(&s->lock, flags); + } + if (events & SS_BATDEAD) + send_event(s, CS_EVENT_BATTERY_DEAD, CS_EVENT_PRI_LOW); + if (events & SS_BATWARN) + send_event(s, CS_EVENT_BATTERY_LOW, CS_EVENT_PRI_LOW); + if (events & SS_READY) { + if (!(s->state & SOCKET_RESET_PENDING)) + send_event(s, CS_EVENT_READY_CHANGE, CS_EVENT_PRI_LOW); + else DEBUG(1, "cs: ready change during reset\n"); + } +} /* parse_events */ + +/*====================================================================== + + Another event handler, for power management events. + + This does not comply with the latest PC Card spec for handling + power management events. + +======================================================================*/ + +#ifdef CONFIG_APM +static int handle_apm_event(apm_event_t event) +{ + int i, stat; + socket_info_t *s; + static int down = 0; + + switch (event) { + case APM_SYS_SUSPEND: + case APM_USER_SUSPEND: + DEBUG(1, "cs: received suspend notification\n"); + if (down) { + printk(KERN_DEBUG "cs: received extra suspend event\n"); + break; + } + down = 1; + for (i = 0; i < sockets; i++) { + s = socket_table[i]; + if ((s->state & SOCKET_PRESENT) && + !(s->state & SOCKET_SUSPEND)){ + send_event(s, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW); + s->ss_entry(s->sock, SS_SetSocket, &dead_socket); + s->state |= SOCKET_SUSPEND; + } + } + break; + case APM_NORMAL_RESUME: + case APM_CRITICAL_RESUME: + DEBUG(1, "cs: received resume notification\n"); + if (!down) { + printk(KERN_DEBUG "cs: received bogus resume event\n"); + break; + } + down = 0; + for (i = 0; i < sockets; i++) { + s = socket_table[i]; + /* Do this just to reinitialize the socket */ + init_socket(s); + s->ss_entry(s->sock, SS_GetStatus, &stat); + /* If there was or is a card here, we need to do something + about it... but parse_events will sort it all out. */ + if ((s->state & SOCKET_PRESENT) || (stat & SS_DETECT)) + parse_events(s, SS_DETECT); + } + break; + } + return 0; +} /* handle_apm_event */ +#endif + +/*====================================================================== + + Special stuff for managing IO windows, because they are scarce. + +======================================================================*/ + +static int alloc_io_space(socket_info_t *s, u_int attr, ioaddr_t *base, + ioaddr_t num, char *name) +{ + int i; + ioaddr_t try; + + for (i = 0; i < MAX_IO_WIN; i++) { + if (s->io[i].NumPorts == 0) { + if (find_io_region(base, num, name) == 0) { + s->io[i].Attributes = attr; + s->io[i].BasePort = *base; + s->io[i].NumPorts = s->io[i].InUse = num; + break; + } else + return 1; + } else if (s->io[i].Attributes != attr) + continue; + /* Try to extend top of window */ + try = s->io[i].BasePort + s->io[i].NumPorts; + if ((*base == 0) || (*base == try)) + if (find_io_region(&try, num, name) == 0) { + *base = try; + s->io[i].NumPorts += num; + s->io[i].InUse += num; + break; + } + /* Try to extend bottom of window */ + try = s->io[i].BasePort - num; + if ((*base == 0) || (*base == try)) + if (find_io_region(&try, num, name) == 0) { + s->io[i].BasePort = *base = try; + s->io[i].NumPorts += num; + s->io[i].InUse += num; + break; + } + } + return (i == MAX_IO_WIN); +} /* alloc_io_space */ + +static void release_io_space(socket_info_t *s, ioaddr_t base, + ioaddr_t num) +{ + int i; + release_region(base, num); + for (i = 0; i < MAX_IO_WIN; i++) { + if ((s->io[i].BasePort <= base) && + (s->io[i].BasePort+s->io[i].NumPorts >= base+num)) { + s->io[i].InUse -= num; + /* Free the window if no one else is using it */ + if (s->io[i].InUse == 0) + s->io[i].NumPorts = 0; + } + } +} + +/*====================================================================== + + Access_configuration_register() reads and writes configuration + registers in attribute memory. Memory window 0 is reserved for + this and the tuple reading services. + +======================================================================*/ + +static int access_configuration_register(client_handle_t handle, + conf_reg_t *reg) +{ + socket_info_t *s; + config_t *c; + int addr; + u_char val; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (handle->Function == BIND_FN_ALL) { + if (reg->Function >= s->functions) + return CS_BAD_ARGS; + c = &s->config[reg->Function]; + } else + c = CONFIG(handle); + if (!(c->state & CONFIG_LOCKED)) + return CS_CONFIGURATION_LOCKED; + + addr = (c->ConfigBase + reg->Offset) >> 1; + + switch (reg->Action) { + case CS_READ: + read_cis_mem(s, 1, addr, 1, &val); + reg->Value = val; + break; + case CS_WRITE: + val = reg->Value; + write_cis_mem(s, 1, addr, 1, &val); + break; + default: + return CS_BAD_ARGS; + break; + } + return CS_SUCCESS; +} /* access_configuration_register */ + +/*====================================================================== + + Bind_device() associates a device driver with a particular socket. + It is normally called by Driver Services after it has identified + a newly inserted card. An instance of that driver will then be + eligible to register as a client of this socket. + +======================================================================*/ + +static int bind_device(bind_req_t *req) +{ + client_t *client; + socket_info_t *s; + + if (CHECK_SOCKET(req->Socket)) + return CS_BAD_SOCKET; + s = SOCKET(req); + + client = (client_t *)kmalloc(sizeof(client_t), GFP_KERNEL); + memset(client, '\0', sizeof(client_t)); + client->client_magic = CLIENT_MAGIC; + strncpy(client->dev_info, (char *)req->dev_info, DEV_NAME_LEN); + client->Socket = req->Socket; + client->Function = req->Function; + client->state = CLIENT_UNBOUND; + client->erase_busy.next = &client->erase_busy; + client->erase_busy.prev = &client->erase_busy; + init_waitqueue_head(&client->mtd_req); + client->next = s->clients; + s->clients = client; + DEBUG(1, "cs: bind_device(): client 0x%p, sock %d, dev %s\n", + client, client->Socket, client->dev_info); + return CS_SUCCESS; +} /* bind_device */ + +/*====================================================================== + + Bind_mtd() associates a device driver with a particular memory + region. It is normally called by Driver Services after it has + identified a memory device type. An instance of the corresponding + driver will then be able to register to control this region. + +======================================================================*/ + +static int bind_mtd(mtd_bind_t *req) +{ + socket_info_t *s; + memory_handle_t region; + + if (CHECK_SOCKET(req->Socket)) + return CS_BAD_SOCKET; + s = SOCKET(req); + + if (req->Attributes & REGION_TYPE_AM) + region = s->a_region; + else + region = s->c_region; + + while (region) { + if (region->info.CardOffset == req->CardOffset) break; + region = region->info.next; + } + if (!region || (region->mtd != NULL)) + return CS_BAD_OFFSET; + strncpy(region->dev_info, (char *)req->dev_info, DEV_NAME_LEN); + + DEBUG(1, "cs: bind_mtd(): attr 0x%x, offset 0x%x, dev %s\n", + req->Attributes, req->CardOffset, (char *)req->dev_info); + return CS_SUCCESS; +} /* bind_mtd */ + +/*====================================================================*/ + +static int deregister_client(client_handle_t handle) +{ + client_t **client; + socket_info_t *s; + memory_handle_t region; + u_long flags; + int i, sn; + + DEBUG(1, "cs: deregister_client(%p)\n", handle); + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + if (handle->state & + (CLIENT_IRQ_REQ|CLIENT_IO_REQ|CLIENT_CONFIG_LOCKED)) + return CS_IN_USE; + for (i = 0; i < MAX_WIN; i++) + if (handle->state & CLIENT_WIN_REQ(i)) + return CS_IN_USE; + + /* Disconnect all MTD links */ + s = SOCKET(handle); + if (handle->mtd_count) { + for (region = s->a_region; region; region = region->info.next) + if (region->mtd == handle) region->mtd = NULL; + for (region = s->c_region; region; region = region->info.next) + if (region->mtd == handle) region->mtd = NULL; + } + + sn = handle->Socket; s = socket_table[sn]; + + if ((handle->state & CLIENT_STALE) || + (handle->Attributes & INFO_MASTER_CLIENT)) { + spin_lock_irqsave(&s->lock, flags); + client = &s->clients; + while ((*client) && ((*client) != handle)) + client = &(*client)->next; + if (*client == NULL) + return CS_BAD_HANDLE; + *client = handle->next; + handle->client_magic = 0; + kfree(handle); + spin_unlock_irqrestore(&s->lock, flags); + } else { + handle->state = CLIENT_UNBOUND; + handle->mtd_count = 0; + handle->event_handler = NULL; + } + + if (--s->real_clients == 0) + s->ss_entry(sn, SS_RegisterCallback, NULL); + + return CS_SUCCESS; +} /* deregister_client */ + +/*====================================================================*/ + +static int get_configuration_info(client_handle_t handle, + config_info_t *config) +{ + socket_info_t *s; + config_t *c; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + + if (handle->Function == BIND_FN_ALL) { + if (config->Function && (config->Function >= s->functions)) + return CS_BAD_ARGS; + } else + config->Function = handle->Function; + +#ifdef CONFIG_CARDBUS + if (s->state & SOCKET_CARDBUS) { + u_char fn = config->Function; + memset(config, 0, sizeof(config_info_t)); + config->Function = fn; + config->Vcc = s->socket.Vcc; + config->Vpp1 = config->Vpp2 = s->socket.Vpp; + config->Option = s->cap.cardbus; + if (s->cb_config) { + config->Attributes = CONF_VALID_CLIENT; + config->IntType = INT_CARDBUS; + config->AssignedIRQ = s->irq.AssignedIRQ; + if (config->AssignedIRQ) + config->Attributes |= CONF_ENABLE_IRQ; + config->BasePort1 = s->io[0].BasePort; + config->NumPorts1 = s->io[0].NumPorts; + } + return CS_SUCCESS; + } +#endif + + c = (s->config != NULL) ? &s->config[config->Function] : NULL; + + if ((c == NULL) || !(c->state & CONFIG_LOCKED)) { + config->Attributes = 0; + config->Vcc = s->socket.Vcc; + config->Vpp1 = config->Vpp2 = s->socket.Vpp; + return CS_SUCCESS; + } + + /* !!! This is a hack !!! */ + memcpy(&config->Attributes, &c->Attributes, sizeof(config_t)); + config->Attributes |= CONF_VALID_CLIENT; + config->CardValues = c->CardValues; + config->IRQAttributes = c->irq.Attributes; + config->AssignedIRQ = s->irq.AssignedIRQ; + config->BasePort1 = c->io.BasePort1; + config->NumPorts1 = c->io.NumPorts1; + config->Attributes1 = c->io.Attributes1; + config->BasePort2 = c->io.BasePort2; + config->NumPorts2 = c->io.NumPorts2; + config->Attributes2 = c->io.Attributes2; + config->IOAddrLines = c->io.IOAddrLines; + + return CS_SUCCESS; +} /* get_configuration_info */ + +/*====================================================================== + + Return information about this version of Card Services. + +======================================================================*/ + +static int get_card_services_info(servinfo_t *info) +{ + info->Signature[0] = 'C'; + info->Signature[1] = 'S'; + info->Count = sockets; + info->Revision = CS_RELEASE_CODE; + info->CSLevel = 0x0210; + info->VendorString = (char *)release; + return CS_SUCCESS; +} /* get_card_services_info */ + +/*====================================================================== + + Note that get_first_client() *does* recognize the Socket field + in the request structure. + +======================================================================*/ + +static int get_first_client(client_handle_t *handle, client_req_t *req) +{ + socket_t s; + if (req->Attributes & CLIENT_THIS_SOCKET) + s = req->Socket; + else + s = 0; + if (CHECK_SOCKET(req->Socket)) + return CS_BAD_SOCKET; + if (socket_table[s]->clients == NULL) + return CS_NO_MORE_ITEMS; + *handle = socket_table[s]->clients; + return CS_SUCCESS; +} /* get_first_client */ + +/*====================================================================*/ + +static int get_next_client(client_handle_t *handle, client_req_t *req) +{ + socket_info_t *s; + if ((handle == NULL) || CHECK_HANDLE(*handle)) + return CS_BAD_HANDLE; + if ((*handle)->next == NULL) { + if (req->Attributes & CLIENT_THIS_SOCKET) + return CS_NO_MORE_ITEMS; + s = SOCKET(*handle); + if (s->clients == NULL) + return CS_NO_MORE_ITEMS; + *handle = s->clients; + } else + *handle = (*handle)->next; + return CS_SUCCESS; +} /* get_next_client */ + +/*====================================================================*/ + +static int get_window(window_handle_t *handle, int idx, win_req_t *req) +{ + socket_info_t *s; + window_t *win; + int w; + + if (idx == 0) + s = SOCKET((client_handle_t)*handle); + else + s = (*handle)->sock; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + for (w = idx; w < MAX_WIN; w++) + if (s->state & SOCKET_WIN_REQ(w)) break; + if (w == MAX_WIN) + return CS_NO_MORE_ITEMS; + win = &s->win[w]; + req->Base = win->ctl.sys_start; + req->Size = win->ctl.sys_stop - win->ctl.sys_start + 1; + req->AccessSpeed = win->ctl.speed; + req->Attributes = 0; + if (win->ctl.flags & MAP_ATTRIB) + req->Attributes |= WIN_MEMORY_TYPE_AM; + if (win->ctl.flags & MAP_ACTIVE) + req->Attributes |= WIN_ENABLE; + if (win->ctl.flags & MAP_16BIT) + req->Attributes |= WIN_DATA_WIDTH; + if (win->ctl.flags & MAP_USE_WAIT) + req->Attributes |= WIN_USE_WAIT; + *handle = win; + return CS_SUCCESS; +} /* get_window */ + +static int get_first_window(client_handle_t *handle, win_req_t *req) +{ + if ((handle == NULL) || CHECK_HANDLE(*handle)) + return CS_BAD_HANDLE; + return get_window((window_handle_t *)handle, 0, req); +} + +static int get_next_window(window_handle_t *win, win_req_t *req) +{ + if ((win == NULL) || ((*win)->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + return get_window(win, (*win)->index+1, req); +} + +/*====================================================================== + + Get the current socket state bits. We don't support the latched + SocketState yet: I haven't seen any point for it. + +======================================================================*/ + +static int get_status(client_handle_t handle, cs_status_t *status) +{ + socket_info_t *s; + config_t *c; + int val; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + s->ss_entry(s->sock, SS_GetStatus, &val); + status->CardState = status->SocketState = 0; + status->CardState |= (val & SS_DETECT) ? CS_EVENT_CARD_DETECT : 0; + status->CardState |= (val & SS_CARDBUS) ? CS_EVENT_CB_DETECT : 0; + status->CardState |= (val & SS_3VCARD) ? CS_EVENT_3VCARD : 0; + status->CardState |= (val & SS_XVCARD) ? CS_EVENT_XVCARD : 0; + if (s->state & SOCKET_SUSPEND) + status->CardState |= CS_EVENT_PM_SUSPEND; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (s->state & SOCKET_SETUP_PENDING) + status->CardState |= CS_EVENT_CARD_INSERTION; + + /* Get info from the PRR, if necessary */ + if (handle->Function == BIND_FN_ALL) { + if (status->Function && (status->Function >= s->functions)) + return CS_BAD_ARGS; + c = (s->config != NULL) ? &s->config[status->Function] : NULL; + } else + c = CONFIG(handle); + if ((c != NULL) && (c->state & CONFIG_LOCKED) && + (c->IntType & INT_MEMORY_AND_IO)) { + u_char reg; + if (c->Present & PRESENT_PIN_REPLACE) { + read_cis_mem(s, 1, (c->ConfigBase+CISREG_PRR)>>1, 1, ®); + status->CardState |= + (reg & PRR_WP_STATUS) ? CS_EVENT_WRITE_PROTECT : 0; + status->CardState |= + (reg & PRR_READY_STATUS) ? CS_EVENT_READY_CHANGE : 0; + status->CardState |= + (reg & PRR_BVD2_STATUS) ? CS_EVENT_BATTERY_LOW : 0; + status->CardState |= + (reg & PRR_BVD1_STATUS) ? CS_EVENT_BATTERY_DEAD : 0; + } else { + /* No PRR? Then assume we're always ready */ + status->CardState |= CS_EVENT_READY_CHANGE; + } + if (c->Present & PRESENT_EXT_STATUS) { + read_cis_mem(s, 1, (c->ConfigBase+CISREG_ESR)>>1, 1, ®); + status->CardState |= + (reg & ESR_REQ_ATTN) ? CS_EVENT_REQUEST_ATTENTION : 0; + } + return CS_SUCCESS; + } + status->CardState |= + (val & SS_WRPROT) ? CS_EVENT_WRITE_PROTECT : 0; + status->CardState |= + (val & SS_BATDEAD) ? CS_EVENT_BATTERY_DEAD : 0; + status->CardState |= + (val & SS_BATWARN) ? CS_EVENT_BATTERY_LOW : 0; + status->CardState |= + (val & SS_READY) ? CS_EVENT_READY_CHANGE : 0; + return CS_SUCCESS; +} /* get_status */ + +/*====================================================================== + + Change the card address of an already open memory window. + +======================================================================*/ + +static int get_mem_page(window_handle_t win, memreq_t *req) +{ + if ((win == NULL) || (win->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + req->Page = 0; + req->CardOffset = win->ctl.card_start; + return CS_SUCCESS; +} /* get_mem_page */ + +static int map_mem_page(window_handle_t win, memreq_t *req) +{ + socket_info_t *s; + if ((win == NULL) || (win->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + if (req->Page != 0) + return CS_BAD_PAGE; + s = win->sock; + win->ctl.card_start = req->CardOffset; + if (s->ss_entry(s->sock, SS_SetMemMap, &win->ctl) != 0) + return CS_BAD_OFFSET; + return CS_SUCCESS; +} /* map_mem_page */ + +/*====================================================================== + + Modify a locked socket configuration + +======================================================================*/ + +static int modify_configuration(client_handle_t handle, + modconf_t *mod) +{ + socket_info_t *s; + config_t *c; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); c = CONFIG(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (!(c->state & CONFIG_LOCKED)) + return CS_CONFIGURATION_LOCKED; + + if (mod->Attributes & CONF_IRQ_CHANGE_VALID) { + if (mod->Attributes & CONF_ENABLE_IRQ) { + c->Attributes |= CONF_ENABLE_IRQ; + s->socket.io_irq = s->irq.AssignedIRQ; + } else { + c->Attributes &= ~CONF_ENABLE_IRQ; + s->socket.io_irq = 0; + } + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + } + + if (mod->Attributes & CONF_VCC_CHANGE_VALID) + return CS_BAD_VCC; + + /* We only allow changing Vpp1 and Vpp2 to the same value */ + if ((mod->Attributes & CONF_VPP1_CHANGE_VALID) && + (mod->Attributes & CONF_VPP2_CHANGE_VALID)) { + if (mod->Vpp1 != mod->Vpp2) + return CS_BAD_VPP; + c->Vpp1 = c->Vpp2 = s->socket.Vpp = mod->Vpp1; + if (s->ss_entry(s->sock, SS_SetSocket, &s->socket)) + return CS_BAD_VPP; + } else if ((mod->Attributes & CONF_VPP1_CHANGE_VALID) || + (mod->Attributes & CONF_VPP2_CHANGE_VALID)) + return CS_BAD_VPP; + + return CS_SUCCESS; +} /* modify_configuration */ + +/*====================================================================== + + Modify the attributes of a window returned by RequestWindow. + +======================================================================*/ + +static int modify_window(window_handle_t win, modwin_t *req) +{ + if ((win == NULL) || (win->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + + win->ctl.flags &= ~(MAP_ATTRIB|MAP_ACTIVE); + if (req->Attributes & WIN_MEMORY_TYPE) + win->ctl.flags |= MAP_ATTRIB; + if (req->Attributes & WIN_ENABLE) + win->ctl.flags |= MAP_ACTIVE; + if (req->Attributes & WIN_DATA_WIDTH) + win->ctl.flags |= MAP_16BIT; + if (req->Attributes & WIN_USE_WAIT) + win->ctl.flags |= MAP_USE_WAIT; + win->ctl.speed = req->AccessSpeed; + win->sock->ss_entry(win->sock->sock, SS_SetMemMap, &win->ctl); + + return CS_SUCCESS; +} /* modify_window */ + +/*====================================================================== + + Register_client() uses the dev_info_t handle to match the + caller with a socket. The driver must have already been bound + to a socket with bind_device() -- in fact, bind_device() + allocates the client structure that will be used. + +======================================================================*/ + +static int register_client(client_handle_t *handle, client_reg_t *req) +{ + client_t *client; + socket_info_t *s; + socket_t ns; + + /* Look for unbound client with matching dev_info */ + client = NULL; + for (ns = 0; ns < sockets; ns++) { + client = socket_table[ns]->clients; + while (client != NULL) { + if ((strcmp(client->dev_info, (char *)req->dev_info) == 0) + && (client->state & CLIENT_UNBOUND)) break; + client = client->next; + } + if (client != NULL) break; + } + if (client == NULL) + return CS_OUT_OF_RESOURCE; + + s = socket_table[ns]; + if (++s->real_clients == 1) { + ss_callback_t call; + int status; + call.handler = &parse_events; + call.info = s; + s->ss_entry(ns, SS_RegisterCallback, &call); + s->ss_entry(ns, SS_GetStatus, &status); + if ((status & SS_DETECT) && + !(s->state & SOCKET_SETUP_PENDING)) { + s->state |= SOCKET_SETUP_PENDING; + setup_socket(ns); + } + } + + *handle = client; + client->state &= ~CLIENT_UNBOUND; + client->Socket = ns; + client->Attributes = req->Attributes; + client->EventMask = req->EventMask; + client->event_handler = req->event_handler; + client->event_callback_args = req->event_callback_args; + client->event_callback_args.client_handle = client; + client->event_callback_args.bus = s->cap.bus; + + if (s->state & SOCKET_CARDBUS) + client->state |= CLIENT_CARDBUS; + + if ((!(s->state & SOCKET_CARDBUS)) && (s->functions == 0) && + (client->Function != BIND_FN_ALL)) { + cistpl_longlink_mfc_t mfc; + if (read_tuple(client, CISTPL_LONGLINK_MFC, &mfc) + == CS_SUCCESS) + s->functions = mfc.nfn; + else + s->functions = 1; + s->config = kmalloc(sizeof(config_t) * s->functions, + GFP_KERNEL); + memset(s->config, 0, sizeof(config_t) * s->functions); + } + + DEBUG(1, "cs: register_client(): client 0x%p, sock %d, dev %s\n", + client, client->Socket, client->dev_info); + if (client->EventMask & CS_EVENT_REGISTRATION_COMPLETE) + EVENT(client, CS_EVENT_REGISTRATION_COMPLETE, CS_EVENT_PRI_LOW); + if ((socket_table[ns]->state & SOCKET_PRESENT) && + !(socket_table[ns]->state & SOCKET_SETUP_PENDING)) { + if (client->EventMask & CS_EVENT_CARD_INSERTION) + EVENT(client, CS_EVENT_CARD_INSERTION, CS_EVENT_PRI_LOW); + else + client->PendingEvents |= CS_EVENT_CARD_INSERTION; + } + return CS_SUCCESS; +} /* register_client */ + +/*====================================================================*/ + +static int release_configuration(client_handle_t handle, + socket_t *Socket) +{ + pccard_io_map io; + socket_info_t *s; + int i; + + if (CHECK_HANDLE(handle) || + !(handle->state & CLIENT_CONFIG_LOCKED)) + return CS_BAD_HANDLE; + handle->state &= ~CLIENT_CONFIG_LOCKED; + s = SOCKET(handle); + +#ifdef CONFIG_CARDBUS + if (handle->state & CLIENT_CARDBUS) { + cb_disable(s); + s->lock_count = 0; + return CS_SUCCESS; + } +#endif + + if (!(handle->state & CLIENT_STALE)) { + config_t *c = CONFIG(handle); + if (--(s->lock_count) == 0) { + s->socket.flags = SS_OUTPUT_ENA; + s->socket.Vpp = 0; + s->socket.io_irq = 0; + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + } + if (c->state & CONFIG_IO_REQ) + for (i = 0; i < MAX_IO_WIN; i++) { + if (s->io[i].NumPorts == 0) + continue; + s->io[i].Config--; + if (s->io[i].Config != 0) + continue; + io.map = i; + s->ss_entry(s->sock, SS_GetIOMap, &io); + io.flags &= ~MAP_ACTIVE; + s->ss_entry(s->sock, SS_SetIOMap, &io); + } + c->state &= ~CONFIG_LOCKED; + } + + return CS_SUCCESS; +} /* release_configuration */ + +/*====================================================================== + + Release_io() releases the I/O ranges allocated by a client. This + may be invoked some time after a card ejection has already dumped + the actual socket configuration, so if the client is "stale", we + don't bother checking the port ranges against the current socket + values. + +======================================================================*/ + +static int release_io(client_handle_t handle, io_req_t *req) +{ + socket_info_t *s; + + if (CHECK_HANDLE(handle) || !(handle->state & CLIENT_IO_REQ)) + return CS_BAD_HANDLE; + handle->state &= ~CLIENT_IO_REQ; + s = SOCKET(handle); + +#ifdef CONFIG_CARDBUS + if (handle->state & CLIENT_CARDBUS) { + cb_release(s); + return CS_SUCCESS; + } +#endif + + if (!(handle->state & CLIENT_STALE)) { + config_t *c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + if ((c->io.BasePort1 != req->BasePort1) || + (c->io.NumPorts1 != req->NumPorts1) || + (c->io.BasePort2 != req->BasePort2) || + (c->io.NumPorts2 != req->NumPorts2)) + return CS_BAD_ARGS; + c->state &= ~CONFIG_IO_REQ; + } + + release_io_space(s, req->BasePort1, req->NumPorts1); + if (req->NumPorts2) + release_io_space(s, req->BasePort2, req->NumPorts2); + + return CS_SUCCESS; +} /* release_io */ + +/*====================================================================*/ + +static int cs_release_irq(client_handle_t handle, irq_req_t *req) +{ + socket_info_t *s; + if (CHECK_HANDLE(handle) || !(handle->state & CLIENT_IRQ_REQ)) + return CS_BAD_HANDLE; + handle->state &= ~CLIENT_IRQ_REQ; + s = SOCKET(handle); + + if (!(handle->state & CLIENT_STALE)) { + config_t *c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + if (c->irq.Attributes != req->Attributes) + return CS_BAD_ATTRIBUTE; + if (s->irq.AssignedIRQ != req->AssignedIRQ) + return CS_BAD_IRQ; + if (--s->irq.Config == 0) { + c->state &= ~CONFIG_IRQ_REQ; + s->irq.AssignedIRQ = 0; + } + } + + if (req->Attributes & IRQ_HANDLE_PRESENT) { + bus_free_irq(s->cap.bus, req->AssignedIRQ, req->Instance); + } + +#ifdef CONFIG_ISA + if (req->AssignedIRQ != s->cap.pci_irq) + undo_irq(req->Attributes, req->AssignedIRQ); +#endif + + return CS_SUCCESS; +} /* cs_release_irq */ + +/*====================================================================*/ + +static int release_window(window_handle_t win) +{ + socket_info_t *s; + + if ((win == NULL) || (win->magic != WINDOW_MAGIC)) + return CS_BAD_HANDLE; + s = win->sock; + if (!(win->handle->state & CLIENT_WIN_REQ(win->index))) + return CS_BAD_HANDLE; + + /* Shut down memory window */ + win->ctl.flags &= ~MAP_ACTIVE; + s->ss_entry(s->sock, SS_SetMemMap, &win->ctl); + s->state &= ~SOCKET_WIN_REQ(win->index); + + /* Release system memory */ + release_mem_region(win->base, win->size); + win->handle->state &= ~CLIENT_WIN_REQ(win->index); + + win->magic = 0; + + return CS_SUCCESS; +} /* release_window */ + +/*====================================================================*/ + +static int request_configuration(client_handle_t handle, + config_req_t *req) +{ + int i; + u_int base; + socket_info_t *s; + config_t *c; + pccard_io_map iomap; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + i = handle->Socket; s = socket_table[i]; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + +#ifdef CONFIG_CARDBUS + if (handle->state & CLIENT_CARDBUS) { + if (!(req->IntType & INT_CARDBUS)) + return CS_UNSUPPORTED_MODE; + if (s->lock_count != 0) + return CS_CONFIGURATION_LOCKED; + cb_enable(s); + handle->state |= CLIENT_CONFIG_LOCKED; + s->lock_count++; + return CS_SUCCESS; + } +#endif + + if (req->IntType & INT_CARDBUS) + return CS_UNSUPPORTED_MODE; + c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + + /* Do power control. We don't allow changes in Vcc. */ + if (s->socket.Vcc != req->Vcc) + return CS_BAD_VCC; + if (req->Vpp1 != req->Vpp2) + return CS_BAD_VPP; + s->socket.Vpp = req->Vpp1; + if (s->ss_entry(s->sock, SS_SetSocket, &s->socket)) + return CS_BAD_VPP; + + c->Vcc = req->Vcc; c->Vpp1 = c->Vpp2 = req->Vpp1; + + /* Pick memory or I/O card, DMA mode, interrupt */ + c->IntType = req->IntType; + c->Attributes = req->Attributes; + if (req->IntType & INT_MEMORY_AND_IO) + s->socket.flags |= SS_IOCARD; + if (req->Attributes & CONF_ENABLE_DMA) + s->socket.flags |= SS_DMA_MODE; + if (req->Attributes & CONF_ENABLE_SPKR) + s->socket.flags |= SS_SPKR_ENA; + if (req->Attributes & CONF_ENABLE_IRQ) + s->socket.io_irq = s->irq.AssignedIRQ; + else + s->socket.io_irq = 0; + s->ss_entry(s->sock, SS_SetSocket, &s->socket); + s->lock_count++; + + /* Set up CIS configuration registers */ + base = c->ConfigBase = req->ConfigBase; + c->Present = c->CardValues = req->Present; + if (req->Present & PRESENT_COPY) { + c->Copy = req->Copy; + write_cis_mem(s, 1, (base + CISREG_SCR)>>1, 1, &c->Copy); + } + if (req->Present & PRESENT_OPTION) { + if (s->functions == 1) + c->Option = req->ConfigIndex & COR_CONFIG_MASK; + else { + c->Option = req->ConfigIndex & COR_MFC_CONFIG_MASK; + c->Option |= COR_FUNC_ENA|COR_ADDR_DECODE|COR_IREQ_ENA; + } + if (c->state & CONFIG_IRQ_REQ) + if (!(c->irq.Attributes & IRQ_FORCED_PULSE)) + c->Option |= COR_LEVEL_REQ; + write_cis_mem(s, 1, (base + CISREG_COR)>>1, 1, &c->Option); + udelay(40*1000); + } + if (req->Present & PRESENT_STATUS) { + c->Status = req->Status; + write_cis_mem(s, 1, (base + CISREG_CCSR)>>1, 1, &c->Status); + } + if (req->Present & PRESENT_PIN_REPLACE) { + c->Pin = req->Pin; + write_cis_mem(s, 1, (base + CISREG_PRR)>>1, 1, &c->Pin); + } + if (req->Present & PRESENT_EXT_STATUS) { + c->ExtStatus = req->ExtStatus; + write_cis_mem(s, 1, (base + CISREG_ESR)>>1, 1, &c->ExtStatus); + } + if (req->Present & PRESENT_IOBASE_0) { + i = c->io.BasePort1 & 0xff; + write_cis_mem(s, 1, (base + CISREG_IOBASE_0)>>1, 1, &i); + i = (c->io.BasePort1 >> 8) & 0xff; + write_cis_mem(s, 1, (base + CISREG_IOBASE_1)>>1, 1, &i); + } + if (req->Present & PRESENT_IOSIZE) { + i = c->io.NumPorts1 + c->io.NumPorts2 - 1; + write_cis_mem(s, 1, (base + CISREG_IOSIZE)>>1, 1, &i); + } + + /* Configure I/O windows */ + if (c->state & CONFIG_IO_REQ) { + iomap.speed = io_speed; + for (i = 0; i < MAX_IO_WIN; i++) + if (s->io[i].NumPorts != 0) { + iomap.map = i; + iomap.flags = MAP_ACTIVE; + switch (s->io[i].Attributes & IO_DATA_PATH_WIDTH) { + case IO_DATA_PATH_WIDTH_16: + iomap.flags |= MAP_16BIT; break; + case IO_DATA_PATH_WIDTH_AUTO: + iomap.flags |= MAP_AUTOSZ; break; + default: + break; + } + iomap.start = s->io[i].BasePort; + iomap.stop = iomap.start + s->io[i].NumPorts - 1; + s->ss_entry(s->sock, SS_SetIOMap, &iomap); + s->io[i].Config++; + } + } + + c->state |= CONFIG_LOCKED; + handle->state |= CLIENT_CONFIG_LOCKED; + return CS_SUCCESS; +} /* request_configuration */ + +/*====================================================================== + + Request_io() reserves ranges of port addresses for a socket. + I have not implemented range sharing or alias addressing. + +======================================================================*/ + +static int request_io(client_handle_t handle, io_req_t *req) +{ + socket_info_t *s; + config_t *c; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + + if (handle->state & CLIENT_CARDBUS) { +#ifdef CONFIG_CARDBUS + int ret = cb_config(s); + if (ret == CS_SUCCESS) + handle->state |= CLIENT_IO_REQ; + return ret; +#else + return CS_UNSUPPORTED_FUNCTION; +#endif + } + + if (!req) + return CS_UNSUPPORTED_MODE; + c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + if (c->state & CONFIG_IO_REQ) + return CS_IN_USE; + if (req->Attributes1 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS)) + return CS_BAD_ATTRIBUTE; + if ((req->NumPorts2 > 0) && + (req->Attributes2 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS))) + return CS_BAD_ATTRIBUTE; + + if (alloc_io_space(s, req->Attributes1, &req->BasePort1, + req->NumPorts1, handle->dev_info)) + return CS_IN_USE; + + if (req->NumPorts2) { + if (alloc_io_space(s, req->Attributes2, &req->BasePort2, + req->NumPorts2, handle->dev_info)) { + release_io_space(s, req->BasePort1, req->NumPorts1); + return CS_IN_USE; + } + } + + c->io = *req; + c->state |= CONFIG_IO_REQ; + handle->state |= CLIENT_IO_REQ; + return CS_SUCCESS; +} /* request_io */ + +/*====================================================================== + + Request_irq() reserves an irq for this client. + + Also, since Linux only reserves irq's when they are actually + hooked, we don't guarantee that an irq will still be available + when the configuration is locked. Now that I think about it, + there might be a way to fix this using a dummy handler. + +======================================================================*/ + +static int cs_request_irq(client_handle_t handle, irq_req_t *req) +{ + socket_info_t *s; + config_t *c; + int try, ret = 0, irq = 0; + u_int mask; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + s = SOCKET(handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + c = CONFIG(handle); + if (c->state & CONFIG_LOCKED) + return CS_CONFIGURATION_LOCKED; + if (c->state & CONFIG_IRQ_REQ) + return CS_IN_USE; + + /* Short cut: if the interrupt is PCI, there are no options */ + if (s->cap.irq_mask == (1 << s->cap.pci_irq)) + irq = s->cap.pci_irq; +#ifdef CONFIG_ISA + else if (s->irq.AssignedIRQ != 0) { + /* If the interrupt is already assigned, it must match */ + irq = s->irq.AssignedIRQ; + if (req->IRQInfo1 & IRQ_INFO2_VALID) { + mask = req->IRQInfo2 & s->cap.irq_mask; + ret = ((mask >> irq) & 1) ? 0 : CS_BAD_ARGS; + } else + ret = ((req->IRQInfo1&IRQ_MASK) == irq) ? 0 : CS_BAD_ARGS; + } else { + ret = CS_IN_USE; + if (req->IRQInfo1 & IRQ_INFO2_VALID) { + mask = req->IRQInfo2 & s->cap.irq_mask; + mask &= ~(1 << s->cap.pci_irq); + for (try = 0; try < 2; try++) { + for (irq = 0; irq < 32; irq++) + if ((mask >> irq) & 1) { + ret = try_irq(req->Attributes, irq, try); + if (ret == 0) break; + } + if (ret == 0) break; + } + } else { + irq = req->IRQInfo1 & IRQ_MASK; + ret = try_irq(req->Attributes, irq, 1); + } + } +#endif + if (ret != 0) return ret; + + if (req->Attributes & IRQ_HANDLE_PRESENT) { + if (bus_request_irq(s->cap.bus, irq, req->Handler, + ((req->Attributes & IRQ_TYPE_DYNAMIC_SHARING) || + (s->functions > 1) || + (irq == s->cap.pci_irq)) ? SA_SHIRQ : 0, + handle->dev_info, req->Instance)) + return CS_IN_USE; + } + + c->irq.Attributes = req->Attributes; + s->irq.AssignedIRQ = req->AssignedIRQ = irq; + s->irq.Config++; + + c->state |= CONFIG_IRQ_REQ; + handle->state |= CLIENT_IRQ_REQ; + return CS_SUCCESS; +} /* cs_request_irq */ + +/*====================================================================== + + Request_window() establishes a mapping between card memory space + and system memory space. + +======================================================================*/ + +static int request_window(client_handle_t *handle, win_req_t *req) +{ + socket_info_t *s; + window_t *win; + int w; + + if (CHECK_HANDLE(*handle)) + return CS_BAD_HANDLE; + s = SOCKET(*handle); + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (req->Attributes & (WIN_PAGED | WIN_SHARED)) + return CS_BAD_ATTRIBUTE; + + for (w = 0; w < MAX_WIN; w++) + if (!(s->state & SOCKET_WIN_REQ(w))) break; + if (w == MAX_WIN) + return CS_OUT_OF_RESOURCE; + + /* Window size defaults to smallest available */ + if (req->Size == 0) + req->Size = s->cap.map_size; + + /* Allocate system memory window */ + win = &s->win[w]; + win->magic = WINDOW_MAGIC; + win->index = w; + win->handle = *handle; + win->sock = s; + win->base = req->Base; + win->size = req->Size; + if (find_mem_region(&win->base, win->size, (*handle)->dev_info, + ((s->cap.features & SS_CAP_MEM_ALIGN) ? + req->Size : s->cap.map_size), + (req->Attributes & WIN_MAP_BELOW_1MB) || + !(s->cap.features & SS_CAP_PAGE_REGS))) + return CS_IN_USE; + req->Base = win->base; + (*handle)->state |= CLIENT_WIN_REQ(w); + + /* Configure the socket controller */ + win->ctl.map = w+1; + win->ctl.flags = 0; + win->ctl.speed = req->AccessSpeed; + if (req->Attributes & WIN_MEMORY_TYPE) + win->ctl.flags |= MAP_ATTRIB; + if (req->Attributes & WIN_ENABLE) + win->ctl.flags |= MAP_ACTIVE; + if (req->Attributes & WIN_DATA_WIDTH) + win->ctl.flags |= MAP_16BIT; + if (req->Attributes & WIN_USE_WAIT) + win->ctl.flags |= MAP_USE_WAIT; + win->ctl.sys_start = req->Base; + win->ctl.sys_stop = req->Base + req->Size-1; + win->ctl.card_start = 0; + if (s->ss_entry(s->sock, SS_SetMemMap, &win->ctl) != 0) + return CS_BAD_ARGS; + s->state |= SOCKET_WIN_REQ(w); + + /* Return window handle */ + *handle = (client_handle_t)win; + + return CS_SUCCESS; +} /* request_window */ + +/*====================================================================== + + I'm not sure which "reset" function this is supposed to use, + but for now, it uses the low-level interface's reset, not the + CIS register. + +======================================================================*/ + +static int reset_card(client_handle_t handle, client_req_t *req) +{ + int i, ret; + socket_info_t *s; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + i = handle->Socket; s = socket_table[i]; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (s->state & SOCKET_RESET_PENDING) + return CS_IN_USE; + s->state |= SOCKET_RESET_PENDING; + + ret = send_event(s, CS_EVENT_RESET_REQUEST, CS_EVENT_PRI_LOW); + if (ret != 0) { + s->state &= ~SOCKET_RESET_PENDING; + handle->event_callback_args.info = (void *)(u_long)ret; + EVENT(handle, CS_EVENT_RESET_COMPLETE, CS_EVENT_PRI_LOW); + } else { + DEBUG(1, "cs: resetting socket %d\n", i); + send_event(s, CS_EVENT_RESET_PHYSICAL, CS_EVENT_PRI_LOW); + s->reset_handle = handle; + reset_socket(i); + } + return CS_SUCCESS; +} /* reset_card */ + +/*====================================================================== + + These shut down or wake up a socket. They are sort of user + initiated versions of the APM suspend and resume actions. + +======================================================================*/ + +static int suspend_card(client_handle_t handle, client_req_t *req) +{ + int i; + socket_info_t *s; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + i = handle->Socket; s = socket_table[i]; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (s->state & SOCKET_SUSPEND) + return CS_IN_USE; + + DEBUG(1, "cs: suspending socket %d\n", i); + send_event(s, CS_EVENT_PM_SUSPEND, CS_EVENT_PRI_LOW); + s->ss_entry(s->sock, SS_SetSocket, &dead_socket); + s->state |= SOCKET_SUSPEND; + + return CS_SUCCESS; +} /* suspend_card */ + +static int resume_card(client_handle_t handle, client_req_t *req) +{ + int i; + socket_info_t *s; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + i = handle->Socket; s = socket_table[i]; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + if (!(s->state & SOCKET_SUSPEND)) + return CS_IN_USE; + + DEBUG(1, "cs: waking up socket %d\n", i); + setup_socket(i); + + return CS_SUCCESS; +} /* resume_card */ + +/*====================================================================== + + These handle user requests to eject or insert a card. + +======================================================================*/ + +static int eject_card(client_handle_t handle, client_req_t *req) +{ + int i, ret; + socket_info_t *s; + u_long flags; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + i = handle->Socket; s = socket_table[i]; + if (!(s->state & SOCKET_PRESENT)) + return CS_NO_CARD; + + DEBUG(1, "cs: user eject request on socket %d\n", i); + + ret = send_event(s, CS_EVENT_EJECTION_REQUEST, CS_EVENT_PRI_LOW); + if (ret != 0) + return ret; + + spin_lock_irqsave(&s->lock, flags); + do_shutdown(s); + spin_unlock_irqrestore(&s->lock, flags); + + return CS_SUCCESS; + +} /* eject_card */ + +static int insert_card(client_handle_t handle, client_req_t *req) +{ + int i, status; + socket_info_t *s; + u_long flags; + + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + i = handle->Socket; s = socket_table[i]; + if (s->state & SOCKET_PRESENT) + return CS_IN_USE; + + DEBUG(1, "cs: user insert request on socket %d\n", i); + + spin_lock_irqsave(&s->lock, flags); + if (!(s->state & SOCKET_SETUP_PENDING)) { + s->state |= SOCKET_SETUP_PENDING; + spin_unlock_irqrestore(&s->lock, flags); + s->ss_entry(i, SS_GetStatus, &status); + if (status & SS_DETECT) + setup_socket(i); + else { + s->state &= ~SOCKET_SETUP_PENDING; + return CS_NO_CARD; + } + } else + spin_unlock_irqrestore(&s->lock, flags); + + return CS_SUCCESS; +} /* insert_card */ + +/*====================================================================== + + Maybe this should send a CS_EVENT_CARD_INSERTION event if we + haven't sent one to this client yet? + +======================================================================*/ + +static int set_event_mask(client_handle_t handle, eventmask_t *mask) +{ + u_int events, bit; + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + if (handle->Attributes & CONF_EVENT_MASK_VALID) + return CS_BAD_SOCKET; + handle->EventMask = mask->EventMask; + events = handle->PendingEvents & handle->EventMask; + handle->PendingEvents -= events; + while (events != 0) { + bit = ((events ^ (events-1)) + 1) >> 1; + EVENT(handle, bit, CS_EVENT_PRI_LOW); + events -= bit; + } + return CS_SUCCESS; +} /* set_event_mask */ + +/*====================================================================*/ + +static int report_error(client_handle_t handle, error_info_t *err) +{ + int i; + char *serv; + + if (CHECK_HANDLE(handle)) + printk(KERN_NOTICE); + else + printk(KERN_NOTICE "%s: ", handle->dev_info); + + for (i = 0; i < SERVICE_COUNT; i++) + if (service_table[i].key == err->func) break; + if (i < SERVICE_COUNT) + serv = service_table[i].msg; + else + serv = "Unknown service number"; + + for (i = 0; i < ERROR_COUNT; i++) + if (error_table[i].key == err->retcode) break; + if (i < ERROR_COUNT) + printk("%s: %s\n", serv, error_table[i].msg); + else + printk("%s: Unknown error code %#x\n", serv, err->retcode); + + return CS_SUCCESS; +} /* report_error */ + +/*====================================================================*/ + +int CardServices(int func, void *a1, void *a2, void *a3) +{ + +#ifdef PCMCIA_DEBUG + if (pc_debug > 1) { + int i; + for (i = 0; i < SERVICE_COUNT; i++) + if (service_table[i].key == func) break; + if (i < SERVICE_COUNT) + printk(KERN_DEBUG "cs: CardServices(%s, 0x%p, 0x%p)\n", + service_table[i].msg, a1, a2); + else + printk(KERN_DEBUG "cs: CardServices(Unknown func %d, " + "0x%p, 0x%p)\n", func, a1, a2); + } +#endif + switch (func) { + case AccessConfigurationRegister: + return access_configuration_register(a1, a2); break; + case AdjustResourceInfo: + return adjust_resource_info(a1, a2); break; + case CheckEraseQueue: + return check_erase_queue(a1); break; + case CloseMemory: + return close_memory(a1); break; + case CopyMemory: + return copy_memory(a1, a2); break; + case DeregisterClient: + return deregister_client(a1); break; + case DeregisterEraseQueue: + return deregister_erase_queue(a1); break; + case GetFirstClient: + return get_first_client(a1, a2); break; + case GetCardServicesInfo: + return get_card_services_info(a1); break; + case GetConfigurationInfo: + return get_configuration_info(a1, a2); break; + case GetNextClient: + return get_next_client(a1, a2); break; + case GetFirstRegion: + return get_first_region(a1, a2); break; + case GetFirstTuple: + return get_first_tuple(a1, a2); break; + case GetNextRegion: + return get_next_region(a1, a2); break; + case GetNextTuple: + return get_next_tuple(a1, a2); break; + case GetStatus: + return get_status(a1, a2); break; + case GetTupleData: + return get_tuple_data(a1, a2); break; + case MapMemPage: + return map_mem_page(a1, a2); break; + case ModifyConfiguration: + return modify_configuration(a1, a2); break; + case ModifyWindow: + return modify_window(a1, a2); break; + case OpenMemory: + return open_memory(a1, a2); + case ParseTuple: + return parse_tuple(a1, a2, a3); break; + case ReadMemory: + return read_memory(a1, a2, a3); break; + case RegisterClient: + return register_client(a1, a2); break; + case RegisterEraseQueue: + return register_erase_queue(a1, a2); break; + case RegisterMTD: + return register_mtd(a1, a2); break; + case ReleaseConfiguration: + return release_configuration(a1, a2); break; + case ReleaseIO: + return release_io(a1, a2); break; + case ReleaseIRQ: + return cs_release_irq(a1, a2); break; + case ReleaseWindow: + return release_window(a1); break; + case RequestConfiguration: + return request_configuration(a1, a2); break; + case RequestIO: + return request_io(a1, a2); break; + case RequestIRQ: + return cs_request_irq(a1, a2); break; + case RequestWindow: + return request_window(a1, a2); break; + case ResetCard: + return reset_card(a1, a2); break; + case SetEventMask: + return set_event_mask(a1, a2); break; + case ValidateCIS: + return validate_cis(a1, a2); break; + case WriteMemory: + return write_memory(a1, a2, a3); break; + case BindDevice: + return bind_device(a1); break; + case BindMTD: + return bind_mtd(a1); break; + case ReportError: + return report_error(a1, a2); break; + case SuspendCard: + return suspend_card(a1, a2); break; + case ResumeCard: + return resume_card(a1, a2); break; + case EjectCard: + return eject_card(a1, a2); break; + case InsertCard: + return insert_card(a1, a2); break; + case ReplaceCIS: + return replace_cis(a1, a2); break; + case GetFirstWindow: + return get_first_window(a1, a2); break; + case GetNextWindow: + return get_next_window(a1, a2); break; + case GetMemPage: + return get_mem_page(a1, a2); break; + default: + return CS_UNSUPPORTED_FUNCTION; break; + } + +} /* CardServices */ + +/*====================================================================== + + OS-specific module glue goes here + +======================================================================*/ + +EXPORT_SYMBOL(register_ss_entry); +EXPORT_SYMBOL(unregister_ss_entry); +EXPORT_SYMBOL(CardServices); +EXPORT_SYMBOL(MTDHelperEntry); + +static int __init init_pcmcia_cs(void) +{ + printk(KERN_INFO "%s\n", release); +#ifdef MODULE + printk(KERN_INFO " %s\n", kernel); +#endif + printk(KERN_INFO " %s\n", options); + DEBUG(0, "%s\n", version); +#ifdef CONFIG_APM + if (do_apm) + apm_register_callback(&handle_apm_event); +#endif +#ifdef CONFIG_PROC_FS + proc_pccard = create_proc_entry("pccard", S_IFDIR, proc_bus); +#endif + return 0; +} + +static void __exit exit_pcmcia_cs(void) +{ + printk(KERN_INFO "unloading PCMCIA Card Services\n"); +#ifdef CONFIG_PROC_FS + if (proc_pccard) { + remove_proc_entry("pccard", proc_bus); + } +#endif +#ifdef CONFIG_APM + if (do_apm) + apm_unregister_callback(&handle_apm_event); +#endif + release_resource_db(); +} + +module_init(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..aca0a0d0c --- /dev/null +++ b/drivers/pcmcia/cs_internal.h @@ -0,0 +1,280 @@ +/* + * cs_internal.h 1.43 1999/09/07 15:19:04 + * + * 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 + * <dhinds@hyper.stanford.edu>. Portions created by David A. Hinds + * are Copyright (C) 1999 David A. Hinds. All Rights Reserved. + */ + +#ifndef _LINUX_CS_INTERNAL_H +#define _LINUX_CS_INTERNAL_H + +typedef struct erase_busy_t { + eraseq_entry_t *erase; + client_handle_t client; + struct timer_list timeout; + struct erase_busy_t *prev, *next; +} erase_busy_t; + +#define ERASEQ_MAGIC 0xFA67 +typedef struct eraseq_t { + u_short eraseq_magic; + client_handle_t handle; + int count; + eraseq_entry_t *entry; +} eraseq_t; + +#define CLIENT_MAGIC 0x51E6 +typedef struct client_t { + u_short client_magic; + socket_t Socket; + u_char Function; + dev_info_t dev_info; + u_int Attributes; + u_int state; + event_t EventMask, PendingEvents; + int (*event_handler)(event_t event, int priority, + event_callback_args_t *); + event_callback_args_t event_callback_args; + struct client_t *next; + u_int mtd_count; + wait_queue_head_t mtd_req; + erase_busy_t erase_busy; +} client_t; + +/* Flags in client state */ +#define CLIENT_CONFIG_LOCKED 0x0001 +#define CLIENT_IRQ_REQ 0x0002 +#define CLIENT_IO_REQ 0x0004 +#define CLIENT_UNBOUND 0x0008 +#define CLIENT_STALE 0x0010 +#define CLIENT_WIN_REQ(i) (0x20<<(i)) +#define CLIENT_CARDBUS 0x8000 + +typedef struct io_window_t { + u_int Attributes; + ioaddr_t BasePort, NumPorts; + ioaddr_t InUse, Config; +} io_window_t; + +#define WINDOW_MAGIC 0xB35C +typedef struct window_t { + u_short magic; + u_short index; + client_handle_t handle; + struct socket_info_t *sock; + u_long base; + u_long size; + pccard_mem_map ctl; +} window_t; + +#define REGION_MAGIC 0xE3C9 +typedef struct region_t { + u_short region_magic; + u_short state; + dev_info_t dev_info; + client_handle_t mtd; + u_int MediaID; + region_info_t info; +} region_t; + +#define REGION_STALE 0x01 + +/* Each card function gets one of these guys */ +typedef struct config_t { + u_int state; + u_int Attributes; + u_int Vcc, Vpp1, Vpp2; + u_int IntType; + u_int ConfigBase; + u_char Status, Pin, Copy, Option, ExtStatus; + u_int Present; + u_int CardValues; + io_req_t io; + struct { + u_int Attributes; + } irq; +} config_t; + +/* Maximum number of IO windows per socket */ +#define MAX_IO_WIN 2 + +/* Maximum number of memory windows per socket */ +#define MAX_WIN 4 + +/* The size of the CIS cache */ +#define MAX_CIS_TABLE 64 +#define MAX_CIS_DATA 512 + +typedef struct socket_info_t { + spinlock_t lock; + ss_entry_t ss_entry; + u_int sock; + socket_state_t socket; + socket_cap_t cap; + u_int state; + u_short functions; + u_short lock_count; + client_handle_t clients; + u_int real_clients; + client_handle_t reset_handle; + struct timer_list setup, shutdown; + u_long unreset_timeout; + pccard_mem_map cis_mem; + u_char *cis_virt; + config_t *config; +#ifdef CONFIG_CARDBUS + u_int cb_cis_space; + cb_bridge_map cb_cis_map; + u_char *cb_cis_virt; + struct cb_config_t *cb_config; +#endif + struct { + u_int AssignedIRQ; + u_int Config; + } irq; + io_window_t io[MAX_IO_WIN]; + window_t win[MAX_WIN]; + region_t *c_region, *a_region; + erase_busy_t erase_busy; + int cis_used; + struct { + u_int addr; + u_short len; + u_short attr; + } cis_table[MAX_CIS_TABLE]; + char cis_cache[MAX_CIS_DATA]; + u_int fake_cis_len; + char *fake_cis; +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *proc; +#endif +} socket_info_t; + +/* 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_SETUP_PENDING 0x0010 +#define SOCKET_SHUTDOWN_PENDING 0x0020 +#define SOCKET_RESET_PENDING 0x0040 +#define SOCKET_SUSPEND 0x0080 +#define SOCKET_WIN_REQ(i) (0x0100<<(i)) +#define SOCKET_IO_REQ(i) (0x1000<<(i)) +#define SOCKET_REGION_INFO 0x4000 +#define SOCKET_CARDBUS 0x8000 + +#define CHECK_HANDLE(h) \ + (((h) == NULL) || ((h)->client_magic != CLIENT_MAGIC)) + +#define CHECK_SOCKET(s) \ + (((s) >= sockets) || (socket_table[s]->ss_entry == NULL)) + +#define SOCKET(h) (socket_table[(h)->Socket]) +#define CONFIG(h) (&SOCKET(h)->config[(h)->Function]) + +#define CHECK_REGION(r) \ + (((r) == NULL) || ((r)->region_magic != REGION_MAGIC)) + +#define CHECK_ERASEQ(q) \ + (((q) == NULL) || ((q)->eraseq_magic != ERASEQ_MAGIC)) + +#define EVENT(h, e, p) \ + ((h)->event_handler((e), (p), &(h)->event_callback_args)) + +/* In cardbus.c */ +int cb_alloc(socket_info_t *s); +void cb_free(socket_info_t *s); +int cb_config(socket_info_t *s); +void cb_release(socket_info_t *s); +void cb_enable(socket_info_t *s); +void cb_disable(socket_info_t *s); +void read_cb_mem(socket_info_t *s, u_char fn, int space, + u_int addr, u_int len, void *ptr); +int cb_setup_cis_mem(socket_info_t *s, int space); +void cb_release_cis_mem(socket_info_t *s); + +/* In cistpl.c */ +void read_cis_mem(socket_info_t *s, int attr, + u_int addr, u_int len, void *ptr); +void write_cis_mem(socket_info_t *s, int attr, + u_int addr, u_int len, void *ptr); +int setup_cis_mem(socket_info_t *s); +void release_cis_mem(socket_info_t *s); +int verify_cis_cache(socket_info_t *s); +void preload_cis_cache(socket_info_t *s); +int get_first_tuple(client_handle_t handle, tuple_t *tuple); +int get_next_tuple(client_handle_t handle, tuple_t *tuple); +int get_tuple_data(client_handle_t handle, tuple_t *tuple); +int parse_tuple(client_handle_t handle, tuple_t *tuple, cisparse_t *parse); +int validate_cis(client_handle_t handle, cisinfo_t *info); +int replace_cis(client_handle_t handle, cisdump_t *cis); +int read_tuple(client_handle_t handle, cisdata_t code, void *parse); + +/* In bulkmem.c */ +void retry_erase_list(struct erase_busy_t *list, u_int cause); +int get_first_region(client_handle_t handle, region_info_t *rgn); +int get_next_region(client_handle_t handle, region_info_t *rgn); +int register_mtd(client_handle_t handle, mtd_reg_t *reg); +int register_erase_queue(client_handle_t *handle, eraseq_hdr_t *header); +int deregister_erase_queue(eraseq_handle_t eraseq); +int check_erase_queue(eraseq_handle_t eraseq); +int open_memory(client_handle_t *handle, open_mem_t *open); +int close_memory(memory_handle_t handle); +int read_memory(memory_handle_t handle, mem_op_t *req, caddr_t buf); +int write_memory(memory_handle_t handle, mem_op_t *req, caddr_t buf); +int copy_memory(memory_handle_t handle, copy_op_t *req); + +/* In rsrc_mgr */ +void validate_mem(int (*is_valid)(u_long), int (*do_cksum)(u_long), + int force_low); +int find_io_region(ioaddr_t *base, ioaddr_t num, char *name); +int find_mem_region(u_long *base, u_long num, char *name, + u_long align, int force_low); +int try_irq(u_int Attributes, int irq, int specific); +void undo_irq(u_int Attributes, int irq); +int adjust_resource_info(client_handle_t handle, adjust_t *adj); +void release_resource_db(void); +int proc_read_io(char *buf, char **start, off_t pos, + int count, int *eof, void *data); +int proc_read_mem(char *buf, char **start, off_t pos, + int count, int *eof, void *data); + +/* in pnp components */ +int proc_read_irq(char *buf, char **start, off_t pos, + int count, int *eof, void *data); +void pnp_bios_init(void); +void pnp_proc_init(void); +void pnp_proc_done(void); +void pnp_rsrc_init(void); +void pnp_rsrc_done(void); + +#define MAX_SOCK 8 +extern socket_t sockets; +extern socket_info_t *socket_table[MAX_SOCK]; + +#ifdef CONFIG_PROC_FS +extern struct proc_dir_entry *proc_pccard; +#endif + +#ifdef PCMCIA_DEBUG +extern int pc_debug; +#define DEBUG(n, args...) do { if (pc_debug>(n)) printk(KERN_DEBUG args); } while (0) +#else +#define DEBUG(n, args...) do { } while (0) +#endif + +#endif /* _LINUX_CS_INTERNAL_H */ diff --git a/drivers/pcmcia/ds.c b/drivers/pcmcia/ds.c new file mode 100644 index 000000000..71550228b --- /dev/null +++ b/drivers/pcmcia/ds.c @@ -0,0 +1,899 @@ +/*====================================================================== + + PC Card Driver Services + + ds.c 1.98 1999/09/15 15:32:19 + + 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 + <dhinds@hyper.stanford.edu>. 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 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/kernel.h> +#include <linux/major.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/malloc.h> +#include <linux/mm.h> +#include <linux/fcntl.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/ioctl.h> +#include <linux/poll.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include <pcmcia/ds.h> + +#ifdef PCMCIA_DEBUG +int pc_debug = PCMCIA_DEBUG; +MODULE_PARM(pc_debug, "i"); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static const char *version = +"ds.c 1.98 1999/09/15 15:32:19 (David Hinds)"; +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +typedef struct driver_info_t { + dev_info_t dev_info; + int use_count; + dev_link_t *(*attach)(void); + void (*detach)(dev_link_t *); + struct driver_info_t *next; +} driver_info_t; + +typedef struct socket_bind_t { + driver_info_t *driver; + dev_link_t *instance; + struct socket_bind_t *next; +} socket_bind_t; + +/* Device user information */ +#define MAX_EVENTS 32 +#define USER_MAGIC 0x7ea4 +#define CHECK_USER(u) \ + (((u) == NULL) || ((u)->user_magic != USER_MAGIC)) +typedef struct user_info_t { + u_int user_magic; + int event_head, event_tail; + event_t event[MAX_EVENTS]; + struct user_info_t *next; +} user_info_t; + +/* Socket state information */ +typedef struct socket_info_t { + client_handle_t handle; + int state; + user_info_t *user; + int req_pending, req_result; + wait_queue_head_t queue, request; + struct timer_list removal; + socket_bind_t *bind; +} socket_info_t; + +#define SOCKET_PRESENT 0x01 +#define SOCKET_BUSY 0x02 +#define SOCKET_REMOVAL_PENDING 0x10 + +/*====================================================================*/ + +/* Device driver ID passed to Card Services */ +static dev_info_t dev_info = "Driver Services"; + +/* Linked list of all registered device drivers */ +static driver_info_t *root_driver = NULL; + +static int sockets = 0, major_dev = -1; +static socket_info_t *socket_table = NULL; + +/*====================================================================*/ + +static void cs_error(client_handle_t handle, int func, int ret) +{ + error_info_t err = { func, ret }; + CardServices(ReportError, handle, &err); +} + +/*====================================================================== + + Register_pccard_driver() and unregister_pccard_driver() are used + tell Driver Services that a PC Card client driver is available to + be bound to sockets. + +======================================================================*/ + +int register_pccard_driver(dev_info_t *dev_info, + dev_link_t *(*attach)(void), + void (*detach)(dev_link_t *)) +{ + driver_info_t *driver; + socket_bind_t *b; + int i; + + DEBUG(0, "ds: register_pccard_driver('%s')\n", (char *)dev_info); + for (driver = root_driver; driver; driver = driver->next) + if (strncmp((char *)dev_info, (char *)driver->dev_info, + DEV_NAME_LEN) == 0) + break; + if (!driver) { + driver = kmalloc(sizeof(driver_info_t), GFP_KERNEL); + strncpy(driver->dev_info, (char *)dev_info, DEV_NAME_LEN); + driver->use_count = 0; + driver->next = root_driver; + root_driver = driver; + } + + driver->attach = attach; + driver->detach = detach; + if (driver->use_count == 0) return 0; + + /* Instantiate any already-bound devices */ + for (i = 0; i < sockets; i++) + for (b = socket_table[i].bind; b; b = b->next) { + if (b->driver != driver) continue; + b->instance = driver->attach(); + if (b->instance == NULL) + printk(KERN_NOTICE "ds: unable to create instance " + "of '%s'!\n", driver->dev_info); + } + + return 0; +} /* register_pccard_driver */ + +/*====================================================================*/ + +int unregister_pccard_driver(dev_info_t *dev_info) +{ + driver_info_t *target, **d = &root_driver; + socket_bind_t *b; + int i; + + DEBUG(0, "ds: unregister_pccard_driver('%s')\n", + (char *)dev_info); + while ((*d) && (strncmp((*d)->dev_info, (char *)dev_info, + DEV_NAME_LEN) != 0)) + d = &(*d)->next; + if (*d == NULL) + return -1; + + target = *d; + if (target->use_count == 0) { + *d = target->next; + kfree(target); + } else { + /* Blank out any left-over device instances */ + target->attach = NULL; target->detach = NULL; + for (i = 0; i < sockets; i++) + for (b = socket_table[i].bind; b; b = b->next) + if (b->driver == target) b->instance = NULL; + } + return 0; +} /* unregister_pccard_driver */ + +/*====================================================================== + + These manage a ring buffer of events pending for one user process + +======================================================================*/ + +static int queue_empty(user_info_t *user) +{ + return (user->event_head == user->event_tail); +} + +static event_t get_queued_event(user_info_t *user) +{ + user->event_tail = (user->event_tail+1) % MAX_EVENTS; + return user->event[user->event_tail]; +} + +static void queue_event(user_info_t *user, event_t event) +{ + user->event_head = (user->event_head+1) % MAX_EVENTS; + if (user->event_head == user->event_tail) + user->event_tail = (user->event_tail+1) % MAX_EVENTS; + user->event[user->event_head] = event; +} + +static void handle_event(socket_info_t *s, event_t event) +{ + user_info_t *user; + for (user = s->user; user; user = user->next) + queue_event(user, event); + wake_up_interruptible(&s->queue); +} + +static int handle_request(socket_info_t *s, event_t event) +{ + if (s->req_pending != 0) + return CS_IN_USE; + if (s->state & SOCKET_BUSY) + s->req_pending = 1; + handle_event(s, event); + if (s->req_pending > 0) { + interruptible_sleep_on(&s->request); + if (signal_pending(current)) + return CS_IN_USE; + else + return s->req_result; + } + return CS_SUCCESS; +} + +static void handle_removal(u_long sn) +{ + socket_info_t *s = &socket_table[sn]; + handle_event(s, CS_EVENT_CARD_REMOVAL); + s->state &= ~SOCKET_REMOVAL_PENDING; +} + +/*====================================================================== + + The card status event handler. + +======================================================================*/ + +static int ds_event(event_t event, int priority, + event_callback_args_t *args) +{ + socket_info_t *s; + int i; + + DEBUG(1, "ds: ds_event(0x%06x, %d, 0x%p)\n", + event, priority, args->client_handle); + s = args->client_data; + i = s - socket_table; + + switch (event) { + + case CS_EVENT_CARD_REMOVAL: + s->state &= ~SOCKET_PRESENT; + if (!(s->state & SOCKET_REMOVAL_PENDING)) { + s->state |= SOCKET_REMOVAL_PENDING; + s->removal.expires = jiffies + HZ/10; + add_timer(&s->removal); + } + break; + + case CS_EVENT_CARD_INSERTION: + s->state |= SOCKET_PRESENT; + handle_event(s, event); + break; + + case CS_EVENT_EJECTION_REQUEST: + return handle_request(s, event); + break; + + default: + handle_event(s, event); + break; + } + + return 0; +} /* ds_event */ + +/*====================================================================== + + bind_mtd() connects a memory region with an MTD client. + +======================================================================*/ + +static int bind_mtd(int i, mtd_info_t *mtd_info) +{ + mtd_bind_t bind_req; + int ret; + + bind_req.dev_info = &mtd_info->dev_info; + bind_req.Attributes = mtd_info->Attributes; + bind_req.Socket = i; + bind_req.CardOffset = mtd_info->CardOffset; + ret = CardServices(BindMTD, &bind_req); + if (ret != CS_SUCCESS) { + cs_error(NULL, BindMTD, ret); + printk(KERN_NOTICE "ds: unable to bind MTD '%s' to socket %d" + " offset 0x%x\n", + (char *)bind_req.dev_info, i, bind_req.CardOffset); + return -ENODEV; + } + return 0; +} /* bind_mtd */ + +/*====================================================================== + + bind_request() connects a socket to a particular client driver. + It looks up the specified device ID in the list of registered + drivers, binds it to the socket, and tries to create an instance + of the device. unbind_request() deletes a driver instance. + +======================================================================*/ + +static int bind_request(int i, bind_info_t *bind_info) +{ + struct driver_info_t *driver; + socket_bind_t *b; + bind_req_t bind_req; + socket_info_t *s = &socket_table[i]; + int ret; + + DEBUG(2, "bind_request(%d, '%s')\n", i, + (char *)bind_info->dev_info); + for (driver = root_driver; driver; driver = driver->next) + if (strcmp((char *)driver->dev_info, + (char *)bind_info->dev_info) == 0) + break; + if (driver == NULL) { + driver = kmalloc(sizeof(driver_info_t), GFP_KERNEL); + strncpy(driver->dev_info, bind_info->dev_info, DEV_NAME_LEN); + driver->use_count = 0; + driver->next = root_driver; + driver->attach = NULL; driver->detach = NULL; + root_driver = driver; + } + + for (b = s->bind; b; b = b->next) + if (driver == b->driver) + break; + if (b != NULL) { + bind_info->instance = b->instance; + return -EBUSY; + } + + bind_req.Socket = i; + bind_req.Function = bind_info->function; + bind_req.dev_info = &driver->dev_info; + ret = CardServices(BindDevice, &bind_req); + if (ret != CS_SUCCESS) { + cs_error(NULL, BindDevice, ret); + printk(KERN_NOTICE "ds: unable to bind '%s' to socket %d\n", + (char *)dev_info, i); + return -ENODEV; + } + + /* Add binding to list for this socket */ + driver->use_count++; + b = kmalloc(sizeof(socket_bind_t), GFP_KERNEL); + b->driver = driver; + b->instance = NULL; + b->next = s->bind; + s->bind = b; + + if (driver->attach) { + b->instance = driver->attach(); + if (b->instance == NULL) { + printk(KERN_NOTICE "ds: unable to create instance " + "of '%s'!\n", (char *)bind_info->dev_info); + return -ENODEV; + } + } + + return 0; +} /* bind_request */ + +/*====================================================================*/ + +static int get_device_info(int i, bind_info_t *bind_info, int first) +{ + socket_info_t *s = &socket_table[i]; + socket_bind_t *b; + dev_node_t *node; + + for (b = s->bind; b; b = b->next) + if (strcmp((char *)b->driver->dev_info, + (char *)bind_info->dev_info) == 0) + break; + if (b == NULL) return -ENODEV; + if ((b->instance == NULL) || + (b->instance->state & DEV_CONFIG_PENDING)) + return -EAGAIN; + if (first) + node = b->instance->dev; + else + for (node = b->instance->dev; node; node = node->next) + if (node == bind_info->next) break; + if (node == NULL) return -ENODEV; + + strncpy(bind_info->name, node->dev_name, DEV_NAME_LEN); + bind_info->name[DEV_NAME_LEN-1] = '\0'; + bind_info->major = node->major; + bind_info->minor = node->minor; + bind_info->next = node->next; + + return 0; +} /* get_device_info */ + +/*====================================================================*/ + +static int unbind_request(int i, bind_info_t *bind_info) +{ + socket_info_t *s = &socket_table[i]; + socket_bind_t **b, *c; + + DEBUG(2, "unbind_request(%d, '%s')\n", i, + (char *)bind_info->dev_info); + for (b = &s->bind; *b; b = &(*b)->next) + if (strcmp((char *)(*b)->driver->dev_info, + (char *)bind_info->dev_info) == 0) + break; + if (*b == NULL) + return -ENODEV; + + c = *b; + c->driver->use_count--; + if (c->driver->detach) { + if (c->instance) + c->driver->detach(c->instance); + } else { + if (c->driver->use_count == 0) { + driver_info_t **d; + for (d = &root_driver; *d; d = &((*d)->next)) + if (c->driver == *d) break; + *d = (*d)->next; + kfree_s(c->driver, sizeof(driver_info_t)); + } + } + *b = c->next; + kfree_s(c, sizeof(socket_bind_t)); + + return 0; +} /* unbind_request */ + +/*====================================================================== + + The user-mode PC Card device interface + +======================================================================*/ + +static int ds_open(struct inode *inode, struct file *file) +{ + socket_t i = MINOR(inode->i_rdev); + socket_info_t *s; + user_info_t *user; + + DEBUG(0, "ds_open(socket %d)\n", i); + if ((i >= sockets) || (sockets == 0)) + return -ENODEV; + s = &socket_table[i]; + if ((file->f_flags & O_ACCMODE) != O_RDONLY) { + if (s->state & SOCKET_BUSY) + return -EBUSY; + else + s->state |= SOCKET_BUSY; + } + + MOD_INC_USE_COUNT; + user = kmalloc(sizeof(user_info_t), GFP_KERNEL); + user->event_tail = user->event_head = 0; + user->next = s->user; + user->user_magic = USER_MAGIC; + s->user = user; + file->private_data = user; + + if (s->state & SOCKET_PRESENT) + queue_event(user, CS_EVENT_CARD_INSERTION); + return 0; +} /* ds_open */ + +/*====================================================================*/ + +static int ds_release(struct inode *inode, struct file *file) +{ + socket_t i = MINOR(inode->i_rdev); + socket_info_t *s; + user_info_t *user, **link; + + DEBUG(0, "ds_release(socket %d)\n", i); + if ((i >= sockets) || (sockets == 0)) + return 0; + s = &socket_table[i]; + user = file->private_data; + if (CHECK_USER(user)) + return 0; + + /* Unlink user data structure */ + if ((file->f_flags & O_ACCMODE) != O_RDONLY) + s->state &= ~SOCKET_BUSY; + file->private_data = NULL; + for (link = &s->user; *link; link = &(*link)->next) + if (*link == user) break; + if (link == NULL) + return 0; + *link = user->next; + user->user_magic = 0; + kfree_s(user, sizeof(user_info_t)); + + MOD_DEC_USE_COUNT; + return 0; +} /* ds_release */ + +/*====================================================================*/ + +static ssize_t ds_read(struct file *file, char *buf, + size_t count, loff_t *ppos) +{ + socket_t i = MINOR(file->f_dentry->d_inode->i_rdev); + socket_info_t *s; + user_info_t *user; + + DEBUG(2, "ds_read(socket %d)\n", i); + + if ((i >= sockets) || (sockets == 0)) + return -ENODEV; + if (count < 4) + return -EINVAL; + s = &socket_table[i]; + user = file->private_data; + if (CHECK_USER(user)) + return -EIO; + + if (queue_empty(user)) { + interruptible_sleep_on(&s->queue); + if (signal_pending(current)) + return -EINTR; + } + put_user(get_queued_event(user), (int *)buf); + return 4; +} /* ds_read */ + +/*====================================================================*/ + +static ssize_t ds_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + socket_t i = MINOR(file->f_dentry->d_inode->i_rdev); + socket_info_t *s; + user_info_t *user; + + DEBUG(2, "ds_write(socket %d)\n", i); + + if ((i >= sockets) || (sockets == 0)) + return -ENODEV; + if (count != 4) + return -EINVAL; + if ((file->f_flags & O_ACCMODE) == O_RDONLY) + return -EBADF; + s = &socket_table[i]; + user = file->private_data; + if (CHECK_USER(user)) + return -EIO; + + if (s->req_pending) { + s->req_pending--; + get_user(s->req_result, (int *)buf); + if ((s->req_result != 0) || (s->req_pending == 0)) + wake_up_interruptible(&s->request); + } else + return -EIO; + + return 4; +} /* ds_write */ + +/*====================================================================*/ + +static u_int ds_poll(struct file *file, poll_table *wait) +{ + socket_t i = MINOR(file->f_dentry->d_inode->i_rdev); + socket_info_t *s; + user_info_t *user; + + DEBUG(2, "ds_poll(socket %d)\n", i); + + if ((i >= sockets) || (sockets == 0)) + return POLLERR; + s = &socket_table[i]; + user = file->private_data; + if (CHECK_USER(user)) + return POLLERR; + poll_wait(file, &s->queue, wait); + if (!queue_empty(user)) + return POLLIN | POLLRDNORM; + return 0; +} /* ds_poll */ + +/*====================================================================*/ + +static int ds_ioctl(struct inode * inode, struct file * file, + u_int cmd, u_long arg) +{ + socket_t i = MINOR(inode->i_rdev); + socket_info_t *s; + u_int size; + int ret, err; + ds_ioctl_arg_t buf; + + DEBUG(2, "ds_ioctl(socket %d, %#x, %#lx)\n", i, cmd, arg); + + if ((i >= sockets) || (sockets == 0)) + return -ENODEV; + s = &socket_table[i]; + + size = (cmd & IOCSIZE_MASK) >> IOCSIZE_SHIFT; + if (size > sizeof(ds_ioctl_arg_t)) return -EINVAL; + + /* Permission check */ + if (!(cmd & IOC_OUT) && !suser()) + return -EPERM; + + if (cmd & IOC_IN) { + err = verify_area(VERIFY_READ, (char *)arg, size); + if (err) { + DEBUG(3, "ds_ioctl(): verify_read = %d\n", err); + return err; + } + } + if (cmd & IOC_OUT) { + err = verify_area(VERIFY_WRITE, (char *)arg, size); + if (err) { + DEBUG(3, "ds_ioctl(): verify_write = %d\n", err); + return err; + } + } + + err = ret = 0; + + if (cmd & IOC_IN) copy_from_user((char *)&buf, (char *)arg, size); + + switch (cmd) { + case DS_ADJUST_RESOURCE_INFO: + ret = CardServices(AdjustResourceInfo, s->handle, &buf.adjust); + break; + case DS_GET_CARD_SERVICES_INFO: + ret = CardServices(GetCardServicesInfo, &buf.servinfo); + break; + case DS_GET_CONFIGURATION_INFO: + ret = CardServices(GetConfigurationInfo, s->handle, &buf.config); + break; + case DS_GET_FIRST_TUPLE: + ret = CardServices(GetFirstTuple, s->handle, &buf.tuple); + break; + case DS_GET_NEXT_TUPLE: + ret = CardServices(GetNextTuple, s->handle, &buf.tuple); + break; + case DS_GET_TUPLE_DATA: + buf.tuple.TupleData = buf.tuple_parse.data; + buf.tuple.TupleDataMax = sizeof(buf.tuple_parse.data); + ret = CardServices(GetTupleData, s->handle, &buf.tuple); + break; + case DS_PARSE_TUPLE: + buf.tuple.TupleData = buf.tuple_parse.data; + ret = CardServices(ParseTuple, s->handle, &buf.tuple, + &buf.tuple_parse.parse); + break; + case DS_RESET_CARD: + ret = CardServices(ResetCard, s->handle, NULL); + break; + case DS_GET_STATUS: + ret = CardServices(GetStatus, s->handle, &buf.status); + break; + case DS_VALIDATE_CIS: + ret = CardServices(ValidateCIS, s->handle, &buf.cisinfo); + break; + case DS_SUSPEND_CARD: + ret = CardServices(SuspendCard, s->handle, NULL); + break; + case DS_RESUME_CARD: + ret = CardServices(ResumeCard, s->handle, NULL); + break; + case DS_EJECT_CARD: + ret = CardServices(EjectCard, s->handle, NULL); + break; + case DS_INSERT_CARD: + ret = CardServices(InsertCard, s->handle, NULL); + break; + case DS_ACCESS_CONFIGURATION_REGISTER: + if ((buf.conf_reg.Action == CS_WRITE) && !suser()) + return -EPERM; + ret = CardServices(AccessConfigurationRegister, s->handle, + &buf.conf_reg); + break; + case DS_GET_FIRST_REGION: + ret = CardServices(GetFirstRegion, s->handle, &buf.region); + break; + case DS_GET_NEXT_REGION: + ret = CardServices(GetNextRegion, s->handle, &buf.region); + break; + case DS_GET_FIRST_WINDOW: + buf.win_info.handle = (window_handle_t)s->handle; + ret = CardServices(GetFirstWindow, &buf.win_info.handle, + &buf.win_info.window); + break; + case DS_GET_NEXT_WINDOW: + ret = CardServices(GetNextWindow, &buf.win_info.handle, + &buf.win_info.window); + break; + case DS_GET_MEM_PAGE: + ret = CardServices(GetMemPage, buf.win_info.handle, + &buf.win_info.map); + break; + case DS_REPLACE_CIS: + ret = CardServices(ReplaceCIS, s->handle, &buf.cisdump); + break; + case DS_BIND_REQUEST: + if (!suser()) return -EPERM; + err = bind_request(i, &buf.bind_info); + break; + case DS_GET_DEVICE_INFO: + err = get_device_info(i, &buf.bind_info, 1); + break; + case DS_GET_NEXT_DEVICE: + err = get_device_info(i, &buf.bind_info, 0); + break; + case DS_UNBIND_REQUEST: + err = unbind_request(i, &buf.bind_info); + break; + case DS_BIND_MTD: + if (!suser()) return -EPERM; + err = bind_mtd(i, &buf.mtd_info); + break; + default: + err = -EINVAL; + } + + if ((err == 0) && (ret != CS_SUCCESS)) { + DEBUG(2, "ds_ioctl: ret = %d\n", ret); + switch (ret) { + case CS_BAD_SOCKET: case CS_NO_CARD: + err = -ENODEV; break; + case CS_BAD_ARGS: case CS_BAD_ATTRIBUTE: case CS_BAD_IRQ: + case CS_BAD_TUPLE: + err = -EINVAL; break; + case CS_IN_USE: + err = -EBUSY; break; + case CS_OUT_OF_RESOURCE: + err = -ENOSPC; break; + case CS_NO_MORE_ITEMS: + err = -ENODATA; break; + case CS_UNSUPPORTED_FUNCTION: + err = -ENOSYS; break; + default: + err = -EIO; break; + } + } + + if (cmd & IOC_OUT) copy_to_user((char *)arg, (char *)&buf, size); + + return err; +} /* ds_ioctl */ + +/*====================================================================*/ + +static struct file_operations ds_fops = { + NULL, /* lseek */ + ds_read, /* read */ + ds_write, /* write */ + NULL, /* readdir */ + ds_poll, /* poll */ + ds_ioctl, /* ioctl */ + NULL, /* mmap */ + ds_open, /* open */ + NULL, /* flush */ + ds_release, /* release */ + NULL /* fsync */ +}; + +EXPORT_SYMBOL(register_pccard_driver); +EXPORT_SYMBOL(unregister_pccard_driver); + +/*====================================================================*/ + +int __init init_pcmcia_ds(void) +{ + client_reg_t client_reg; + servinfo_t serv; + bind_req_t bind; + socket_info_t *s; + int i, ret; + + DEBUG(0, "%s\n", version); + + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "ds: Card Services release does not match!\n"); + return -1; + } + if (serv.Count == 0) { + printk(KERN_NOTICE "ds: no socket drivers loaded!\n"); + return -1; + } + + sockets = serv.Count; + socket_table = kmalloc(sockets*sizeof(socket_info_t), GFP_KERNEL); + for (i = 0, s = socket_table; i < sockets; i++, s++) { + s->state = 0; + s->user = NULL; + s->req_pending = 0; + init_waitqueue_head(&s->queue); + init_waitqueue_head(&s->request); + s->handle = NULL; + s->removal.prev = s->removal.next = NULL; + s->removal.data = i; + s->removal.function = &handle_removal; + s->bind = NULL; + } + + /* Set up hotline to Card Services */ + client_reg.dev_info = bind.dev_info = &dev_info; + client_reg.Attributes = INFO_MASTER_CLIENT; + client_reg.EventMask = + CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL | + CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET | + CS_EVENT_EJECTION_REQUEST | CS_EVENT_INSERTION_REQUEST | + CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME; + client_reg.event_handler = &ds_event; + client_reg.Version = 0x0210; + for (i = 0; i < sockets; i++) { + bind.Socket = i; + bind.Function = BIND_FN_ALL; + ret = CardServices(BindDevice, &bind); + if (ret != CS_SUCCESS) { + cs_error(NULL, BindDevice, ret); + break; + } + client_reg.event_callback_args.client_data = &socket_table[i]; + ret = CardServices(RegisterClient, &socket_table[i].handle, + &client_reg); + if (ret != CS_SUCCESS) { + cs_error(NULL, RegisterClient, ret); + break; + } + } + + /* Set up character device for user mode clients */ + i = register_chrdev(0, "pcmcia", &ds_fops); + if (i == -EBUSY) + printk(KERN_NOTICE "unable to find a free device # for " + "Driver Services\n"); + else + major_dev = i; + + return 0; +} + +#ifdef MODULE + +int __init init_module(void) +{ + return init_pcmcia_ds(); +} + +void __exit cleanup_module(void) +{ + int i; + if (major_dev != -1) + unregister_chrdev(major_dev, "pcmcia"); + for (i = 0; i < sockets; i++) + CardServices(DeregisterClient, socket_table[i].handle); + sockets = 0; + kfree(socket_table); +} + +#endif diff --git a/drivers/pcmcia/i82365.c b/drivers/pcmcia/i82365.c new file mode 100644 index 000000000..9e80943e4 --- /dev/null +++ b/drivers/pcmcia/i82365.c @@ -0,0 +1,2804 @@ +/*====================================================================== + + Device driver for Intel 82365 and compatible PC Card controllers, + and Yenta-compatible PCI-to-CardBus controllers. + + i82365.c 1.254 1999/09/15 15:32:19 + + 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 + <dhinds@hyper.stanford.edu>. 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 Public License version 2 (the "GPL"), in which + case the provisions of the GPL are applicable instead of the + above. If you wish to allow the use of your version of this file + only under the terms of the GPL and not to allow others to use + your version of this file under the MPL, indicate your decision + by deleting the provisions above and replace them with the notice + and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this + file under either the MPL or the GPL. + +======================================================================*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/malloc.h> +#include <linux/pci.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/proc_fs.h> +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/bitops.h> +#include <asm/segment.h> +#include <asm/system.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> + +/* ISA-bus controllers */ +#include "i82365.h" +#include "cirrus.h" +#include "vg468.h" +#include "ricoh.h" +#include "o2micro.h" + +/* PCI-bus controllers */ +#include "yenta.h" +#include "ti113x.h" +#include "smc34c90.h" +#include "topic.h" + +#ifdef PCMCIA_DEBUG +static int pc_debug = PCMCIA_DEBUG; +MODULE_PARM(pc_debug, "i"); +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +static const char *version = +"i82365.c 1.254 1999/09/15 15:32:19 (David Hinds)"; +#else +#define DEBUG(n, args...) do { } while (0) +#endif + +static void irq_count(int, void *, struct pt_regs *); +static inline int _check_irq(int irq, int flags) +{ + if (request_irq(irq, irq_count, flags, "x", NULL) != 0) + return -1; + free_irq(irq, NULL); + return 0; +} + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +#ifdef CONFIG_ISA +/* Default base address for i82365sl and other ISA chips */ +static int 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] = { -1 }; +/* The card status change interrupt -- 0 means autoselect */ +static int cs_irq = 0; +#endif + +/* 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; + +#ifdef CONFIG_ISA +/* Vadem options */ +static int async_clock = -1; +static int cable_mode = -1; +static int wakeup = 0; +#endif + +#ifdef CONFIG_ISA +MODULE_PARM(i365_base, "i"); +MODULE_PARM(ignore, "i"); +MODULE_PARM(extra_sockets, "i"); +MODULE_PARM(irq_mask, "i"); +MODULE_PARM(irq_list, "1-16i"); +MODULE_PARM(cs_irq, "i"); +MODULE_PARM(async_clock, "i"); +MODULE_PARM(cable_mode, "i"); +MODULE_PARM(wakeup, "i"); +#endif + +MODULE_PARM(do_scan, "i"); +MODULE_PARM(poll_interval, "i"); +MODULE_PARM(cycle_time, "i"); +MODULE_PARM(has_dma, "i"); +MODULE_PARM(has_led, "i"); +MODULE_PARM(has_ring, "i"); +MODULE_PARM(dynamic_mode, "i"); +MODULE_PARM(freq_bypass, "i"); +MODULE_PARM(setup_time, "i"); +MODULE_PARM(cmd_time, "i"); +MODULE_PARM(recov_time, "i"); + +#ifdef CONFIG_PCI +/* Scan PCI bus? */ +static int do_pci_probe = 1; +/* Default memory base address for CardBus controllers */ +static u_int cb_mem_base[] = { 0x68000000, 0xf8000000 }; +static int fast_pci = -1; +static int hold_time = -1; +/* Override BIOS interrupt routing mode? */ +static int irq_mode = -1; +static int has_clkrun = -1; +static int clkrun_sel = -1; +static int pci_latency = -1; +static int cb_latency = -1; +static int cb_bus_base = 0; +static int cb_bus_step = 2; +static int cb_write_post = -1; +MODULE_PARM(do_pci_probe, "i"); +MODULE_PARM(cb_mem_base, "i"); +MODULE_PARM(fast_pci, "i"); +MODULE_PARM(hold_time, "i"); +MODULE_PARM(irq_mode, "i"); +MODULE_PARM(has_clkrun, "i"); +MODULE_PARM(clkrun_sel, "i"); +MODULE_PARM(pci_latency, "i"); +MODULE_PARM(cb_latency, "i"); +MODULE_PARM(cb_bus_base, "i"); +MODULE_PARM(cb_bus_step, "i"); +MODULE_PARM(cb_write_post, "i"); +#endif + +#ifdef CONFIG_ISA +#ifdef CONFIG_PCI +/* PCI card status change interrupts? */ +static int pci_csc = 0; +/* PCI IO card functional interrupts? */ +static int pci_int = 0; +MODULE_PARM(pci_csc, "i"); +MODULE_PARM(pci_int, "i"); +#else /* no PCI */ +#define pci_csc 0 +#define pci_int 0 +#endif +#else /* no ISA */ +#ifdef CONFIG_PCI +#define pci_csc 0 +#define pci_int 1 +#else +#error "No bus architectures defined!" +#endif +#endif + +/*====================================================================*/ + +typedef struct cirrus_state_t { + u_char misc1, misc2; + u_char timer[6]; +} cirrus_state_t; + +typedef struct vg46x_state_t { + u_char ctl, ema; +} vg46x_state_t; + +typedef struct ti113x_state_t { + u_int sysctl; + u_char cardctl, devctl, diag; +} ti113x_state_t; + +typedef struct rl5c4xx_state_t { + u_short misc, ctl, io, mem; +} rl5c4xx_state_t; + +typedef struct o2micro_state_t { + u_char mode_a, mode_b, mode_c, mode_d; + u_char mhpg, fifo, mode_e; +} o2micro_state_t; + +typedef struct topic_state_t { + u_char slot, ccr, cdr; + u_int rcr; +} topic_state_t; + +typedef struct socket_info_t { + u_short type, flags; + socket_cap_t cap; + u_short ioaddr; + u_short psock; + u_char cs_irq, intr; + void (*handler)(void *info, u_int events); + void *info; +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *proc; +#endif +#ifdef CONFIG_PCI + u_short vendor, device; + u_char revision, bus, devfn; + u_short bcr; + u_char pci_lat, cb_lat, sub_bus; + u_char cache, pmcs; + u_int cb_phys; + char *cb_virt; +#endif + union { + cirrus_state_t cirrus; + vg46x_state_t vg46x; +#ifdef CONFIG_PCI + o2micro_state_t o2micro; + ti113x_state_t ti113x; + rl5c4xx_state_t rl5c4xx; + topic_state_t topic; +#endif + } state; +} socket_info_t; + +/* Where we keep track of our sockets... */ +static int sockets = 0; +static socket_info_t socket[8] = { + { 0, }, /* ... */ +}; + +/* Default ISA interrupt mask */ +#define I365_MASK 0xdeb8 /* irq 15,14,12,11,10,9,7,5,4,3 */ + +static void pcic_interrupt_wrapper(u_long); +static void pcic_interrupt(int irq, void *dev, + struct pt_regs *regs); +static int pcic_service(u_int sock, u_int cmd, void *arg); +#ifdef CONFIG_PROC_FS +static void pcic_proc_remove(u_short sock); +#endif + +#ifdef CONFIG_ISA +static int grab_irq; +static spinlock_t isa_lock = SPIN_LOCK_UNLOCKED; +#endif +static struct timer_list poll_timer; + +/*====================================================================*/ + +#ifdef CONFIG_PCI + +#ifndef PCI_VENDOR_ID_INTEL +#define PCI_VENDOR_ID_INTEL 0x8086 +#endif +#ifndef PCI_VENDOR_ID_OMEGA +#define PCI_VENDOR_ID_OMEGA 0x119b +#endif +#ifndef PCI_DEVICE_ID_OMEGA_PCMCIA +#define PCI_DEVICE_ID_OMEGA_PCMCIA 0x1221 +#endif + +/* Default settings for PCI command configuration register */ +#define CMD_DFLT (PCI_COMMAND_IO|PCI_COMMAND_MEMORY| \ + PCI_COMMAND_MASTER|PCI_COMMAND_WAIT) + +#endif + +/* These definitions must match the pcic table! */ +typedef enum pcic_id { +#ifdef CONFIG_ISA + IS_I82365A, IS_I82365B, IS_I82365DF, + IS_IBM, IS_RF5Cx96, IS_VLSI, IS_VG468, IS_VG469, + IS_PD6710, IS_PD672X, IS_VT83C469, +#endif +#ifdef CONFIG_PCI + IS_PD6729, IS_PD6730, IS_OZ6729, IS_OZ6730, + IS_I82092AA, IS_OM82C092G, + IS_PD6832, IS_OZ6832, IS_OZ6836, + IS_RL5C465, IS_RL5C466, IS_RL5C475, IS_RL5C476, IS_RL5C478, + IS_SMC34C90, + IS_TI1130, IS_TI1131, IS_TI1250A, IS_TI1220, IS_TI1221, IS_TI1210, + IS_TI1251A, IS_TI1251B, IS_TI1450, IS_TI1225, IS_TI1211, IS_TI1420, + IS_TOPIC95_A, IS_TOPIC95_B, IS_TOPIC97, + IS_UNK_PCI, IS_UNK_CARDBUS +#endif +} pcic_id; + +/* Flags for classifying groups of controllers */ +#define IS_VADEM 0x0001 +#define IS_CIRRUS 0x0002 +#define IS_TI 0x0004 +#define IS_O2MICRO 0x0008 +#define IS_VIA 0x0010 +#define IS_TOPIC 0x0020 +#define IS_RICOH 0x0040 +#define IS_UNKNOWN 0x0400 +#define IS_VG_PWR 0x0800 +#define IS_DF_PWR 0x1000 +#define IS_PCI 0x2000 +#define IS_CARDBUS 0x4000 +#define IS_ALIVE 0x8000 + +typedef struct pcic_t { + char *name; + u_short flags; +#ifdef CONFIG_PCI + u_short vendor, device; +#endif +} pcic_t; + +static pcic_t pcic[] = { +#ifdef CONFIG_ISA + { "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 }, +#endif +#ifdef CONFIG_PCI + { "Cirrus PD6729", IS_CIRRUS|IS_PCI, + PCI_VENDOR_ID_CIRRUS, PCI_DEVICE_ID_CIRRUS_6729 }, + { "Cirrus PD6730", IS_CIRRUS|IS_PCI, + PCI_VENDOR_ID_CIRRUS, 0xffff }, + { "O2Micro OZ6729", IS_O2MICRO|IS_PCI|IS_VG_PWR, + PCI_VENDOR_ID_O2, PCI_DEVICE_ID_O2_6729 }, + { "O2Micro OZ6730", IS_O2MICRO|IS_PCI|IS_VG_PWR, + PCI_VENDOR_ID_O2, PCI_DEVICE_ID_O2_6730 }, + { "Intel 82092AA", IS_PCI, + PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_OMEGA_PCMCIA }, + { "Omega Micro 82C092G", IS_PCI, + PCI_VENDOR_ID_OMEGA, PCI_DEVICE_ID_OMEGA_PCMCIA }, + { "Cirrus PD6832", IS_CIRRUS|IS_CARDBUS, + PCI_VENDOR_ID_CIRRUS, PCI_DEVICE_ID_CIRRUS_6832 }, + { "O2Micro OZ6832/OZ6833", IS_O2MICRO|IS_CARDBUS|IS_VG_PWR, + PCI_VENDOR_ID_O2, PCI_DEVICE_ID_O2_6832 }, + { "O2Micro OZ6836/OZ6860", IS_O2MICRO|IS_CARDBUS|IS_VG_PWR, + PCI_VENDOR_ID_O2, PCI_DEVICE_ID_O2_6836 }, + { "Ricoh RL5C465", IS_RICOH|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C465 }, + { "Ricoh RL5C466", IS_RICOH|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C466 }, + { "Ricoh RL5C475", IS_RICOH|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C475 }, + { "Ricoh RL5C476", IS_RICOH|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C476 }, + { "Ricoh RL5C478", IS_RICOH|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C478 }, + { "SMC 34C90", IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_SMC, PCI_DEVICE_ID_SMC_34C90 }, + { "TI 1130", IS_TI|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1130 }, + { "TI 1131", IS_TI|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1131 }, + { "TI 1250A", IS_TI|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1250A }, + { "TI 1220", IS_TI|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1220 }, + { "TI 1221", IS_TI|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1221 }, + { "TI 1210", IS_TI|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1210 }, + { "TI 1251A", IS_TI|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1251A }, + { "TI 1251B", IS_TI|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1251B }, + { "TI 1450", IS_TI|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1450 }, + { "TI 1225", IS_TI|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1225 }, + { "TI 1211", IS_TI|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1211 }, + { "TI 1420", IS_TI|IS_CARDBUS|IS_DF_PWR, + PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1420 }, + { "Toshiba ToPIC95-A", IS_CARDBUS|IS_TOPIC|IS_DF_PWR, + PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_TOSHIBA_TOPIC95_A }, + { "Toshiba ToPIC95-B", IS_CARDBUS|IS_TOPIC|IS_DF_PWR, + PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_TOSHIBA_TOPIC95_B }, + { "Toshiba ToPIC97", IS_CARDBUS|IS_TOPIC|IS_DF_PWR, + PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_TOSHIBA_TOPIC97 }, + { "Unknown", IS_PCI|IS_UNKNOWN, 0, 0 }, + { "Unknown", IS_CARDBUS|IS_DF_PWR|IS_UNKNOWN, 0, 0 } +#endif +}; + +#define PCIC_COUNT (sizeof(pcic)/sizeof(pcic_t)) + +/*====================================================================*/ + +/* Some PCI shortcuts */ + +#ifdef CONFIG_PCI + +#define pci_readb pcibios_read_config_byte +#define pci_writeb pcibios_write_config_byte +#define pci_readw pcibios_read_config_word +#define pci_writew pcibios_write_config_word +#define pci_readl pcibios_read_config_dword +#define pci_writel pcibios_write_config_dword + +#define cb_readb(s, r) readb(socket[s].cb_virt + (r)) +#define cb_readl(s, r) readl(socket[s].cb_virt + (r)) +#define cb_writeb(s, r, v) writeb(v, socket[s].cb_virt + (r)) +#define cb_writel(s, r, v) writel(v, socket[s].cb_virt + (r)) + +static void cb_get_power(u_short sock, socket_state_t *state); +static void cb_set_power(u_short sock, socket_state_t *state); +#endif + +/*====================================================================*/ + +static u_char i365_get(u_short sock, u_short reg) +{ +#ifdef CONFIG_PCI + if (socket[sock].cb_virt) + return cb_readb(sock, 0x0800 + reg); + else +#endif + { + u_short port = socket[sock].ioaddr; + u_char val; + reg = I365_REG(socket[sock].psock, reg); + outb(reg, port); val = inb(port+1); + return val; + } +} + +static void i365_set(u_short sock, u_short reg, u_char data) +{ +#ifdef CONFIG_PCI + if (socket[sock].cb_virt) + cb_writeb(sock, 0x0800 + reg, data); + else +#endif + { + u_short port = socket[sock].ioaddr; + u_char val = I365_REG(socket[sock].psock, reg); + outb(val, port); outb(data, port+1); + } +} + +static void i365_bset(u_short sock, u_short reg, u_char mask) +{ + u_char d = i365_get(sock, reg); + d |= mask; + i365_set(sock, reg, d); +} + +static void i365_bclr(u_short sock, u_short reg, u_char mask) +{ + u_char d = i365_get(sock, reg); + d &= ~mask; + i365_set(sock, reg, d); +} + +static void i365_bflip(u_short sock, u_short reg, u_char mask, int b) +{ + u_char d = i365_get(sock, reg); + if (b) + d |= mask; + else + d &= ~mask; + i365_set(sock, reg, d); +} + +static u_short i365_get_pair(u_short sock, u_short reg) +{ + u_short a, b; + a = i365_get(sock, reg); + b = i365_get(sock, reg+1); + return (a + (b<<8)); +} + +static void i365_set_pair(u_short sock, u_short reg, u_short data) +{ + i365_set(sock, reg, data & 0xff); + i365_set(sock, reg+1, data >> 8); +} + +/*====================================================================== + + Code to save and restore global state information for Cirrus + PD67xx controllers, and to set and report global configuration + options. + + The VIA controllers also use these routines, as they are mostly + Cirrus lookalikes, without the timing registers. + +======================================================================*/ + +#define flip(v,b,f) (v = ((f)<0) ? v : ((f) ? ((v)|(b)) : ((v)&(~b)))) + +static void cirrus_get_state(u_short s) +{ + int i; + cirrus_state_t *p = &socket[s].state.cirrus; + p->misc1 = i365_get(s, PD67_MISC_CTL_1); + p->misc1 &= (PD67_MC1_MEDIA_ENA | PD67_MC1_INPACK_ENA); + p->misc2 = i365_get(s, PD67_MISC_CTL_2); + for (i = 0; i < 6; i++) + p->timer[i] = i365_get(s, PD67_TIME_SETUP(0)+i); +} + +static void cirrus_set_state(u_short s) +{ + int i; + u_char misc; + cirrus_state_t *p = &socket[s].state.cirrus; + + misc = i365_get(s, PD67_MISC_CTL_2); + i365_set(s, PD67_MISC_CTL_2, p->misc2); + if (misc & PD67_MC2_SUSPEND) mdelay(50); + misc = i365_get(s, PD67_MISC_CTL_1); + misc &= ~(PD67_MC1_MEDIA_ENA | PD67_MC1_INPACK_ENA); + i365_set(s, PD67_MISC_CTL_1, misc | p->misc1); + for (i = 0; i < 6; i++) + i365_set(s, PD67_TIME_SETUP(0)+i, p->timer[i]); +} + +#ifdef CONFIG_PCI +static int cirrus_set_irq_mode(u_short s, int pcsc, int pint) +{ + flip(socket[s].bcr, PD6832_BCR_MGMT_IRQ_ENA, !pcsc); + return 0; +} +#endif /* CONFIG_PCI */ + +static u_int __init cirrus_set_opts(u_short s, char *buf) +{ + socket_info_t *t = &socket[s]; + cirrus_state_t *p = &socket[s].state.cirrus; + u_int mask = 0xffff; + + if (has_ring == -1) has_ring = 1; + flip(p->misc2, PD67_MC2_IRQ15_RI, has_ring); + flip(p->misc2, PD67_MC2_DYNAMIC_MODE, dynamic_mode); + if (p->misc2 & PD67_MC2_IRQ15_RI) + strcat(buf, " [ring]"); + if (p->misc2 & PD67_MC2_DYNAMIC_MODE) + strcat(buf, " [dyn mode]"); + if (p->misc1 & PD67_MC1_INPACK_ENA) + strcat(buf, " [inpack]"); + if (!(t->flags & (IS_PCI | IS_CARDBUS))) { + 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; + flip(p->misc2, PD67_MC2_FREQ_BYPASS, freq_bypass); + if (p->misc2 & PD67_MC2_FREQ_BYPASS) + strcat(buf, " [freq bypass]"); + } +#ifdef CONFIG_PCI + } else { + p->misc1 &= ~PD67_MC1_MEDIA_ENA; + flip(p->misc2, PD67_MC2_FAST_PCI, fast_pci); + if (p->misc2 & PD67_MC2_IRQ15_RI) + mask &= (socket[s].type == IS_PD6730) ? ~0x0400 : ~0x8000; +#endif + } + 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. + +======================================================================*/ + +#ifdef CONFIG_ISA + +static void vg46x_get_state(u_short s) +{ + vg46x_state_t *p = &socket[s].state.vg46x; + p->ctl = i365_get(s, VG468_CTL); + if (socket[s].type == IS_VG469) + p->ema = i365_get(s, VG469_EXT_MODE); +} + +static void vg46x_set_state(u_short s) +{ + vg46x_state_t *p = &socket[s].state.vg46x; + i365_set(s, VG468_CTL, p->ctl); + if (socket[s].type == IS_VG469) + i365_set(s, VG469_EXT_MODE, p->ema); +} + +static u_int __init vg46x_set_opts(u_short s, char *buf) +{ + vg46x_state_t *p = &socket[s].state.vg46x; + + flip(p->ctl, VG468_CTL_ASYNC, async_clock); + flip(p->ema, VG469_MODE_CABLE, cable_mode); + if (p->ctl & VG468_CTL_ASYNC) + strcat(buf, " [async]"); + if (p->ctl & VG468_CTL_INPACK) + strcat(buf, " [inpack]"); + if (socket[s].type == IS_VG469) { + u_char vsel = i365_get(s, VG469_VSELECT); + if (vsel & VG469_VSEL_EXT_STAT) { + strcat(buf, " [ext mode]"); + if (vsel & VG469_VSEL_EXT_BUS) + strcat(buf, " [isa buf]"); + } + if (p->ema & VG469_MODE_CABLE) + strcat(buf, " [cable]"); + if (p->ema & VG469_MODE_COMPAT) + strcat(buf, " [c step]"); + } + return 0xffff; +} + +#endif + +/*====================================================================== + + Code to save and restore global state information for TI 1130 and + TI 1131 controllers, and to set and report global configuration + options. + +======================================================================*/ + +#ifdef CONFIG_PCI + +static void ti113x_get_state(u_short s) +{ + socket_info_t *t = &socket[s]; + ti113x_state_t *p = &socket[s].state.ti113x; + pci_readl(t->bus, t->devfn, TI113X_SYSTEM_CONTROL, &p->sysctl); + pci_readb(t->bus, t->devfn, TI113X_CARD_CONTROL, &p->cardctl); + pci_readb(t->bus, t->devfn, TI113X_DEVICE_CONTROL, &p->devctl); + pci_readb(t->bus, t->devfn, TI1250_DIAGNOSTIC, &p->diag); +} + +static void ti113x_set_state(u_short s) +{ + socket_info_t *t = &socket[s]; + ti113x_state_t *p = &socket[s].state.ti113x; + pci_writel(t->bus, t->devfn, TI113X_SYSTEM_CONTROL, p->sysctl); + pci_writeb(t->bus, t->devfn, TI113X_CARD_CONTROL, p->cardctl); + pci_writeb(t->bus, t->devfn, TI113X_DEVICE_CONTROL, p->devctl); + pci_writeb(t->bus, t->devfn, TI1250_MULTIMEDIA_CTL, 0); + pci_writeb(t->bus, t->devfn, TI1250_DIAGNOSTIC, p->diag); + i365_set_pair(s, TI113X_IO_OFFSET(0), 0); + i365_set_pair(s, TI113X_IO_OFFSET(1), 0); +} + +static int ti113x_set_irq_mode(u_short s, int pcsc, int pint) +{ + socket_info_t *t = &socket[s]; + ti113x_state_t *p = &t->state.ti113x; + t->intr = (pcsc) ? I365_INTR_ENA : 0; + if (t->type <= IS_TI1131) { + p->cardctl &= ~(TI113X_CCR_PCI_IRQ_ENA | + TI113X_CCR_PCI_IREQ | TI113X_CCR_PCI_CSC); + if (pcsc) + p->cardctl |= TI113X_CCR_PCI_IRQ_ENA | TI113X_CCR_PCI_CSC; + if (pint) + p->cardctl |= TI113X_CCR_PCI_IRQ_ENA | TI113X_CCR_PCI_IREQ; + } else if (t->type == IS_TI1250A) { + p->diag &= TI1250_DIAG_PCI_CSC | TI1250_DIAG_PCI_IREQ; + if (pcsc) + p->diag |= TI1250_DIAG_PCI_CSC; + if (pint) + p->diag |= TI1250_DIAG_PCI_IREQ; + } + return 0; +} + +static u_int __init ti113x_set_opts(u_short s, char *buf) +{ + socket_info_t *t = &socket[s]; + ti113x_state_t *p = &t->state.ti113x; + u_int mask = 0xffff; + int old = (t->type <= IS_TI1131); + + flip(p->sysctl, TI113X_SCR_CLKRUN_ENA, has_clkrun); + flip(p->sysctl, TI113X_SCR_CLKRUN_SEL, clkrun_sel); + flip(p->cardctl, TI113X_CCR_RIENB, has_ring); + p->cardctl &= ~TI113X_CCR_ZVENABLE; + switch (irq_mode) { + case 1: + p->devctl &= ~TI113X_DCR_IMODE_MASK; + p->devctl |= TI113X_DCR_IMODE_ISA; + break; + case 2: + p->devctl &= ~TI113X_DCR_IMODE_MASK; + p->devctl |= TI113X_DCR_IMODE_SERIAL; + break; + case 3: + p->devctl &= ~TI113X_DCR_IMODE_MASK; + p->devctl |= TI12XX_DCR_IMODE_ALL_SERIAL; + break; + default: + if ((p->devctl & TI113X_DCR_IMODE_MASK) == 0) + p->devctl |= TI113X_DCR_IMODE_ISA; + } + if (p->cardctl & TI113X_CCR_RIENB) { + strcat(buf, " [ring]"); + if (old) mask &= ~0x8000; + } + if (old && (p->sysctl & TI113X_SCR_CLKRUN_ENA)) { + if (p->sysctl & TI113X_SCR_CLKRUN_SEL) { + strcat(buf, " [clkrun irq 12]"); + mask &= ~0x1000; + } else { + strcat(buf, " [clkrun irq 10]"); + mask &= ~0x0400; + } + } + if (p->sysctl & TI113X_SCR_PWRSAVINGS) + strcat(buf, " [pwr save]"); + switch (p->devctl & TI113X_DCR_IMODE_MASK) { + case TI12XX_DCR_IMODE_PCI_ONLY: + strcat(buf, " [pci only]"); + mask = 0; + break; + case TI113X_DCR_IMODE_ISA: + strcat(buf, " [isa irq]"); + if (old) mask &= ~0x0018; + break; + case TI113X_DCR_IMODE_SERIAL: + strcat(buf, " [pci + serial irq]"); + mask = 0xffff; + break; + case TI12XX_DCR_IMODE_ALL_SERIAL: + strcat(buf, " [serial pci & irq]"); + mask = 0xffff; + break; + } + return mask; +} + +#endif + +/*====================================================================== + + Code to save and restore global state information for the Ricoh + RL5C4XX controllers, and to set and report global configuration + options. + +======================================================================*/ + +#ifdef CONFIG_PCI + +static void rl5c4xx_get_state(u_short s) +{ + socket_info_t *t = &socket[s]; + rl5c4xx_state_t *p = &socket[s].state.rl5c4xx; + pci_readw(t->bus, t->devfn, RL5C4XX_MISC, &p->misc); + pci_readw(t->bus, t->devfn, RL5C4XX_16BIT_CTL, &p->ctl); + pci_readw(t->bus, t->devfn, RL5C4XX_16BIT_IO_0, &p->io); + pci_readw(t->bus, t->devfn, RL5C4XX_16BIT_MEM_0, &p->mem); +} + +static void rl5c4xx_set_state(u_short s) +{ + socket_info_t *t = &socket[s]; + rl5c4xx_state_t *p = &socket[s].state.rl5c4xx; + pci_writew(t->bus, t->devfn, RL5C4XX_MISC, p->misc); + pci_writew(t->bus, t->devfn, RL5C4XX_16BIT_CTL, p->ctl); + pci_writew(t->bus, t->devfn, RL5C4XX_16BIT_IO_0, p->io); + pci_writew(t->bus, t->devfn, RL5C4XX_16BIT_MEM_0, p->mem); +} + +static u_int __init rl5c4xx_set_opts(u_short s, char *buf) +{ + rl5c4xx_state_t *p = &socket[s].state.rl5c4xx; + u_int mask = 0xffff; + int old = (socket[s].type < IS_RL5C475); + + p->ctl = RL5C4XX_16CTL_IO_TIMING | RL5C4XX_16CTL_MEM_TIMING; + if (old) p->ctl |= RL5C46X_16CTL_LEVEL_1 | RL5C46X_16CTL_LEVEL_2; + + if (setup_time >= 0) { + p->io = (p->io & ~RL5C4XX_SETUP_MASK) + + ((setup_time+1) << RL5C4XX_SETUP_SHIFT); + p->mem = (p->mem & ~RL5C4XX_SETUP_MASK) + + (setup_time << RL5C4XX_SETUP_SHIFT); + } + if (cmd_time >= 0) { + p->io = (p->io & ~RL5C4XX_CMD_MASK) + + (cmd_time << RL5C4XX_CMD_SHIFT); + p->mem = (p->mem & ~RL5C4XX_CMD_MASK) + + (cmd_time << RL5C4XX_CMD_SHIFT); + } + if (hold_time >= 0) { + p->io = (p->io & ~RL5C4XX_HOLD_MASK) + + (hold_time << RL5C4XX_HOLD_SHIFT); + p->mem = (p->mem & ~RL5C4XX_HOLD_MASK) + + (hold_time << RL5C4XX_HOLD_SHIFT); + } + if (!old) { + switch (irq_mode) { + case 1: + p->misc &= ~RL5C47X_MISC_SRIRQ_ENA; break; + case 2: + p->misc |= RL5C47X_MISC_SRIRQ_ENA; break; + } + if (p->misc & RL5C47X_MISC_SRIRQ_ENA) + sprintf(buf, " [serial irq]"); + else + sprintf(buf, " [isa irq]"); + buf += strlen(buf); + } + sprintf(buf, " [io %d/%d/%d] [mem %d/%d/%d]", + (p->io & RL5C4XX_SETUP_MASK) >> RL5C4XX_SETUP_SHIFT, + (p->io & RL5C4XX_CMD_MASK) >> RL5C4XX_CMD_SHIFT, + (p->io & RL5C4XX_HOLD_MASK) >> RL5C4XX_HOLD_SHIFT, + (p->mem & RL5C4XX_SETUP_MASK) >> RL5C4XX_SETUP_SHIFT, + (p->mem & RL5C4XX_CMD_MASK) >> RL5C4XX_CMD_SHIFT, + (p->mem & RL5C4XX_HOLD_MASK) >> RL5C4XX_HOLD_SHIFT); + return mask; +} + +#endif + +/*====================================================================== + + Code to save and restore global state information for O2Micro + controllers, and to set and report global configuration options. + +======================================================================*/ + +#ifdef CONFIG_PCI + +static void o2micro_get_state(u_short s) +{ + socket_info_t *t = &socket[s]; + o2micro_state_t *p = &socket[s].state.o2micro; + if ((t->revision == 0x34) || (t->revision == 0x62)) { + p->mode_a = i365_get(s, O2_MODE_A_2); + p->mode_b = i365_get(s, O2_MODE_B_2); + } else { + p->mode_a = i365_get(s, O2_MODE_A); + p->mode_b = i365_get(s, O2_MODE_B); + } + p->mode_c = i365_get(s, O2_MODE_C); + p->mode_d = i365_get(s, O2_MODE_D); + if (t->flags & IS_CARDBUS) { + p->mhpg = i365_get(s, O2_MHPG_DMA); + p->fifo = i365_get(s, O2_FIFO_ENA); + p->mode_e = i365_get(s, O2_MODE_E); + } +} + +static void o2micro_set_state(u_short s) +{ + socket_info_t *t = &socket[s]; + o2micro_state_t *p = &socket[s].state.o2micro; + if ((t->revision == 0x34) || (t->revision == 0x62)) { + i365_set(s, O2_MODE_A_2, p->mode_a); + i365_set(s, O2_MODE_B_2, p->mode_b); + } else { + i365_set(s, O2_MODE_A, p->mode_a); + i365_set(s, O2_MODE_B, p->mode_b); + } + i365_set(s, O2_MODE_C, p->mode_c); + i365_set(s, O2_MODE_D, p->mode_d); + if (t->flags & IS_CARDBUS) { + i365_set(s, O2_MHPG_DMA, p->mhpg); + i365_set(s, O2_FIFO_ENA, p->fifo); + i365_set(s, O2_MODE_E, p->mode_e); + } +} + +static u_int __init o2micro_set_opts(u_short s, char *buf) +{ + socket_info_t *t = &socket[s]; + o2micro_state_t *p = &socket[s].state.o2micro; + u_int mask = 0xffff; + + p->mode_b = (p->mode_b & ~O2_MODE_B_IDENT) | O2_MODE_B_ID_CSTEP; + flip(p->mode_b, O2_MODE_B_IRQ15_RI, has_ring); + p->mode_c &= ~(O2_MODE_C_ZVIDEO | O2_MODE_C_DREQ_MASK); + if (t->flags & IS_CARDBUS) { + p->mode_d &= ~O2_MODE_D_W97_IRQ; + p->mode_e &= ~O2_MODE_E_MHPG_DMA; + p->mhpg |= O2_MHPG_CINT_ENA | O2_MHPG_CSC_ENA; + p->mhpg &= ~O2_MHPG_CHANNEL; + } else { + if (p->mode_b & O2_MODE_B_IRQ15_RI) mask &= ~0x8000; + } + sprintf(buf, " [a %02x] [b %02x] [c %02x] [d %02x]", + p->mode_a, p->mode_b, p->mode_c, p->mode_d); + if (t->flags & IS_CARDBUS) { + buf += strlen(buf); + sprintf(buf, " [mhpg %02x] [fifo %02x] [e %02x]", + p->mhpg, p->fifo, p->mode_e); + } + return mask; +} + +#endif + +/*====================================================================== + + Code to save and restore global state information for the Toshiba + ToPIC 95 and 97 controllers, and to set and report global + configuration options. + +======================================================================*/ + +#ifdef CONFIG_PCI + +static void topic_get_state(u_short s) +{ + socket_info_t *t = &socket[s]; + topic_state_t *p = &socket[s].state.topic; + pci_readb(t->bus, t->devfn, TOPIC_SLOT_CONTROL, &p->slot); + pci_readb(t->bus, t->devfn, TOPIC_CARD_CONTROL, &p->ccr); + pci_readb(t->bus, t->devfn, TOPIC_CARD_DETECT, &p->cdr); + pci_readl(t->bus, t->devfn, TOPIC_REGISTER_CONTROL, &p->rcr); +} + +static void topic_set_state(u_short s) +{ + socket_info_t *t = &socket[s]; + topic_state_t *p = &socket[s].state.topic; + pci_writeb(t->bus, t->devfn, TOPIC_SLOT_CONTROL, p->slot); + pci_writeb(t->bus, t->devfn, TOPIC_CARD_CONTROL, p->ccr); + pci_writeb(t->bus, t->devfn, TOPIC_CARD_DETECT, p->cdr); + pci_writel(t->bus, t->devfn, TOPIC_REGISTER_CONTROL, p->rcr); +} + +static int topic_set_irq_mode(u_short s, int pcsc, int pint) +{ + if (socket[s].type == IS_TOPIC97) { + topic_state_t *p = &socket[s].state.topic; + flip(p->ccr, TOPIC97_ICR_IRQSEL, pcsc); + return 0; + } else { + return !pcsc; + } +} + +static u_int __init topic_set_opts(u_short s, char *buf) +{ + topic_state_t *p = &socket[s].state.topic; + + p->slot |= TOPIC_SLOT_SLOTON|TOPIC_SLOT_SLOTEN|TOPIC_SLOT_ID_LOCK; + p->cdr |= TOPIC_CDR_MODE_PC32; + p->cdr &= ~(TOPIC_CDR_SW_DETECT); + sprintf(buf, " [slot 0x%02x] [ccr 0x%02x] [cdr 0x%02x] [rcr 0x%02x]", + p->slot, p->ccr, p->cdr, p->rcr); + return 0xffff; +} + +#endif + +/*====================================================================== + + Routines to handle common CardBus options + +======================================================================*/ + +#ifdef CONFIG_PCI + +static void cb_get_state(u_short s) +{ + socket_info_t *t = &socket[s]; + + pci_readb(t->bus, t->devfn, PCI_CACHE_LINE_SIZE, &t->cache); + pci_readb(t->bus, t->devfn, PCI_LATENCY_TIMER, &t->pci_lat); + pci_readb(t->bus, t->devfn, CB_LATENCY_TIMER, &t->cb_lat); + pci_readb(t->bus, t->devfn, CB_CARDBUS_BUS, &t->cap.cardbus); + pci_readb(t->bus, t->devfn, CB_SUBORD_BUS, &t->sub_bus); + pci_readw(t->bus, t->devfn, CB_BRIDGE_CONTROL, &t->bcr); + { + struct pci_dev *pdev = pci_find_slot(t->bus, t->devfn); + t->cap.pci_irq = (pdev) ? pdev->irq : 0; + } + if (t->cap.pci_irq >= NR_IRQS) t->cap.pci_irq = 0; +} + +static void cb_set_state(u_short s) +{ + socket_info_t *t = &socket[s]; + if (t->pmcs) + pci_writew(t->bus, t->devfn, t->pmcs, PCI_PMCS_PWR_STATE_D0); + pci_writel(t->bus, t->devfn, CB_LEGACY_MODE_BASE, 0); + pci_writel(t->bus, t->devfn, PCI_BASE_ADDRESS_0, t->cb_phys); + pci_writew(t->bus, t->devfn, PCI_COMMAND, CMD_DFLT); + pci_writeb(t->bus, t->devfn, PCI_CACHE_LINE_SIZE, t->cache); + pci_writeb(t->bus, t->devfn, PCI_LATENCY_TIMER, t->pci_lat); + pci_writeb(t->bus, t->devfn, CB_LATENCY_TIMER, t->cb_lat); + pci_writeb(t->bus, t->devfn, CB_CARDBUS_BUS, t->cap.cardbus); + pci_writeb(t->bus, t->devfn, CB_SUBORD_BUS, t->sub_bus); + pci_writew(t->bus, t->devfn, CB_BRIDGE_CONTROL, t->bcr); +} + +static int cb_get_irq_mode(u_short s) +{ + return (!(socket[s].bcr & CB_BCR_ISA_IRQ)); +} + +static int cb_set_irq_mode(u_short s, int pcsc, int pint) +{ + socket_info_t *t = &socket[s]; + flip(t->bcr, CB_BCR_ISA_IRQ, !(pint)); + if (t->flags & IS_CIRRUS) + return cirrus_set_irq_mode(s, pcsc, pint); + else if (t->flags & IS_TI) + return ti113x_set_irq_mode(s, pcsc, pint); + else if (t->flags & IS_TOPIC) + return topic_set_irq_mode(s, pcsc, pint); + return 0; +} + +static void __init pci_scan(u_short sock); + +static void __init cb_set_opts(u_short s, char *buf) +{ + socket_info_t *t = &socket[s]; + t->bcr |= CB_BCR_WRITE_POST; + /* some TI1130's seem to exhibit problems with write posting */ + if (((t->type == IS_TI1130) && (t->revision == 4) && + (cb_write_post < 0)) || (cb_write_post == 0)) + t->bcr &= ~CB_BCR_WRITE_POST; + if (t->cache == 0) t->cache = 8; + if (pci_latency >= 0) t->pci_lat = pci_latency; + if (t->pci_lat == 0) t->pci_lat = 0xa8; + if (cb_latency >= 0) t->cb_lat = cb_latency; + if (t->cb_lat == 0) t->cb_lat = 0xb0; + if ((t->cap.pci_irq == 0) && (pci_csc || pci_int) && do_scan) + pci_scan(s); + if (t->cap.pci_irq == 0) + strcat(buf, " [no pci irq]"); + else + sprintf(buf, " [pci irq %d]", t->cap.pci_irq); + buf += strlen(buf); + if ((cb_bus_base > 0) || (t->cap.cardbus == 0)) { + if (cb_bus_base <= 0) cb_bus_base = 0x20; + t->cap.cardbus = cb_bus_base; + t->sub_bus = cb_bus_base+cb_bus_step; + cb_bus_base += cb_bus_step+1; + } + if (!(t->flags & IS_TOPIC)) + t->cap.features |= SS_CAP_PAGE_REGS; + sprintf(buf, " [lat %d/%d] [bus %d/%d]", + t->pci_lat, t->cb_lat, t->cap.cardbus, t->sub_bus); +} + +#endif + +/*====================================================================== + + Generic routines to get and set controller options + +======================================================================*/ + +static void get_host_state(u_short s) +{ + socket_info_t *t = &socket[s]; + if (t->flags & IS_CIRRUS) + cirrus_get_state(s); +#ifdef CONFIG_ISA + else if (t->flags & IS_VADEM) + vg46x_get_state(s); +#endif +#ifdef CONFIG_PCI + else if (t->flags & IS_O2MICRO) + o2micro_get_state(s); + else if (t->flags & IS_TI) + ti113x_get_state(s); + else if (t->flags & IS_RICOH) + rl5c4xx_get_state(s); + else if (t->flags & IS_TOPIC) + topic_get_state(s); + if (t->flags & IS_CARDBUS) + cb_get_state(s); +#endif +} + +static void set_host_state(u_short s) +{ + socket_info_t *t = &socket[s]; +#ifdef CONFIG_PCI + if (t->flags & IS_CARDBUS) + cb_set_state(s); +#endif + 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); +#ifdef CONFIG_ISA + if (t->flags & IS_VADEM) + vg46x_set_state(s); +#endif +#ifdef CONFIG_PCI + if (t->flags & IS_O2MICRO) + o2micro_set_state(s); + else if (t->flags & IS_TI) + ti113x_set_state(s); + else if (t->flags & IS_RICOH) + rl5c4xx_set_state(s); + else if (t->flags & IS_TOPIC) + topic_set_state(s); +#endif +} + +static u_int __init set_host_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_host_state(i); + if (socket[i].flags & IS_CIRRUS) + m = cirrus_set_opts(i, buf); +#ifdef CONFIG_ISA + else if (socket[i].flags & IS_VADEM) + m = vg46x_set_opts(i, buf); +#endif +#ifdef CONFIG_PCI + else if (socket[i].flags & IS_O2MICRO) + m = o2micro_set_opts(i, buf); + else if (socket[i].flags & IS_TI) + m = ti113x_set_opts(i, buf); + else if (socket[i].flags & IS_RICOH) + m = rl5c4xx_set_opts(i, buf); + else if (socket[i].flags & IS_TOPIC) + m = topic_set_opts(i, buf); + if (socket[i].flags & IS_CARDBUS) + cb_set_opts(i, buf+strlen(buf)); +#endif + set_host_state(i); + printk(KERN_INFO " host opts [%d]:%s\n", i, + (*buf) ? buf : " none"); + } +#ifdef CONFIG_PCI + /* Mask out all PCI interrupts */ + for (i = 0; i < sockets; i++) + m &= ~(1<<socket[i].cap.pci_irq); + { + struct pci_dev *p; + for (p = pci_devices; p; p = p->next) + m &= ~(1<<p->irq); + } +#endif + return m; +} + +/*====================================================================== + + Interrupt testing code, for ISA and PCI interrupts + +======================================================================*/ + +static volatile u_int irq_hits; +static u_short irq_sock; + +static void irq_count(int irq, void *dev, struct pt_regs *regs) +{ +#ifdef CONFIG_PCI + if (socket[irq_sock].flags & IS_CARDBUS) { + cb_writel(irq_sock, CB_SOCKET_EVENT, -1); + } else +#endif + i365_get(irq_sock, I365_CSC); + irq_hits++; + DEBUG(2, "-> hit on irq %d\n", irq); +} + +static u_int __init test_irq(u_short sock, int irq, int pci) +{ + u_char csc = (pci) ? 0 : irq; + DEBUG(2, " testing %s irq %d\n", pci ? "PCI" : "ISA", irq); + + if (request_irq(irq, irq_count, (pci?SA_SHIRQ:0), "scan", NULL) != 0) + return 1; + irq_hits = 0; irq_sock = sock; + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ/100); + if (irq_hits) { + free_irq(irq, NULL); + DEBUG(2, " spurious hit!\n"); + return 1; + } + + /* Generate one interrupt */ +#ifdef CONFIG_PCI + if (socket[sock].flags & IS_CARDBUS) { + cb_writel(sock, CB_SOCKET_EVENT, -1); + i365_set(sock, I365_CSCINT, I365_CSC_STSCHG | (csc << 4)); + cb_writel(sock, CB_SOCKET_EVENT, -1); + cb_writel(sock, CB_SOCKET_MASK, CB_SM_CSTSCHG); + cb_writel(sock, CB_SOCKET_FORCE, CB_SE_CSTSCHG); + udelay(1000); + cb_writel(sock, CB_SOCKET_EVENT, -1); + cb_writel(sock, CB_SOCKET_MASK, 0); + } else +#endif + { + i365_set(sock, I365_CSCINT, I365_CSC_DETECT | (csc << 4)); + i365_bset(sock, I365_GENCTL, I365_CTL_SW_IRQ); + udelay(1000); + } + + free_irq(irq, NULL); + + /* mask all interrupts */ + i365_set(sock, I365_CSCINT, 0); + DEBUG(2, " hits = %d\n", irq_hits); + + return (irq_hits != 1); +} + +#ifdef CONFIG_ISA + +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 + +#ifdef CONFIG_PCI + /* Only scan if we can select ISA csc irq's */ + if (!(socket[sock].flags & IS_CARDBUS) || + (cb_set_irq_mode(sock, 0, 0) == 0)) +#endif + if (do_scan) { + set_host_state(sock); + i365_set(sock, I365_CSCINT, 0); + for (i = 0; i < 16; i++) + if ((mask0 & (1 << i)) && (test_irq(sock, i, 0) == 0)) + mask1 |= (1 << i); + for (i = 0; i < 16; i++) + if ((mask1 & (1 << i)) && (test_irq(sock, i, 0) != 0)) + mask1 ^= (1 << i); + } + + printk(KERN_INFO " ISA irqs ("); + if (mask1) { + printk("scanned"); + } else { + /* Fallback: just find interrupts that aren't in use */ + for (i = 0; i < 16; i++) + if ((mask0 & (1 << i)) && (_check_irq(i, 0) == 0)) + mask1 |= (1 << i); + printk("default"); + /* If scan failed, default to polled status */ + if (!cs_irq && (poll_interval == 0)) poll_interval = HZ; + } + printk(") = "); + + for (i = 0; i < 16; i++) + if (mask1 & (1<<i)) + printk("%s%d", ((mask1 & ((1<<i)-1)) ? "," : ""), i); + if (mask1 == 0) printk("none!"); + + return mask1; +} + +#endif /* CONFIG_ISA */ + +#ifdef CONFIG_PCI + +static void __init pci_scan(u_short sock) +{ + u_int i; + + cb_set_irq_mode(sock, 1, 0); + set_host_state(sock); + i365_set(sock, I365_CSCINT, 0); + /* Only probe irq's 9..11, to be conservative */ + for (i = 9; i < 12; i++) { + if ((test_irq(sock, i, 1) == 0) && + (test_irq(sock, i, 1) == 0)) + break; + } + if (i < 12) socket[sock].cap.pci_irq = i; +} + +#endif /* CONFIG_PCI */ + +/*====================================================================*/ + +/* Time conversion functions */ + +static int to_cycles(int ns) +{ + return ns/cycle_time; +} /* speed_convert */ + +static int to_ns(int cycles) +{ + return cycle_time*cycles; +} + +/*====================================================================*/ + +#ifdef CONFIG_ISA + +static int __init identify(u_short 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 */ + +#endif + +/*====================================================================== + + 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; + u_short start, stop; + + stat = i365_get(sock, I365_STATUS); + start = i365_get_pair(sock, I365_IO(0)+I365_W_START); + stop = i365_get_pair(sock, I365_IO(0)+I365_W_STOP); + if ((stat & I365_CS_DETECT) && (stat & I365_CS_POWERON) && + (i365_get(sock, I365_INTCTL) & I365_PC_IOCARD) && + (i365_get(sock, I365_ADDRWIN) & I365_ENA_IO(0)) && + (check_region(start, stop-start+1) != 0) && + ((start & 0xfeef) != 0x02e8)) + return 1; + else + return 0; +} + +/*====================================================================*/ + +static void __init add_socket(u_short 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 use_pci = 0, isa_irq = 0; + socket_info_t *t = &socket[sockets-ns]; + + base = sockets-ns; + if (t->ioaddr > 0) request_region(t->ioaddr, 2, "i82365"); + + if (base == 0) printk("\n"); + printk(KERN_INFO " %s", pcic[type].name); +#ifdef CONFIG_PCI + if (t->flags & IS_UNKNOWN) + printk(" [0x%04x 0x%04x]", t->vendor, t->device); + if (t->flags & IS_CARDBUS) + printk(" PCI-to-CardBus at bus %d slot %d, mem 0x%08x", + t->bus, PCI_SLOT(t->devfn), t->cb_phys); + else if (t->flags & IS_PCI) + printk(" PCI-to-PCMCIA at bus %d slot %d, port %#x", + t->bus, PCI_SLOT(t->devfn), t->ioaddr); + else +#endif + printk(" ISA-to-PCMCIA at port %#x ofs 0x%02x", + t->ioaddr, t->psock*0x40); + printk(", %d socket%s\n", ns, ((ns > 1) ? "s" : "")); + +#ifdef CONFIG_ISA + /* Set host options, build basic interrupt mask */ + if (irq_list[0] == -1) + mask = irq_mask; + else + for (i = mask = 0; i < 16; i++) + mask |= (1<<irq_list[i]); +#endif + mask &= I365_MASK & set_host_opts(base, ns); +#ifdef CONFIG_ISA + /* Scan for ISA interrupts */ + mask = isa_scan(base, mask); +#else + printk(KERN_INFO " PCI card interrupts,"); +#endif + +#ifdef CONFIG_PCI + /* Can we use a PCI interrupt for card status changes? */ + if (pci_csc && t->cap.pci_irq) { + for (i = 0; i < ns; i++) + if (_check_irq(t[i].cap.pci_irq, SA_SHIRQ)) break; + if (i == ns) { + use_pci = 1; + printk(" PCI status changes\n"); + } + } +#endif + +#ifdef CONFIG_ISA + /* Poll if only two interrupts available */ + if (!use_pci && !poll_interval) { + u_int tmp = (mask & (mask-1)); + if ((tmp & (tmp-1)) == 0) + poll_interval = HZ; + } + /* Only try an ISA cs_irq if this is the first controller */ + if (!use_pci && !grab_irq && (cs_irq || !poll_interval)) { + /* Avoid irq 12 unless it is explicitly requested */ + u_int cs_mask = mask & ((cs_irq) ? (1<<cs_irq) : ~(1<<12)); + for (cs_irq = 15; cs_irq > 0; cs_irq--) + if ((cs_mask & (1 << cs_irq)) && + (_check_irq(cs_irq, 0) == 0)) + break; + if (cs_irq) { + grab_irq = 1; + isa_irq = cs_irq; + printk(" status change on irq %d\n", cs_irq); + } + } +#endif + + if (!use_pci && !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].cap.features |= SS_CAP_PCCARD; + t[i].cap.map_size = 0x1000; + t[i].cap.irq_mask = mask; + if (pci_int && t[i].cap.pci_irq) + t[i].cap.irq_mask |= (1 << t[i].cap.pci_irq); + t[i].cs_irq = isa_irq; +#ifdef CONFIG_PCI + if (t[i].flags & IS_CARDBUS) { + t[i].cap.features |= SS_CAP_CARDBUS; + cb_set_irq_mode(i, pci_csc && t[i].cap.pci_irq, + pci_int && t[i].cap.pci_irq); + } +#endif + } + +} /* add_pcic */ + +/*====================================================================*/ + +#ifdef CONFIG_PCI + +typedef struct pci_dev *pci_id_t; +static int __init pci_lookup(u_int class, pci_id_t *id, + u_char *bus, u_char *devfn) +{ + if ((*id = pci_find_class(class<<8, *id)) != NULL) { + *bus = (*id)->bus->number; + *devfn = (*id)->devfn; + return 0; + } else return -1; +} + +static void __init add_pci_bridge(int type, u_char bus, u_char devfn, + u_short v, u_short d) +{ + socket_info_t *s = &socket[sockets]; + u_short i, ns; + u_int addr; + + if (type == PCIC_COUNT) type = IS_UNK_PCI; + pci_readl(bus, devfn, PCI_BASE_ADDRESS_0, &addr); + addr &= ~0x1; + pci_writew(bus, devfn, PCI_COMMAND, CMD_DFLT); + for (i = ns = 0; i < ((type == IS_I82092AA) ? 4 : 2); i++) { + s->bus = bus; s->devfn = devfn; + s->vendor = v; s->device = d; + add_socket(addr, i, type); + ns++; s++; + } + add_pcic(ns, type); +} + +static void __init add_cb_bridge(int type, u_char bus, u_char devfn, + u_short v, u_short d0) +{ + socket_info_t *s = &socket[sockets]; + u_short d, ns; + u_char a, b, r, max; + + /* PCI bus enumeration is broken on some systems */ + for (ns = 0; ns < sockets; ns++) + if ((socket[ns].bus == bus) && (socket[ns].devfn == devfn)) + return; + + if (type == PCIC_COUNT) type = IS_UNK_CARDBUS; + pci_readb(bus, devfn, PCI_HEADER_TYPE, &a); + pci_readb(bus, devfn, PCI_CLASS_REVISION, &r); + max = (a & 0x80) ? 8 : 1; + for (ns = 0; ns < max; ns++, s++, devfn++) { + if (pci_readw(bus, devfn, PCI_DEVICE_ID, &d) || (d != d0)) + break; + s->bus = bus; s->devfn = devfn; + s->vendor = v; s->device = d; s->revision = r; + + /* Check for power management capabilities */ + pci_readb(bus, devfn, PCI_STATUS, &a); + if (a & PCI_STATUS_CAPLIST) { + pci_readb(bus, devfn, PCI_CB_CAPABILITY_POINTER, &b); + while (b != 0) { + pci_readb(bus, devfn, b+PCI_CAPABILITY_ID, &a); + if (a == PCI_CAPABILITY_PM) { + s->pmcs = b + PCI_PM_CONTROL_STATUS; + break; + } + pci_readb(bus, devfn, b+PCI_NEXT_CAPABILITY, &b); + } + } + /* If capability exists, make sure we're in D0 state */ + if (s->pmcs) + pci_writew(bus, devfn, s->pmcs, PCI_PMCS_PWR_STATE_D0); + + /* Map CardBus registers if they are not already mapped */ + pci_writel(bus, devfn, CB_LEGACY_MODE_BASE, 0); + pci_readl(bus, devfn, PCI_BASE_ADDRESS_0, &s->cb_phys); + if (s->cb_phys == 0) { + int i; + pci_writew(bus, devfn, PCI_COMMAND, CMD_DFLT); + for (i = 0; i < sizeof(cb_mem_base)/sizeof(u_int); i++) { + s->cb_phys = cb_mem_base[i]; + s->cb_virt = ioremap(s->cb_phys, 0x1000); + pci_writel(bus, devfn, PCI_BASE_ADDRESS_0, s->cb_phys); + /* Simple sanity checks */ + if (((readb(s->cb_virt+0x800+I365_IDENT) & 0xf0) + == 0x80) && + !(readb(s->cb_virt+0x800+I365_CSC) && + readb(s->cb_virt+0x800+I365_CSC) && + readb(s->cb_virt+0x800+I365_CSC))) + break; + iounmap(s->cb_virt); + } + if (i == sizeof(cb_mem_base)/sizeof(u_int)) { + pci_writel(bus, devfn, PCI_BASE_ADDRESS_0, 0); + s->cb_phys = 0; s->cb_virt = NULL; + printk("\n"); + printk(KERN_NOTICE " Bridge register mapping failed:" + " check cb_mem_base setting\n"); + break; + } + cb_mem_base[0] = cb_mem_base[i] + PAGE_SIZE; + } else { + s->cb_virt = ioremap(s->cb_phys, 0x1000); + } + + request_mem_region(s->cb_phys, 0x1000, "i82365"); + add_socket(0, 0, type); + } + if (ns == 0) return; + + s -= ns; + if (ns == 2) { + /* Nasty special check for bad bus mapping */ + pci_readb(bus, s[0].devfn, CB_CARDBUS_BUS, &a); + pci_readb(bus, s[1].devfn, CB_CARDBUS_BUS, &b); + if (a == b) { + pci_writeb(bus, s[0].devfn, CB_CARDBUS_BUS, 0); + pci_writeb(bus, s[1].devfn, CB_CARDBUS_BUS, 0); + } + } + add_pcic(ns, type); + + /* Re-do card type & voltage detection */ + cb_writel(sockets-ns, CB_SOCKET_FORCE, CB_SF_CVSTEST); + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ/5); + + /* Set up PCI bus bridge structures if needed */ + for (a = 0; a < ns; a++) { + struct pci_dev *self = pci_find_slot(bus, s[a].devfn); + struct pci_bus *child, *parent = self->bus; + for (child = parent->children; child; child = child->next) + if (child->number == s[a].cap.cardbus) break; + if (!child) { + child = kmalloc(sizeof(struct pci_bus), GFP_KERNEL); + memset(child, 0, sizeof(struct pci_bus)); + child->self = self; + child->primary = bus; + child->number = child->secondary = s[a].cap.cardbus; + child->subordinate = s[a].sub_bus; + child->parent = parent; + child->ops = parent->ops; + child->next = parent->children; + } + s[a].cap.cb_bus = parent->children = child; + } +} + +static void __init pci_probe(u_int class, void (add_fn) + (int, u_char, u_char, u_short, u_short)) +{ + u_short i, v, d; + u_char bus, devfn; + pci_id_t id; + + id = 0; + while (pci_lookup(class, &id, &bus, &devfn) == 0) { + if (PCI_FUNC(devfn) != 0) continue; + pci_readw(bus, devfn, PCI_VENDOR_ID, &v); + pci_readw(bus, devfn, PCI_DEVICE_ID, &d); + for (i = 0; i < PCIC_COUNT; i++) + if ((pcic[i].vendor == v) && (pcic[i].device == d)) break; + add_fn(i, bus, devfn, v, d); + } +} + +#endif /* CONFIG_PCI */ + +/*====================================================================*/ + +#ifdef CONFIG_ISA + +static void __init isa_probe(void) +{ + int i, j, sock, k; + int ns, id; + u_short port; + + if (check_region(i365_base, 2) != 0) { + if (sockets == 0) + printk("port conflict at %#x\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 < (extra_sockets ? 8 : 4); i += 2) { + 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); + } + } +} + +#endif + +/*====================================================================*/ + +static int __init init_i82365(void) +{ + servinfo_t serv; + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "i82365: Card Services release " + "does not match!\n"); + return -1; + } + DEBUG(0, "%s\n", version); + printk(KERN_INFO "Intel PCIC probe: "); + sockets = 0; + +#ifdef CONFIG_PCI + if (do_pci_probe && pcibios_present()) { + pci_probe(PCI_CLASS_BRIDGE_CARDBUS, add_cb_bridge); + pci_probe(PCI_CLASS_BRIDGE_PCMCIA, add_pci_bridge); + } +#endif + +#ifdef CONFIG_ISA + isa_probe(); +#endif + + if (sockets == 0) { + printk("not found.\n"); + return -ENODEV; + } + + /* Set up interrupt handler(s) */ +#ifdef CONFIG_ISA + if (grab_irq != 0) + request_irq(cs_irq, pcic_interrupt, 0, "i82365", NULL); +#endif +#ifdef CONFIG_PCI + if (pci_csc) { + u_int i, irq, mask = 0; + for (i = 0; i < sockets; i++) { + irq = socket[i].cap.pci_irq; + if (irq && !(mask & (1<<irq))) + request_irq(irq, pcic_interrupt, SA_SHIRQ, "i82365", NULL); + mask |= (1<<irq); + } + } +#endif + + if (register_ss_entry(sockets, &pcic_service) != 0) + printk(KERN_NOTICE "i82365: register_ss_entry() failed\n"); + + /* Finally, schedule a polling interrupt */ + if (poll_interval != 0) { + poll_timer.function = pcic_interrupt_wrapper; + poll_timer.data = 0; + poll_timer.prev = poll_timer.next = NULL; + poll_timer.expires = jiffies + poll_interval; + add_timer(&poll_timer); + } + + return 0; + +} /* init_i82365 */ + +/*====================================================================*/ + +static void __exit exit_i82365(void) +{ + int i; +#ifdef CONFIG_PROC_FS + for (i = 0; i < sockets; i++) pcic_proc_remove(i); +#endif + unregister_ss_entry(&pcic_service); + if (poll_interval != 0) + del_timer(&poll_timer); +#ifdef CONFIG_ISA + if (grab_irq != 0) + free_irq(cs_irq, NULL); +#endif +#ifdef CONFIG_PCI + if (pci_csc) { + u_int irq, mask = 0; + for (i = 0; i < sockets; i++) { + irq = socket[i].cap.pci_irq; + if (irq && !(mask & (1<<irq))) + free_irq(irq, NULL); + mask |= (1<<irq); + } + } +#endif + for (i = 0; i < sockets; i++) { + i365_set(i, I365_CSCINT, 0); +#ifdef CONFIG_PCI + if (socket[i].cb_virt) { + iounmap(socket[i].cb_virt); + release_mem_region(socket[i].cb_phys, 0x1000); + } else +#endif + release_region(socket[i].ioaddr, 2); + } +} /* exit_i82365 */ + +/*====================================================================*/ + +static void pcic_interrupt_wrapper(u_long data) +{ + pcic_interrupt(0, NULL, NULL); + poll_timer.expires = jiffies + poll_interval; + add_timer(&poll_timer); +} + +static void pcic_interrupt(int irq, void *dev, + struct pt_regs *regs) +{ + int i, j, csc; + u_int events, active; +#ifdef CONFIG_ISA + u_long flags = 0; +#endif + + DEBUG(4, "i82365: 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) && + (socket[i].cap.pci_irq != irq)) + continue; +#ifdef CONFIG_ISA + if (!(socket[i].flags & IS_CARDBUS)) + spin_lock_irqsave(&isa_lock, flags); +#endif + csc = i365_get(i, I365_CSC); + if ((csc == 0) || (!socket[i].handler) || + (i365_get(i, I365_IDENT) & 0x70)) { +#ifdef CONFIG_ISA + if (!(socket[i].flags & IS_CARDBUS)) + spin_unlock_irqrestore(&isa_lock, flags); +#endif + 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; + } +#ifdef CONFIG_ISA + if (!(socket[i].flags & IS_CARDBUS)) + spin_unlock_irqrestore(&isa_lock, flags); +#endif + DEBUG(2, "i82365: socket %d event 0x%02x\n", i, events); + if (events) + socket[i].handler(socket[i].info, events); + active |= events; + } + if (!active) break; + } + if (j == 20) + printk(KERN_NOTICE "i82365: infinite loop in interrupt handler\n"); + + DEBUG(4, "i82365: interrupt done\n"); +} /* pcic_interrupt */ + +/*====================================================================*/ + +static int pcic_register_callback(u_short sock, ss_callback_t *call) +{ + if (call == NULL) { + socket[sock].handler = NULL; + MOD_DEC_USE_COUNT; + } else { + MOD_INC_USE_COUNT; + socket[sock].handler = call->handler; + socket[sock].info = call->info; + } + return 0; +} /* pcic_register_callback */ + +/*====================================================================*/ + +static int pcic_inquire_socket(u_short sock, socket_cap_t *cap) +{ + *cap = socket[sock].cap; + return 0; +} /* pcic_inquire_socket */ + +/*====================================================================*/ + +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; + +#ifdef CONFIG_PCI + if (socket[sock].flags & IS_CARDBUS) { + status = cb_readl(sock, CB_SOCKET_STATE); +#ifndef CONFIG_CARDBUS + *value |= (status & CB_SS_32BIT) ? SS_CARDBUS : 0; +#endif + *value |= (status & CB_SS_3VCARD) ? SS_3VCARD : 0; + *value |= (status & CB_SS_XVCARD) ? SS_XVCARD : 0; + } else if (socket[sock].flags & IS_O2MICRO) { + status = i365_get(sock, O2_MODE_B); + *value |= (status & O2_MODE_B_VS1) ? 0 : SS_3VCARD; + *value |= (status & O2_MODE_B_VS2) ? 0 : SS_XVCARD; + } +#endif +#ifdef CONFIG_ISA + 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; + } + } +#endif + + DEBUG(1, "i82365: GetStatus(%d) = %#4.4x\n", sock, *value); + return 0; +} /* i365_get_status */ + +/*====================================================================*/ + +static int i365_get_socket(u_short sock, socket_state_t *state) +{ + socket_info_t *t = &socket[sock]; + u_char reg, vcc, vpp; + + reg = i365_get(sock, I365_POWER); + state->flags = (reg & I365_PWR_AUTO) ? SS_PWR_AUTO : 0; + state->flags |= (reg & I365_PWR_OUT) ? SS_OUTPUT_ENA : 0; + vcc = reg & I365_VCC_MASK; vpp = reg & I365_VPP1_MASK; + state->Vcc = state->Vpp = 0; +#ifdef CONFIG_PCI + if (t->flags & IS_CARDBUS) { + cb_get_power(sock, state); + } else +#endif + if (t->flags & IS_CIRRUS) { + if (i365_get(sock, PD67_MISC_CTL_1) & PD67_MC1_VCC_3V) { + if (reg & I365_VCC_5V) state->Vcc = 33; + if (vpp == I365_VPP1_5V) state->Vpp = 33; + } else { + if (reg & I365_VCC_5V) state->Vcc = 50; + if (vpp == I365_VPP1_5V) state->Vpp = 50; + } + if (vpp == I365_VPP1_12V) state->Vpp = 120; + } else if (t->flags & IS_VG_PWR) { + if (i365_get(sock, VG469_VSELECT) & VG469_VSEL_VCC) { + if (reg & I365_VCC_5V) state->Vcc = 33; + if (vpp == I365_VPP1_5V) state->Vpp = 33; + } else { + if (reg & I365_VCC_5V) state->Vcc = 50; + if (vpp == I365_VPP1_5V) state->Vpp = 50; + } + if (vpp == I365_VPP1_12V) state->Vpp = 120; + } else if (t->flags & IS_DF_PWR) { + if (vcc == I365_VCC_3V) state->Vcc = 33; + if (vcc == I365_VCC_5V) state->Vcc = 50; + if (vpp == I365_VPP1_5V) state->Vpp = 50; + if (vpp == I365_VPP1_12V) state->Vpp = 120; + } else { + if (reg & I365_VCC_5V) { + state->Vcc = 50; + if (vpp == I365_VPP1_5V) state->Vpp = 50; + if (vpp == I365_VPP1_12V) state->Vpp = 120; + } + } + + /* IO card, RESET flags, IO interrupt */ + reg = i365_get(sock, I365_INTCTL); + state->flags |= (reg & I365_PC_RESET) ? 0 : SS_RESET; + if (reg & I365_PC_IOCARD) state->flags |= SS_IOCARD; +#ifdef CONFIG_PCI + if (cb_get_irq_mode(sock) != 0) + state->io_irq = t->cap.pci_irq; + else +#endif + state->io_irq = reg & I365_IRQ_MASK; + + /* speaker control */ + if (t->flags & IS_CIRRUS) { + if (i365_get(sock, PD67_MISC_CTL_1) & PD67_MC1_SPKR_ENA) + state->flags |= SS_SPKR_ENA; + } + + /* Card status change mask */ + reg = i365_get(sock, I365_CSCINT); + state->csc_mask = (reg & I365_CSC_DETECT) ? SS_DETECT : 0; + if (state->flags & SS_IOCARD) + state->csc_mask |= (reg & I365_CSC_STSCHG) ? SS_STSCHG : 0; + else { + state->csc_mask |= (reg & I365_CSC_BVD1) ? SS_BATDEAD : 0; + state->csc_mask |= (reg & I365_CSC_BVD2) ? SS_BATWARN : 0; + state->csc_mask |= (reg & I365_CSC_READY) ? SS_READY : 0; + } + + DEBUG(1, "i82365: GetSocket(%d) = flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x\n", sock, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + return 0; +} /* i365_get_socket */ + +/*====================================================================*/ + +static int i365_set_socket(u_short sock, socket_state_t *state) +{ + socket_info_t *t = &socket[sock]; + u_char reg; + + DEBUG(1, "i82365: 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 */ +#ifdef CONFIG_PCI + if ((t->flags & IS_CARDBUS) && t->cap.pci_irq) + cb_set_irq_mode(sock, pci_csc, + (t->cap.pci_irq == state->io_irq)); + t->bcr &= ~CB_BCR_CB_RESET; +#endif + set_host_state(sock); + + /* IO card, RESET flag, IO interrupt */ + reg = t->intr; + if (state->io_irq != t->cap.pci_irq) 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; + +#ifdef CONFIG_PCI + if (t->flags & IS_CARDBUS) { + cb_set_power(sock, state); + reg |= i365_get(sock, I365_POWER) & + (I365_VCC_MASK|I365_VPP1_MASK); + } else +#endif + 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_get_io_map(u_short sock, struct pccard_io_map *io) +{ + u_char map, ioctl, addr; + + map = io->map; + if (map > 1) return -EINVAL; + io->start = i365_get_pair(sock, I365_IO(map)+I365_W_START); + io->stop = i365_get_pair(sock, I365_IO(map)+I365_W_STOP); + ioctl = i365_get(sock, I365_IOCTL); + addr = i365_get(sock, I365_ADDRWIN); + io->speed = to_ns(ioctl & I365_IOCTL_WAIT(map)) ? 1 : 0; + io->flags = (addr & I365_ENA_IO(map)) ? MAP_ACTIVE : 0; + io->flags |= (ioctl & I365_IOCTL_0WS(map)) ? MAP_0WS : 0; + io->flags |= (ioctl & I365_IOCTL_16BIT(map)) ? MAP_16BIT : 0; + io->flags |= (ioctl & I365_IOCTL_IOCS16(map)) ? MAP_AUTOSZ : 0; + DEBUG(1, "i82365: GetIOMap(%d, %d) = %#2.2x, %d ns, " + "%#4.4x-%#4.4x\n", sock, map, io->flags, io->speed, + io->start, io->stop); + return 0; +} /* i365_get_io_map */ + +/*====================================================================*/ + +static int i365_set_io_map(u_short sock, struct pccard_io_map *io) +{ + u_char map, ioctl; + + DEBUG(1, "i82365: SetIOMap(%d, %d, %#2.2x, %d ns, " + "%#4.4x-%#4.4x)\n", sock, io->map, io->flags, + io->speed, io->start, io->stop); + map = io->map; + if ((map > 1) || (io->start > 0xffff) || (io->stop > 0xffff) || + (io->stop < io->start)) return -EINVAL; + /* Turn off the window before changing anything */ + if (i365_get(sock, I365_ADDRWIN) & I365_ENA_IO(map)) + i365_bclr(sock, I365_ADDRWIN, I365_ENA_IO(map)); + i365_set_pair(sock, I365_IO(map)+I365_W_START, io->start); + i365_set_pair(sock, I365_IO(map)+I365_W_STOP, io->stop); + ioctl = i365_get(sock, I365_IOCTL) & ~I365_IOCTL_MASK(map); + if (io->speed) ioctl |= I365_IOCTL_WAIT(map); + if (io->flags & MAP_0WS) ioctl |= I365_IOCTL_0WS(map); + if (io->flags & MAP_16BIT) ioctl |= I365_IOCTL_16BIT(map); + if (io->flags & MAP_AUTOSZ) ioctl |= I365_IOCTL_IOCS16(map); + i365_set(sock, I365_IOCTL, ioctl); + /* Turn on the window if necessary */ + if (io->flags & MAP_ACTIVE) + i365_bset(sock, I365_ADDRWIN, I365_ENA_IO(map)); + return 0; +} /* i365_set_io_map */ + +/*====================================================================*/ + +static int i365_get_mem_map(u_short sock, struct pccard_mem_map *mem) +{ + u_short base, i; + u_char map, addr; + + map = mem->map; + if (map > 4) return -EINVAL; + addr = i365_get(sock, I365_ADDRWIN); + mem->flags = (addr & I365_ENA_MEM(map)) ? MAP_ACTIVE : 0; + base = I365_MEM(map); + + i = i365_get_pair(sock, base+I365_W_START); + mem->flags |= (i & I365_MEM_16BIT) ? MAP_16BIT : 0; + mem->flags |= (i & I365_MEM_0WS) ? MAP_0WS : 0; + mem->sys_start += ((u_long)(i & 0x0fff) << 12); + + i = i365_get_pair(sock, base+I365_W_STOP); + mem->speed = (i & I365_MEM_WS0) ? 1 : 0; + mem->speed += (i & I365_MEM_WS1) ? 2 : 0; + mem->speed = to_ns(mem->speed); + mem->sys_stop = ((u_long)(i & 0x0fff) << 12) + 0x0fff; + + i = i365_get_pair(sock, base+I365_W_OFF); + mem->flags |= (i & I365_MEM_WRPROT) ? MAP_WRPROT : 0; + mem->flags |= (i & I365_MEM_REG) ? MAP_ATTRIB : 0; + mem->card_start = ((u_int)(i & 0x3fff) << 12) + mem->sys_start; + mem->card_start &= 0x3ffffff; + +#ifdef CONFIG_PCI + /* Take care of high byte, for PCI controllers */ + if (socket[sock].type == IS_PD6729) { + i365_set(sock, PD67_EXT_INDEX, PD67_MEM_PAGE(map)); + addr = i365_get(sock, PD67_EXT_DATA) << 24; + } else if (socket[sock].flags & IS_CARDBUS) { + addr = i365_get(sock, CB_MEM_PAGE(map)) << 24; + mem->sys_stop += addr; mem->sys_start += addr; + } +#endif + + DEBUG(1, "i82365: GetMemMap(%d, %d) = %#2.2x, %d ns, %#5.5lx-%#5." + "5lx, %#5.5x\n", sock, mem->map, mem->flags, mem->speed, + mem->sys_start, mem->sys_stop, mem->card_start); + return 0; +} /* i365_get_mem_map */ + +/*====================================================================*/ + +static int i365_set_mem_map(u_short sock, struct pccard_mem_map *mem) +{ + u_short base, i; + u_char map; + + DEBUG(1, "i82365: SetMemMap(%d, %d, %#2.2x, %d ns, %#5.5lx-%#5.5" + "lx, %#5.5x)\n", sock, mem->map, mem->flags, mem->speed, + mem->sys_start, mem->sys_stop, mem->card_start); + + map = mem->map; + if ((map > 4) || (mem->card_start > 0x3ffffff) || + (mem->sys_start > mem->sys_stop) || (mem->speed > 1000)) + return -EINVAL; + if (!(socket[sock].flags & (IS_PCI | IS_CARDBUS)) && + ((mem->sys_start > 0xffffff) || (mem->sys_stop > 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)); + +#ifdef CONFIG_PCI + /* Take care of high byte, for PCI controllers */ + if (socket[sock].type == IS_PD6729) { + i365_set(sock, PD67_EXT_INDEX, PD67_MEM_PAGE(map)); + i365_set(sock, PD67_EXT_DATA, (mem->sys_start >> 24)); + } else if (socket[sock].flags & IS_CARDBUS) + i365_set(sock, CB_MEM_PAGE(map), mem->sys_start >> 24); +#endif + + base = I365_MEM(map); + i = (mem->sys_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->sys_stop >> 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->sys_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 */ + +/*====================================================================== + + Power control for Cardbus controllers: used both for 16-bit and + Cardbus cards. + +======================================================================*/ + +#ifdef CONFIG_PCI + +static void cb_get_power(u_short sock, socket_state_t *state) +{ + u_int reg = cb_readl(sock, CB_SOCKET_CONTROL); + state->Vcc = state->Vpp = 0; + switch (reg & CB_SC_VCC_MASK) { + case CB_SC_VCC_3V: state->Vcc = 33; break; + case CB_SC_VCC_5V: state->Vcc = 50; break; + } + switch (reg & CB_SC_VCC_MASK) { + case CB_SC_VPP_3V: state->Vpp = 33; break; + case CB_SC_VPP_5V: state->Vpp = 50; break; + case CB_SC_VPP_12V: state->Vpp = 120; break; + } +} + +static void cb_set_power(u_short sock, socket_state_t *state) +{ + u_int reg = 0; + 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(sock, CB_SOCKET_CONTROL)) + cb_writel(sock, CB_SOCKET_CONTROL, reg); +} + +#endif + +/*====================================================================== + + All the stuff that is strictly for Cardbus cards goes here. + +======================================================================*/ + +#ifdef CONFIG_CARDBUS + +static int cb_get_status(u_short sock, u_int *value) +{ + u_int s; + s = cb_readl(sock, CB_SOCKET_STATE); + *value = ((s & CB_SS_32BIT) ? SS_CARDBUS : 0); + *value |= ((s & CB_SS_CCD1) || (s & CB_SS_CCD2)) ? 0 : SS_DETECT; + *value |= (s & CB_SS_CSTSCHG) ? SS_STSCHG : 0; + *value |= (s & CB_SS_PWRCYCLE) ? (SS_POWERON|SS_READY) : 0; + *value |= (s & CB_SS_3VCARD) ? SS_3VCARD : 0; + *value |= (s & CB_SS_XVCARD) ? SS_XVCARD : 0; + DEBUG(1, "yenta: GetStatus(%d) = %#4.4x\n", sock, *value); + return 0; +} /* cb_get_status */ + +static int cb_get_socket(u_short sock, socket_state_t *state) +{ + socket_info_t *s = &socket[sock]; + u_short bcr; + + cb_get_power(sock, state); + pci_readw(s->bus, s->devfn, CB_BRIDGE_CONTROL, &bcr); + state->flags |= (bcr & CB_BCR_CB_RESET) ? SS_RESET : 0; + if (cb_get_irq_mode(sock) != 0) + state->io_irq = s->cap.pci_irq; + else + state->io_irq = i365_get(sock, I365_INTCTL) & I365_IRQ_MASK; + DEBUG(1, "yenta: GetSocket(%d) = flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x\n", sock, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + return 0; +} /* cb_get_socket */ + +static int cb_set_socket(u_short sock, socket_state_t *state) +{ + socket_info_t *s = &socket[sock]; + u_int reg; + + DEBUG(1, "yenta: 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 */ + if (s->cap.pci_irq) + cb_set_irq_mode(sock, pci_csc, + (s->cap.pci_irq == state->io_irq)); + s->bcr &= ~CB_BCR_CB_RESET; + s->bcr |= (state->flags & SS_RESET) ? CB_BCR_CB_RESET : 0; + set_host_state(sock); + + cb_set_power(sock, state); + + /* Handle IO interrupt using ISA routing */ + reg = i365_get(sock, I365_INTCTL) & ~I365_IRQ_MASK; + if (state->io_irq != s->cap.pci_irq) reg |= state->io_irq; + i365_set(sock, I365_INTCTL, reg); + + /* Handle CSC mask */ + reg = (socket[sock].cs_irq << 4); + if (state->csc_mask & SS_DETECT) reg |= I365_CSC_DETECT; + i365_set(sock, I365_CSCINT, reg); + i365_get(sock, I365_CSC); + + return 0; +} /* cb_set_socket */ + +static int cb_get_bridge(u_short sock, struct cb_bridge_map *m) +{ + socket_info_t *s = &socket[sock]; + u_char map; + + map = m->map; + if (map > 1) return -EINVAL; + m->flags &= MAP_IOSPACE; + map += (m->flags & MAP_IOSPACE) ? 2 : 0; + pci_readl(s->bus, s->devfn, CB_MEM_BASE(map), &m->start); + pci_readl(s->bus, s->devfn, CB_MEM_LIMIT(map), &m->stop); + if (m->start || m->stop) { + m->flags |= MAP_ACTIVE; + m->stop |= (map > 1) ? 3 : 0x0fff; + } + if (map > 1) { + u_short bcr; + pci_readw(s->bus, s->devfn, CB_BRIDGE_CONTROL, &bcr); + m->flags |= (bcr & CB_BCR_PREFETCH(map)) ? MAP_PREFETCH : 0; + } + DEBUG(1, "yenta: GetBridge(%d, %d) = %#2.2x, %#4.4x-%#4.4x\n", + sock, map, m->flags, m->start, m->stop); + return 0; +} + +static int cb_set_bridge(u_short sock, struct cb_bridge_map *m) +{ + socket_info_t *s = &socket[sock]; + u_char map; + + DEBUG(1, "yenta: SetBridge(%d, %d, %#2.2x, %#4.4x-%#4.4x)\n", + sock, m->map, m->flags, m->start, m->stop); + map = m->map; + if (!(s->flags & IS_CARDBUS) || (map > 1) || (m->stop < m->start)) + return -EINVAL; + if (m->flags & MAP_IOSPACE) { + if ((m->stop > 0xffff) || (m->start & 3) || + ((m->stop & 3) != 3)) + return -EINVAL; + map += 2; + } else { + u_short bcr; + if ((m->start & 0x0fff) || ((m->stop & 0x0fff) != 0x0fff)) + return -EINVAL; + pci_readw(s->bus, s->devfn, CB_BRIDGE_CONTROL, &bcr); + bcr &= ~CB_BCR_PREFETCH(map); + bcr |= (m->flags & MAP_PREFETCH) ? CB_BCR_PREFETCH(map) : 0; + pci_writew(s->bus, s->devfn, CB_BRIDGE_CONTROL, bcr); + } + if (m->flags & MAP_ACTIVE) { + pci_writel(s->bus, s->devfn, CB_MEM_BASE(map), m->start); + pci_writel(s->bus, s->devfn, CB_MEM_LIMIT(map), m->stop); + } else { + pci_writel(s->bus, s->devfn, CB_IO_BASE(map), 0); + pci_writel(s->bus, s->devfn, CB_IO_LIMIT(map), 0); + } + return 0; +} + +#endif /* CONFIG_CARDBUS */ + +/*====================================================================== + + Routines for accessing socket information and register dumps via + /proc/bus/pccard/... + +======================================================================*/ + +#ifdef CONFIG_PROC_FS + +static int proc_read_info(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + socket_info_t *s = data; + char *p = buf; + p += sprintf(p, "type: %s\npsock: %d\n", + pcic[s->type].name, s->psock); +#ifdef CONFIG_PCI + if (s->flags & (IS_PCI|IS_CARDBUS)) + p += sprintf(p, "bus: %02x\ndevfn: %02x.%1x\n", + s->bus, PCI_SLOT(s->devfn), PCI_FUNC(s->devfn)); + if (s->flags & IS_CARDBUS) + p += sprintf(p, "cardbus: %02x\n", s->cap.cardbus); +#endif + return (p - buf); +} + +static int proc_read_exca(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + u_short sock = (socket_info_t *)data - socket; + char *p = buf; + int i, top; + +#ifdef CONFIG_ISA + u_long flags = 0; + if (!(socket[sock].flags & IS_CARDBUS)) + spin_lock_irqsave(&isa_lock, flags); +#endif + top = 0x40; + if (socket[sock].flags & IS_CARDBUS) + top = (socket[sock].flags & IS_CIRRUS) ? 0x140 : 0x50; + for (i = 0; i < top; i += 4) { + if (i == 0x50) { + p += sprintf(p, "\n"); + i = 0x100; + } + p += sprintf(p, "%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" : " "); + } +#ifdef CONFIG_ISA + if (!(socket[sock].flags & IS_CARDBUS)) + spin_unlock_irqrestore(&isa_lock, flags); +#endif + return (p - buf); +} + +#ifdef CONFIG_PCI +static int proc_read_pci(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + socket_info_t *s = data; + u_char bus = s->bus, devfn = s->devfn; + char *p = buf; + u_int a, b, c, d; + int i; + + for (i = 0; i < 0xc0; i += 0x10) { + pci_readl(bus, devfn, i, &a); + pci_readl(bus, devfn, i+4, &b); + pci_readl(bus, devfn, i+8, &c); + pci_readl(bus, devfn, i+12, &d); + p += sprintf(p, "%08x %08x %08x %08x\n", a, b, c, d); + } + return (p - buf); +} +#endif + +#ifdef CONFIG_CARDBUS +static int proc_read_cardbus(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + u_short sock = (socket_info_t *)data - socket; + int len; + + len = sprintf(buf, "%08x %08x %08x %08x %08x %08x\n", + cb_readl(sock,0), cb_readl(sock,4), + cb_readl(sock,8), cb_readl(sock,12), + cb_readl(sock,16), cb_readl(sock,32)); + return len; +} +#endif + +static void pcic_proc_setup(u_short sock, struct proc_dir_entry *base) +{ + socket_info_t *s = &socket[sock]; + struct proc_dir_entry *ent; + ent = create_proc_entry("info", 0, base); + ent->read_proc = proc_read_info; + ent->data = s; + ent = create_proc_entry("exca", 0, base); + ent->read_proc = proc_read_exca; + ent->data = s; +#ifdef CONFIG_PCI + if (s->flags & (IS_PCI|IS_CARDBUS)) { + ent = create_proc_entry("pci", 0, base); + ent->read_proc = proc_read_pci; + ent->data = s; + } +#endif +#ifdef CONFIG_CARDBUS + if (s->flags & IS_CARDBUS) { + ent = create_proc_entry("cardbus", 0, base); + ent->read_proc = proc_read_cardbus; + ent->data = s; + } +#endif + s->proc = base; +} + +static void pcic_proc_remove(u_short sock) +{ + struct proc_dir_entry *base = socket[sock].proc; + if (base == NULL) return; + remove_proc_entry("info", base); + remove_proc_entry("exca", base); +#ifdef CONFIG_PCI + if (socket[sock].flags & (IS_PCI|IS_CARDBUS)) + remove_proc_entry("pci", base); +#endif +#ifdef CONFIG_CARDBUS + if (socket[sock].flags & IS_CARDBUS) + remove_proc_entry("cardbus", base); +#endif +} + +#endif /* CONFIG_PROC_FS */ + +/*====================================================================*/ + +typedef int (*subfn_t)(u_short, void *); + +static subfn_t pcic_service_table[] = { + (subfn_t)&pcic_register_callback, + (subfn_t)&pcic_inquire_socket, + (subfn_t)&i365_get_status, + (subfn_t)&i365_get_socket, + (subfn_t)&i365_set_socket, + (subfn_t)&i365_get_io_map, + (subfn_t)&i365_set_io_map, + (subfn_t)&i365_get_mem_map, + (subfn_t)&i365_set_mem_map, +#ifdef CONFIG_CARDBUS + (subfn_t)&cb_get_bridge, + (subfn_t)&cb_set_bridge, +#else + NULL, NULL, +#endif +#ifdef CONFIG_PROC_FS + (subfn_t)&pcic_proc_setup +#endif +}; + +#define NFUNC (sizeof(pcic_service_table)/sizeof(subfn_t)) + +static int pcic_service(u_int sock, u_int cmd, void *arg) +{ + subfn_t fn; + + DEBUG(2, "pcic_ioctl(%d, %d, 0x%p)\n", sock, cmd, arg); + + if (cmd >= NFUNC) + return -EINVAL; + + if (socket[sock].flags & IS_ALIVE) { + if (cmd == SS_GetStatus) + *(u_int *)arg = 0; + return -EINVAL; + } + + fn = pcic_service_table[cmd]; +#ifdef CONFIG_CARDBUS + if ((socket[sock].flags & IS_CARDBUS) && + (cb_readl(sock, CB_SOCKET_STATE) & CB_SS_32BIT)) { + if (cmd == SS_GetStatus) + fn = (subfn_t)&cb_get_status; + else if (cmd == SS_GetSocket) + fn = (subfn_t)&cb_get_socket; + else if (cmd == SS_SetSocket) + fn = (subfn_t)&cb_set_socket; + } +#endif + +#ifdef CONFIG_ISA + if (!(socket[sock].flags & IS_CARDBUS)) { + int ret; + u_long flags; + spin_lock_irqsave(&isa_lock, flags); + ret = (fn == NULL) ? -EINVAL : fn(sock, arg); + spin_unlock_irqrestore(&isa_lock, flags); + return ret; + } +#endif + return (fn == NULL) ? -EINVAL : fn(sock, arg); +} /* pcic_service */ + +/*====================================================================*/ + +module_init(init_i82365); +module_exit(exit_i82365); + +/*====================================================================*/ + diff --git a/drivers/pcmcia/i82365.h b/drivers/pcmcia/i82365.h new file mode 100644 index 000000000..ac3ba4e87 --- /dev/null +++ b/drivers/pcmcia/i82365.h @@ -0,0 +1,135 @@ +/* + * i82365.h 1.14 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 + * <dhinds@hyper.stanford.edu>. 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 Public License version 2 (the "GPL"), in which + * case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_I82365_H +#define _LINUX_I82365_H + +/* register definitions for the Intel 82365SL PCMCIA controller */ + +/* Offsets for PCIC registers */ +#define I365_IDENT 0x00 /* Identification and revision */ +#define I365_STATUS 0x01 /* Interface status */ +#define I365_POWER 0x02 /* Power and RESETDRV control */ +#define I365_INTCTL 0x03 /* Interrupt and general control */ +#define I365_CSC 0x04 /* Card status change */ +#define I365_CSCINT 0x05 /* Card status change interrupt control */ +#define I365_ADDRWIN 0x06 /* Address window enable */ +#define I365_IOCTL 0x07 /* I/O control */ +#define I365_GENCTL 0x16 /* Card detect and general control */ +#define I365_GBLCTL 0x1E /* Global control register */ + +/* Offsets for I/O and memory window registers */ +#define I365_IO(map) (0x08+((map)<<2)) +#define I365_MEM(map) (0x10+((map)<<3)) +#define I365_W_START 0 +#define I365_W_STOP 2 +#define I365_W_OFF 4 + +/* Flags for I365_STATUS */ +#define I365_CS_BVD1 0x01 +#define I365_CS_STSCHG 0x01 +#define I365_CS_BVD2 0x02 +#define I365_CS_SPKR 0x02 +#define I365_CS_DETECT 0x0C +#define I365_CS_WRPROT 0x10 +#define I365_CS_READY 0x20 /* Inverted */ +#define I365_CS_POWERON 0x40 +#define I365_CS_GPI 0x80 + +/* Flags for I365_POWER */ +#define I365_PWR_OFF 0x00 /* Turn off the socket */ +#define I365_PWR_OUT 0x80 /* Output enable */ +#define I365_PWR_NORESET 0x40 /* Disable RESETDRV on resume */ +#define I365_PWR_AUTO 0x20 /* Auto pwr switch enable */ +#define I365_VCC_MASK 0x18 /* Mask for turning off Vcc */ +/* There are different layouts for B-step and DF-step chips: the B + step has independent Vpp1/Vpp2 control, and the DF step has only + Vpp1 control, plus 3V control */ +#define I365_VCC_5V 0x10 /* Vcc = 5.0v */ +#define I365_VCC_3V 0x18 /* Vcc = 3.3v */ +#define I365_VPP2_MASK 0x0c /* Mask for turning off Vpp2 */ +#define I365_VPP2_5V 0x04 /* Vpp2 = 5.0v */ +#define I365_VPP2_12V 0x08 /* Vpp2 = 12.0v */ +#define I365_VPP1_MASK 0x03 /* Mask for turning off Vpp1 */ +#define I365_VPP1_5V 0x01 /* Vpp2 = 5.0v */ +#define I365_VPP1_12V 0x02 /* Vpp2 = 12.0v */ + +/* Flags for I365_INTCTL */ +#define I365_RING_ENA 0x80 +#define I365_PC_RESET 0x40 +#define I365_PC_IOCARD 0x20 +#define I365_INTR_ENA 0x10 +#define I365_IRQ_MASK 0x0F + +/* Flags for I365_CSC and I365_CSCINT*/ +#define I365_CSC_BVD1 0x01 +#define I365_CSC_STSCHG 0x01 +#define I365_CSC_BVD2 0x02 +#define I365_CSC_READY 0x04 +#define I365_CSC_DETECT 0x08 +#define I365_CSC_ANY 0x0F +#define I365_CSC_GPI 0x10 + +/* Flags for I365_ADDRWIN */ +#define I365_ENA_IO(map) (0x40 << (map)) +#define I365_ENA_MEM(map) (0x01 << (map)) + +/* Flags for I365_IOCTL */ +#define I365_IOCTL_MASK(map) (0x0F << (map<<2)) +#define I365_IOCTL_WAIT(map) (0x08 << (map<<2)) +#define I365_IOCTL_0WS(map) (0x04 << (map<<2)) +#define I365_IOCTL_IOCS16(map) (0x02 << (map<<2)) +#define I365_IOCTL_16BIT(map) (0x01 << (map<<2)) + +/* Flags for I365_GENCTL */ +#define I365_CTL_16DELAY 0x01 +#define I365_CTL_RESET 0x02 +#define I365_CTL_GPI_ENA 0x04 +#define I365_CTL_GPI_CTL 0x08 +#define I365_CTL_RESUME 0x10 +#define I365_CTL_SW_IRQ 0x20 + +/* Flags for I365_GBLCTL */ +#define I365_GBL_PWRDOWN 0x01 +#define I365_GBL_CSC_LEV 0x02 +#define I365_GBL_WRBACK 0x04 +#define I365_GBL_IRQ_0_LEV 0x08 +#define I365_GBL_IRQ_1_LEV 0x10 + +/* Flags for memory window registers */ +#define I365_MEM_16BIT 0x8000 /* In memory start high byte */ +#define I365_MEM_0WS 0x4000 +#define I365_MEM_WS1 0x8000 /* In memory stop high byte */ +#define I365_MEM_WS0 0x4000 +#define I365_MEM_WRPROT 0x8000 /* In offset high byte */ +#define I365_MEM_REG 0x4000 + +#define I365_REG(slot, reg) (((slot) << 6) + reg) + +#endif /* _LINUX_I82365_H */ diff --git a/drivers/pcmcia/o2micro.h b/drivers/pcmcia/o2micro.h new file mode 100644 index 000000000..89cb6cf01 --- /dev/null +++ b/drivers/pcmcia/o2micro.h @@ -0,0 +1,98 @@ +/* + * o2micro.h 1.10 1999/09/03 16:43:35 + * + * 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 + * <dhinds@hyper.stanford.edu>. 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 Public License version 2 (the "GPL"), in which + * case the provisions of the GPL are applicable instead of the + * above. If you wish to allow the use of your version of this file + * only under the terms of the GPL and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL. If you do not delete the + * provisions above, a recipient may use your version of this file + * under either the MPL or the GPL. + */ + +#ifndef _LINUX_O2MICRO_H +#define _LINUX_O2MICRO_H + +#ifndef PCI_VENDOR_ID_O2 +#define PCI_VENDOR_ID_O2 0x1217 +#endif +#ifndef PCI_DEVICE_ID_O2_6729 +#define PCI_DEVICE_ID_O2_6729 0x6729 +#endif +#ifndef PCI_DEVICE_ID_O2_6730 +#define PCI_DEVICE_ID_O2_6730 0x673a +#endif +#ifndef PCI_DEVICE_ID_O2_6832 +#define PCI_DEVICE_ID_O2_6832 0x6832 +#endif +#ifndef PCI_DEVICE_ID_O2_6836 +#define PCI_DEVICE_ID_O2_6836 0x6836 +#endif + +#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_PWRCHIP 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_D 0x3b +#define O2_MODE_D_IRQ_MODE 0x03 +#define O2_MODE_D_SKT_ACTV 0x20 +#define O2_MODE_D_PCI_FIFO 0x40 /* for OZ6729, OZ6730 */ +#define O2_MODE_D_W97_IRQ 0x40 /* for OZ6832 */ +#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 + +#endif /* _LINUX_O2MICRO_H */ diff --git a/drivers/pcmcia/ricoh.h b/drivers/pcmcia/ricoh.h new file mode 100644 index 000000000..c6fceac2e --- /dev/null +++ b/drivers/pcmcia/ricoh.h @@ -0,0 +1,132 @@ +/* + * ricoh.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 + * <dhinds@hyper.stanford.edu>. 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 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 */ + +#ifndef PCI_VENDOR_ID_RICOH +#define PCI_VENDOR_ID_RICOH 0x1180 +#endif +#ifndef PCI_DEVICE_ID_RICOH_RL5C465 +#define PCI_DEVICE_ID_RICOH_RL5C465 0x0465 +#endif +#ifndef PCI_DEVICE_ID_RICOH_RL5C466 +#define PCI_DEVICE_ID_RICOH_RL5C466 0x0466 +#endif +#ifndef PCI_DEVICE_ID_RICOH_RL5C475 +#define PCI_DEVICE_ID_RICOH_RL5C475 0x0475 +#endif +#ifndef PCI_DEVICE_ID_RICOH_RL5C476 +#define PCI_DEVICE_ID_RICOH_RL5C476 0x0476 +#endif +#ifndef PCI_DEVICE_ID_RICOH_RL5C478 +#define PCI_DEVICE_ID_RICOH_RL5C478 0x0478 +#endif + +/* Extra bits in CB_BRIDGE_CONTROL */ +#define RL5C46X_BCR_3E0_ENA 0x0800 +#define RL5C46X_BCR_3E2_ENA 0x1000 + +/* 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 0x0088 /* 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 + +#endif /* _LINUX_RICOH_H */ diff --git a/drivers/pcmcia/rsrc_mgr.c b/drivers/pcmcia/rsrc_mgr.c new file mode 100644 index 000000000..52813cb5a --- /dev/null +++ b/drivers/pcmcia/rsrc_mgr.c @@ -0,0 +1,790 @@ +/*====================================================================== + + Resource management routines + + rsrc_mgr.c 1.71 1999/09/15 15:32:19 + + 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 + <dhinds@hyper.stanford.edu>. 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 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. + +======================================================================*/ + +#define __NO_VERSION__ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/malloc.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <asm/irq.h> +#include <asm/io.h> + +#include <pcmcia/cs_types.h> +#include <pcmcia/ss.h> +#include <pcmcia/cs.h> +#include <pcmcia/bulkmem.h> +#include <pcmcia/cistpl.h> +#include "cs_internal.h" +#include "rsrc_mgr.h" + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +/* Should we probe resources for conflicts? */ +static int probe_mem = 1; +MODULE_PARM(probe_mem, "i"); +#ifdef CONFIG_ISA +static int probe_io = 1; +static int mem_limit = 0x10000; +MODULE_PARM(probe_io, "i"); +MODULE_PARM(mem_limit, "i"); +#endif + +/*====================================================================== + + The resource_map_t structures are used to track what resources are + available for allocation for PC Card devices. + +======================================================================*/ + +typedef struct resource_map_t { + u_long base, num; + struct resource_map_t *next; +} resource_map_t; + +/* Memory resource database */ +static resource_map_t mem_db = { 0, 0, &mem_db }; + +/* IO port resource database */ +static resource_map_t io_db = { 0, 0, &io_db }; + +#ifdef CONFIG_ISA + +typedef struct irq_info_t { + u_int Attributes; + int time_share, dyn_share; + struct socket_info_t *Socket; +} irq_info_t; + +/* Table of IRQ assignments */ +static irq_info_t irq_table[NR_IRQS] = { { 0, 0, 0 }, /* etc */ }; + +#endif + +static spinlock_t rsrc_lock = SPIN_LOCK_UNLOCKED; + +/*====================================================================== + + Linux resource management extensions + +======================================================================*/ + +typedef struct resource_entry_t { + u_long base, num; + char *name; + struct resource_entry_t *next; +} resource_entry_t; + +/* Ordered linked lists of allocated IO and memory blocks */ +static resource_entry_t io_list = { 0, 0, NULL, NULL }; + +static resource_entry_t *find_gap(resource_entry_t *root, + resource_entry_t *entry) +{ + resource_entry_t *p; + + if (entry->base > entry->base+entry->num-1) + return NULL; + for (p = root; ; p = p->next) { + if ((p != root) && (p->base+p->num-1 >= entry->base)) { + p = NULL; + break; + } + if ((p->next == NULL) || + (p->next->base > entry->base+entry->num-1)) + break; + } + return p; +} + +static int register_my_resource(resource_entry_t *list, + u_long base, u_long num, char *name) +{ + u_long flags; + resource_entry_t *p, *entry; + + entry = kmalloc(sizeof(resource_entry_t), GFP_ATOMIC); + entry->base = base; + entry->num = num; + entry->name = name; + + spin_lock_irqsave(&rsrc_lock, flags); + p = find_gap(list, entry); + if (p == NULL) { + spin_unlock_irqrestore(&rsrc_lock, flags); + kfree(entry); + return -EBUSY; + } + entry->next = p->next; + p->next = entry; + spin_unlock_irqrestore(&rsrc_lock, flags); + return 0; +} + +static void release_my_resource(resource_entry_t *list, + u_long base, u_long num) +{ + u_long flags; + resource_entry_t *p, *q; + + spin_lock_irqsave(&rsrc_lock, flags); + for (p = list; ; p = q) { + q = p->next; + if (q == NULL) break; + if ((q->base == base) && (q->num == num)) { + p->next = q->next; + kfree(q); + spin_unlock_irqrestore(&rsrc_lock, flags); + return; + } + } + spin_unlock_irqrestore(&rsrc_lock, flags); + return; +} + +static int check_my_resource(resource_entry_t *list, + u_long base, u_long num) +{ + if (register_my_resource(list, base, num, NULL) != 0) + return -EBUSY; + release_my_resource(list, base, num); + return 0; +} + +int check_io_region(u_long base, u_long num) +{ + return check_my_resource(&io_list, base, num); +} +void request_io_region(u_long base, u_long num, char *name) +{ + register_my_resource(&io_list, base, num, name); +} +void release_io_region(u_long base, u_long num) +{ + release_my_resource(&io_list, base, num); +} +#ifdef CONFIG_PROC_FS +int proc_read_io(char *buf, char **start, off_t pos, + int count, int *eof, void *data) +{ + resource_entry_t *r; + u_long flags; + char *p = buf; + + spin_lock_irqsave(&rsrc_lock, flags); + for (r = io_list.next; r; r = r->next) + p += sprintf(p, "%04lx-%04lx : %s\n", r->base, + r->base+r->num-1, r->name); + spin_unlock_irqrestore(&rsrc_lock, flags); + return (p - buf); +} +#endif + +/*====================================================================== + + These manage the internal databases of available resources. + +======================================================================*/ + +static int add_interval(resource_map_t *map, u_long base, u_long num) +{ + resource_map_t *p, *q; + + for (p = map; ; p = p->next) { + if ((p != map) && (p->base+p->num-1 >= base)) + return -1; + if ((p->next == map) || (p->next->base > base+num-1)) + break; + } + q = kmalloc(sizeof(resource_map_t), GFP_KERNEL); + q->base = base; q->num = num; + q->next = p->next; p->next = q; + return 0; +} /* add_interval */ + +/*====================================================================*/ + +static int sub_interval(resource_map_t *map, u_long base, u_long num) +{ + resource_map_t *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(resource_map_t), GFP_KERNEL); + 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; +} /* sub_interval */ + +/*====================================================================== + + These routines examine a region of IO or memory addresses to + determine what ranges might be genuinely available. + +======================================================================*/ + +#ifdef CONFIG_ISA +static void do_io_probe(ioaddr_t base, ioaddr_t num) +{ + + ioaddr_t i, j, bad, any; + u_char *b, hole, most; + + printk(KERN_INFO "cs: IO port probe 0x%04x-0x%04x:", + base, base+num-1); + + /* First, what does a floating port look like? */ + b = kmalloc(256, GFP_KERNEL); + memset(b, 0, 256); + for (i = base, most = 0; i < base+num; i += 8) { + if (check_region(i, 8) || check_io_region(i, 8)) + continue; + hole = inb(i); + for (j = 1; j < 8; j++) + if (inb(i+j) != hole) break; + 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) { + if (check_region(i, 8) || check_io_region(i, 8)) + continue; + for (j = 0; j < 8; j++) + if (inb(i+j) != most) break; + if (j < 8) { + if (!any) + printk(" excluding"); + if (!bad) + bad = any = i; + } else { + if (bad) { + sub_interval(&io_db, bad, i-bad); + printk(" %#04x-%#04x", bad, i-1); + bad = 0; + } + } + } + if (bad) { + if ((num > 16) && (bad == base) && (i == base+num)) { + printk(" nothing: probe failed.\n"); + return; + } else { + sub_interval(&io_db, bad, i-bad); + printk(" %#04x-%#04x", bad, i-1); + } + } + + printk(any ? "\n" : " clean.\n"); +} +#endif + +/*====================================================================== + + The memory probe. If the memory list includes a 64K-aligned block + below 1MB, we probe in 64K chunks, and as soon as we accumulate at + least mem_limit free space, we quit. + +======================================================================*/ + +static int do_mem_probe(u_long base, u_long num, + int (*is_valid)(u_long), int (*do_cksum)(u_long)) +{ + u_long i, j, bad, fail, step; + + printk(KERN_INFO "cs: memory probe 0x%06lx-0x%06lx:", + base, base+num-1); + bad = fail = 0; + step = (num < 0x20000) ? 0x2000 : ((num>>4) & ~0x1fff); + for (i = base; i < base+num; i = j + step) { + if (!fail) { + for (j = i; j < base+num; j += step) + if ((check_mem_region(j, step) == 0) && is_valid(j)) + break; + fail = ((i == base) && (j == base+num)); + } + if (fail) { + for (j = i; j < base+num; j += 2*step) + if ((check_mem_region(j, 2*step) == 0) && + do_cksum(j) && do_cksum(j+step)) + break; + } + if (i != j) { + if (!bad) printk(" excluding"); + printk(" %#05lx-%#05lx", i, j-1); + sub_interval(&mem_db, i, j-i); + bad += j-i; + } + } + printk(bad ? "\n" : " clean.\n"); + return (num - bad); +} + +#ifdef CONFIG_ISA + +static u_long inv_probe(int (*is_valid)(u_long), + int (*do_cksum)(u_long), + resource_map_t *m) +{ + u_long ok; + if (m == &mem_db) + return 0; + ok = inv_probe(is_valid, do_cksum, m->next); + if (ok) { + if (m->base >= 0x100000) + sub_interval(&mem_db, m->base, m->num); + return ok; + } + if (m->base < 0x100000) + return 0; + return do_mem_probe(m->base, m->num, is_valid, do_cksum); +} + +void validate_mem(int (*is_valid)(u_long), int (*do_cksum)(u_long), + int force_low) +{ + resource_map_t *m, *n; + static u_char order[] = { 0xd0, 0xe0, 0xc0, 0xf0 }; + static int hi = 0, lo = 0; + u_long b, i, ok = 0; + + if (!probe_mem) return; + /* We do up to four passes through the list */ + if (!force_low) { + if (hi++ || (inv_probe(is_valid, do_cksum, mem_db.next) > 0)) + return; + printk(KERN_NOTICE "cs: warning: no high memory space " + "available!\n"); + } + if (lo++) return; + for (m = mem_db.next; m != &mem_db; m = n) { + n = m->next; + /* Only probe < 1 MB */ + if (m->base >= 0x100000) continue; + if ((m->base | m->num) & 0xffff) { + ok += do_mem_probe(m->base, m->num, is_valid, do_cksum); + continue; + } + /* Special probe for 64K-aligned block */ + for (i = 0; i < 4; i++) { + b = order[i] << 12; + if ((b >= m->base) && (b+0x10000 <= m->base+m->num)) { + if (ok >= mem_limit) + sub_interval(&mem_db, b, 0x10000); + else + ok += do_mem_probe(b, 0x10000, is_valid, do_cksum); + } + } + } +} + +#else /* CONFIG_ISA */ + +void validate_mem(int (*is_valid)(u_long), int (*do_cksum)(u_long), + int force_low) +{ + resource_map_t *m; + static int done = 0; + + if (!probe_mem || done++) + return; + for (m = mem_db.next; m != &mem_db; m = m->next) + if (do_mem_probe(m->base, m->num, is_valid, do_cksum)) + return; +} + +#endif /* CONFIG_ISA */ + +/*====================================================================== + + These find ranges of I/O ports or memory addresses that are not + currently allocated by other devices. + +======================================================================*/ + +int find_io_region(ioaddr_t *base, ioaddr_t num, char *name) +{ + ioaddr_t align; + resource_map_t *m; + + if (*base != 0) { + for (m = io_db.next; m != &io_db; m = m->next) { + if ((*base >= m->base) && (*base+num <= m->base+m->num)) { + if (check_region(*base, num) || + check_io_region(*base, num)) { + return -1; + } else { + request_region(*base, num, name); + return 0; + } + } + } + return -1; + } + + for (align = 1; align < num; align *= 2) ; + for (m = io_db.next; m != &io_db; m = m->next) { + for (*base = (m->base + align - 1) & (~(align-1)); + *base+align <= m->base + m->num; + *base += align) + if ((check_region(*base, num) == 0) && + (check_io_region(*base, num) == 0)) { + request_region(*base, num, name); + return 0; + } + } + return -1; +} /* find_io_region */ + +int find_mem_region(u_long *base, u_long num, char *name, + u_long align, int force_low) +{ + resource_map_t *m; + + if (*base != 0) { + for (m = mem_db.next; m != &mem_db; m = m->next) { + if ((*base >= m->base) && (*base+num <= m->base+m->num)) + if (check_mem_region(*base, num) == 0) { + request_mem_region(*base, num, name); + return 0; + } + } + return -1; + } + + while (1) { + for (m = mem_db.next; m != &mem_db; m = m->next) { + /* first pass >1MB, second pass <1MB */ + if ((force_low != 0) ^ (m->base < 0x100000)) continue; + for (*base = (m->base + align - 1) & (~(align-1)); + *base+num <= m->base+m->num; *base += align) + if (check_mem_region(*base, num) == 0) { + request_mem_region(*base, num, name); + return 0; + } + } + if (force_low) break; + force_low++; + } + return -1; +} /* find_mem_region */ + +/*====================================================================== + + This checks to see if an interrupt is available, with support + for interrupt sharing. We don't support reserving interrupts + yet. If the interrupt is available, we allocate it. + +======================================================================*/ + +#ifdef CONFIG_ISA + +static void fake_irq(int i, void *d, struct pt_regs *r) { } +static inline int check_irq(int irq) +{ + if (request_irq(irq, fake_irq, 0, "bogus", NULL) != 0) + return -1; + free_irq(irq, NULL); + return 0; +} + +int try_irq(u_int Attributes, int irq, int specific) +{ + irq_info_t *info = &irq_table[irq]; + if (info->Attributes & RES_ALLOCATED) { + switch (Attributes & IRQ_TYPE) { + case IRQ_TYPE_EXCLUSIVE: + return CS_IN_USE; + case IRQ_TYPE_TIME: + if ((info->Attributes & RES_IRQ_TYPE) + != RES_IRQ_TYPE_TIME) + return CS_IN_USE; + if (Attributes & IRQ_FIRST_SHARED) + return CS_BAD_ATTRIBUTE; + info->Attributes |= RES_IRQ_TYPE_TIME | RES_ALLOCATED; + info->time_share++; + break; + case IRQ_TYPE_DYNAMIC_SHARING: + if ((info->Attributes & RES_IRQ_TYPE) + != RES_IRQ_TYPE_DYNAMIC) + return CS_IN_USE; + if (Attributes & IRQ_FIRST_SHARED) + return CS_BAD_ATTRIBUTE; + info->Attributes |= RES_IRQ_TYPE_DYNAMIC | RES_ALLOCATED; + info->dyn_share++; + break; + } + } else { + if ((info->Attributes & RES_RESERVED) && !specific) + return CS_IN_USE; + if (check_irq(irq) != 0) + return CS_IN_USE; + switch (Attributes & IRQ_TYPE) { + case IRQ_TYPE_EXCLUSIVE: + info->Attributes |= RES_ALLOCATED; + break; + case IRQ_TYPE_TIME: + if (!(Attributes & IRQ_FIRST_SHARED)) + return CS_BAD_ATTRIBUTE; + info->Attributes |= RES_IRQ_TYPE_TIME | RES_ALLOCATED; + info->time_share = 1; + break; + case IRQ_TYPE_DYNAMIC_SHARING: + if (!(Attributes & IRQ_FIRST_SHARED)) + return CS_BAD_ATTRIBUTE; + info->Attributes |= RES_IRQ_TYPE_DYNAMIC | RES_ALLOCATED; + info->dyn_share = 1; + break; + } + } + return 0; +} /* try_irq */ + +#endif + +/*====================================================================*/ + +#ifdef CONFIG_ISA + +void undo_irq(u_int Attributes, int irq) +{ + irq_info_t *info; + + info = &irq_table[irq]; + switch (Attributes & IRQ_TYPE) { + case IRQ_TYPE_EXCLUSIVE: + info->Attributes &= RES_RESERVED; + break; + case IRQ_TYPE_TIME: + info->time_share--; + if (info->time_share == 0) + info->Attributes &= RES_RESERVED; + break; + case IRQ_TYPE_DYNAMIC_SHARING: + info->dyn_share--; + if (info->dyn_share == 0) + info->Attributes &= RES_RESERVED; + break; + } +} + +#endif + +/*====================================================================== + + The various adjust_* calls form the external interface to the + resource database. + +======================================================================*/ + +static int adjust_memory(adjust_t *adj) +{ + u_long base, num; + int i; + + base = adj->resource.memory.Base; + num = adj->resource.memory.Size; + if ((num == 0) || (base+num-1 < base)) + return CS_BAD_SIZE; + + switch (adj->Action) { + case ADD_MANAGED_RESOURCE: + if (add_interval(&mem_db, base, num) != 0) + return CS_IN_USE; + break; + case REMOVE_MANAGED_RESOURCE: + sub_interval(&mem_db, base, num); + for (i = 0; i < sockets; i++) { + release_cis_mem(socket_table[i]); +#ifdef CONFIG_CARDBUS + cb_release_cis_mem(socket_table[i]); +#endif + } + break; + default: + return CS_UNSUPPORTED_FUNCTION; + break; + } + + return CS_SUCCESS; +} /* adjust_mem */ + +/*====================================================================*/ + +static int adjust_io(adjust_t *adj) +{ + int base, num; + + base = adj->resource.io.BasePort; + num = adj->resource.io.NumPorts; + if ((base < 0) || (base > 0xffff)) + return CS_BAD_BASE; + if ((num <= 0) || (base+num > 0x10000) || (base+num <= base)) + return CS_BAD_SIZE; + + switch (adj->Action) { + case ADD_MANAGED_RESOURCE: + if (add_interval(&io_db, base, num) != 0) + return CS_IN_USE; +#ifdef CONFIG_ISA + if (probe_io) + do_io_probe(base, num); +#endif + break; + case REMOVE_MANAGED_RESOURCE: + sub_interval(&io_db, base, num); + break; + default: + return CS_UNSUPPORTED_FUNCTION; + break; + } + + return CS_SUCCESS; +} /* adjust_io */ + +/*====================================================================*/ + +static int adjust_irq(adjust_t *adj) +{ +#ifdef CONFIG_ISA + int irq; + irq_info_t *info; + + irq = adj->resource.irq.IRQ; + if ((irq < 0) || (irq > 15)) + return CS_BAD_IRQ; + info = &irq_table[irq]; + + switch (adj->Action) { + case ADD_MANAGED_RESOURCE: + if (info->Attributes & RES_REMOVED) + info->Attributes &= ~(RES_REMOVED|RES_ALLOCATED); + else + if (adj->Attributes & RES_ALLOCATED) + return CS_IN_USE; + if (adj->Attributes & RES_RESERVED) + info->Attributes |= RES_RESERVED; + else + info->Attributes &= ~RES_RESERVED; + break; + case REMOVE_MANAGED_RESOURCE: + if (info->Attributes & RES_REMOVED) + return 0; + if (info->Attributes & RES_ALLOCATED) + return CS_IN_USE; + info->Attributes |= RES_ALLOCATED|RES_REMOVED; + info->Attributes &= ~RES_RESERVED; + break; + default: + return CS_UNSUPPORTED_FUNCTION; + break; + } +#endif + return CS_SUCCESS; +} /* adjust_irq */ + +/*====================================================================*/ + +int adjust_resource_info(client_handle_t handle, adjust_t *adj) +{ + if (CHECK_HANDLE(handle)) + return CS_BAD_HANDLE; + + switch (adj->Resource) { + case RES_MEMORY_RANGE: + return adjust_memory(adj); + break; + case RES_IO_RANGE: + return adjust_io(adj); + break; + case RES_IRQ: + return adjust_irq(adj); + break; + } + return CS_UNSUPPORTED_FUNCTION; +} /* adjust_resource_info */ + +/*====================================================================*/ + +void release_resource_db(void) +{ + resource_map_t *p, *q; + resource_entry_t *u, *v; + + for (p = mem_db.next; p != &mem_db; p = q) { + q = p->next; + kfree(p); + } + for (p = io_db.next; p != &io_db; p = q) { + q = p->next; + kfree(p); + } + for (u = io_list.next; u; u = v) { + v = u->next; + kfree(u); + } +} diff --git a/drivers/pcmcia/rsrc_mgr.h b/drivers/pcmcia/rsrc_mgr.h new file mode 100644 index 000000000..3b7f12e09 --- /dev/null +++ b/drivers/pcmcia/rsrc_mgr.h @@ -0,0 +1,39 @@ +/* + * rsrc_mgr.h 1.18 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 + * <dhinds@hyper.stanford.edu>. 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 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 _RSRC_MGR_H +#define _RSRC_MGR_H + +#ifdef __BEOS__ +int check_resource(int type, u_long base, u_long num); +int register_resource(int type, u_long base, u_long num); +int release_resource(int type, u_long base, u_long num); +#endif + +#endif /* _RSRC_MGR_H */ diff --git a/drivers/pcmcia/smc34c90.h b/drivers/pcmcia/smc34c90.h new file mode 100644 index 000000000..8ac3854a4 --- /dev/null +++ b/drivers/pcmcia/smc34c90.h @@ -0,0 +1,52 @@ +/* + * smc34c90.h 1.6 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 + * <dhinds@hyper.stanford.edu>. 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 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_SMC34C90_H +#define _LINUX_SMC34C90_H + +#ifndef PCI_VENDOR_ID_SMC +#define PCI_VENDOR_ID_SMC 0x10b3 +#endif + +#ifndef PCI_DEVICE_ID_SMC_34C90 +#define PCI_DEVICE_ID_SMC_34C90 0xb106 +#endif + +/* Register definitions for SMC 34C90 PCI-to-CardBus bridge */ + +/* EEPROM Information Register */ +#define SMC34C90_EEINFO 0x0088 +#define SMC34C90_EEINFO_ONE_SOCKET 0x0001 +#define SMC34C90_EEINFO_5V_ONLY 0x0002 +#define SMC34C90_EEINFO_ISA_IRQ 0x0004 +#define SMC34C90_EEINFO_ZV_PORT 0x0008 +#define SMC34C90_EEINFO_RING 0x0010 +#define SMC34C90_EEINFO_LED 0x0020 + +#endif /* _LINUX_SMC34C90_H */ diff --git a/drivers/pcmcia/tcic.c b/drivers/pcmcia/tcic.c new file mode 100644 index 000000000..ca2818d7c --- /dev/null +++ b/drivers/pcmcia/tcic.c @@ -0,0 +1,966 @@ +/*====================================================================== + + Device driver for Databook TCIC-2 PCMCIA controller + + tcic.c 1.106 1999/09/15 15:32:19 + + 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 + <dhinds@hyper.stanford.edu>. 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 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/types.h> +#include <linux/fcntl.h> +#include <linux/string.h> + +#include <asm/io.h> +#include <asm/bitops.h> +#include <asm/segment.h> +#include <asm/system.h> + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/malloc.h> +#include <linux/timer.h> +#include <linux/ioport.h> +#include <linux/delay.h> + +#include <pcmcia/version.h> +#include <pcmcia/cs_types.h> +#include <pcmcia/cs.h> +#include <pcmcia/ss.h> +#include "tcic.h" + +#ifdef PCMCIA_DEBUG +static int pc_debug = PCMCIA_DEBUG; +MODULE_PARM(pc_debug, "i"); +static const char *version = +"tcic.c 1.106 1999/09/15 15:32:19 (David Hinds)"; +#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args) +#else +#define DEBUG(n, args...) +#endif + +/*====================================================================*/ + +/* Parameters that can be set with 'insmod' */ + +/* The base port address of the TCIC-2 chip */ +static int 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] = { -1 }; + +/* The card status change interrupt -- 0 means autoselect */ +static int cs_irq = 0; + +/* Poll status interval -- 0 means default to interrupt */ +static int poll_interval = 0; + +/* 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_PARM(tcic_base, "i"); +MODULE_PARM(ignore, "i"); +MODULE_PARM(do_scan, "i"); +MODULE_PARM(irq_mask, "i"); +MODULE_PARM(irq_list, "1-16i"); +MODULE_PARM(cs_irq, "i"); +MODULE_PARM(poll_interval, "i"); +MODULE_PARM(poll_quick, "i"); +MODULE_PARM(cycle_time, "i"); + +/*====================================================================*/ + +static void tcic_interrupt(int irq, void *dev, struct pt_regs *regs); +static void tcic_timer(u_long data); +static int tcic_service(u_int sock, u_int cmd, void *arg); + +typedef struct socket_info_t { + u_short psock; + void (*handler)(void *info, u_int events); + void *info; + u_char last_sstat; + u_char id; +} socket_info_t; + +static struct timer_list poll_timer; +static int tcic_timer_pending = 0; + +static int sockets; +static socket_info_t socket_table[2]; + +static socket_cap_t tcic_cap = { + /* only 16-bit cards, memory windows must be size-aligned */ + SS_CAP_PCCARD | SS_CAP_MEM_ALIGN, + 0x4cf8, /* irq 14, 11, 10, 7, 6, 5, 4, 3 */ + 0x1000, /* 4K minimum window size */ + 0, 0 /* No PCI or CardBus support */ +}; + +/*====================================================================*/ + +/* 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 PCMCIA_DEBUG_X +static u_char tcic_getb(u_char reg) +{ + u_char val = inb(tcic_base+reg); + printk(KERN_DEBUG "tcic_getb(%#x) = %#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(%#x) = %#x\n", tcic_base+reg, val); + return val; +} + +static void tcic_setb(u_char reg, u_char data) +{ + printk(KERN_DEBUG "tcic_setb(%#x, %#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(%#x, %#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 PCMCIA_DEBUG_X + printk(KERN_DEBUG "tcic_setl(%#x, %#lx)\n", tcic_base+reg, data); +#endif + outw(data & 0xffff, tcic_base+reg); + outw(data >> 16, tcic_base+reg+2); +} + +static u_char tcic_aux_getb(u_short reg) +{ + u_char mode = (tcic_getb(TCIC_MODE) & TCIC_MODE_PGMMASK) | reg; + tcic_setb(TCIC_MODE, mode); + return tcic_getb(TCIC_AUX); +} + +static void tcic_aux_setb(u_short reg, u_char data) +{ + u_char mode = (tcic_getb(TCIC_MODE) & TCIC_MODE_PGMMASK) | reg; + tcic_setb(TCIC_MODE, mode); + tcic_setb(TCIC_AUX, data); +} + +static u_short tcic_aux_getw(u_short reg) +{ + u_char mode = (tcic_getb(TCIC_MODE) & TCIC_MODE_PGMMASK) | reg; + tcic_setb(TCIC_MODE, mode); + return tcic_getw(TCIC_AUX); +} + +static void tcic_aux_setw(u_short reg, u_short data) +{ + u_char mode = (tcic_getb(TCIC_MODE) & TCIC_MODE_PGMMASK) | reg; + tcic_setb(TCIC_MODE, mode); + tcic_setw(TCIC_AUX, data); +} + +/*====================================================================*/ + +/* Time conversion functions */ + +static int to_cycles(int ns) +{ + if (ns < 14) + return 0; + else + return 2*(ns-14)/cycle_time; +} + +static int to_ns(int cycles) +{ + return (cycles*cycle_time)/2 + 14; +} + +/*====================================================================*/ + +static volatile u_int irq_hits; + +static void __init irq_count(int irq, void *dev, struct pt_regs *regs) +{ + irq_hits++; +} + +static u_int __init try_irq(int irq) +{ + u_short cfg; + + irq_hits = 0; + if (request_irq(irq, irq_count, 0, "irq scan", NULL) != 0) + return -1; + mdelay(10); + if (irq_hits) { + free_irq(irq, NULL); + 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, NULL); + + /* 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, irq_count, 0, "x", NULL) == 0)) { + mask1 |= (1 << i); + free_irq(i, NULL); + } + 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) && + (check_region(base, num) != 0) && ((base & 0xfeef) != 0x02e8)) + return 1; + else + return 0; +} + +/*====================================================================== + + This returns the revision code for the specified socket. + +======================================================================*/ + +static int __init get_tcic_id(void) +{ + u_short id; + + tcic_aux_setw(TCIC_AUX_TEST, TCIC_TEST_DIAG); + id = tcic_aux_getw(TCIC_AUX_ILOCK); + id = (id & TCIC_ILOCKTEST_ID_MASK) >> TCIC_ILOCKTEST_ID_SH; + tcic_aux_setw(TCIC_AUX_TEST, 0); + return id; +} + +/*====================================================================*/ + +static int __init init_tcic(void) +{ + int i, sock; + u_int mask, scan; + servinfo_t serv; + + DEBUG(0, "%s\n", version); + CardServices(GetCardServicesInfo, &serv); + if (serv.Revision != CS_RELEASE_CODE) { + printk(KERN_NOTICE "tcic: Card Services release " + "does not match!\n"); + return -1; + } + + printk(KERN_INFO "Databook TCIC-2 PCMCIA probe: "); + sock = 0; + + if (check_region(tcic_base, 16) == 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) { + /* 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; + } + } + } else + printk("could not allocate ports, "); + + if (sock == 0) { + printk("not found.\n"); + return -ENODEV; + } + + request_region(tcic_base, 16, "tcic-2"); + + sockets = 0; + for (i = 0; i < sock; i++) { + if ((i == ignore) || is_active(i)) continue; + socket_table[sockets].psock = i; + socket_table[sockets].handler = NULL; + socket_table[sockets].info = NULL; + socket_table[sockets].id = get_tcic_id(); + sockets++; + } + + switch (socket_table[0].id) { + case TCIC_ID_DB86082: + printk("DB86082"); break; + case TCIC_ID_DB86082A: + printk("DB86082A"); break; + case TCIC_ID_DB86084: + printk("DB86084"); break; + case TCIC_ID_DB86084A: + printk("DB86084A"); break; + case TCIC_ID_DB86072: + printk("DB86072"); break; + case TCIC_ID_DB86184: + printk("DB86184"); break; + case TCIC_ID_DB86082B: + printk("DB86082B"); break; + default: + printk("Unknown ID 0x%02x", socket_table[0].id); + } + + /* Set up polling */ + poll_timer.function = &tcic_timer; + poll_timer.data = 0; + poll_timer.prev = poll_timer.next = NULL; + + /* Build interrupt mask */ + printk(", %d sockets\n" KERN_INFO " irq list (", sockets); + if (irq_list[0] == -1) + mask = irq_mask; + else + for (i = mask = 0; i < 16; i++) + mask |= (1<<irq_list[i]); + mask &= tcic_cap.irq_mask; + + /* Scan interrupts */ + mask = irq_scan(mask); + tcic_cap.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", NULL) == 0)) + break; + cs_irq = i; + if (cs_irq == 0) poll_interval = HZ; + } + + if (tcic_cap.irq_mask & (1 << 11)) + printk("sktirq is irq 11, "); + if (cs_irq != 0) + printk("status change on irq %d\n", cs_irq); + else + printk("polled status, interval = %d ms\n", + poll_interval * 1000 / HZ); + + for (i = 0; i < sockets; i++) { + tcic_setw(TCIC_ADDR+2, socket_table[i].psock << TCIC_SS_SHFT); + socket_table[i].last_sstat = tcic_getb(TCIC_SSTAT); + } + + /* jump start interrupt handler, if needed */ + tcic_interrupt(0, NULL, NULL); + + if (register_ss_entry(sockets, &tcic_service) != 0) { + printk(KERN_NOTICE "tcic: register_ss_entry() failed\n"); + release_region(tcic_base, 16); + if (cs_irq != 0) + free_irq(cs_irq, NULL); + return -ENODEV; + } + + return 0; + +} /* init_tcic */ + +/*====================================================================*/ + +static void __exit exit_tcic(void) +{ + u_long flags; + unregister_ss_entry(&tcic_service); + save_flags(flags); + cli(); + if (cs_irq != 0) { + tcic_aux_setw(TCIC_AUX_SYSCFG, TCIC_SYSCFG_AUTOBUSY|0x0a00); + free_irq(cs_irq, NULL); + } + if (tcic_timer_pending) + del_timer(&poll_timer); + restore_flags(flags); + release_region(tcic_base, 16); +} /* exit_tcic */ + +/*====================================================================*/ + +static void tcic_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + int i, quick = 0; + u_char latch, sstat; + u_short psock; + u_int events; + static volatile int active = 0; + + if (active) { + printk(KERN_NOTICE "tcic: reentered interrupt handler!\n"); + return; + } else + active = 1; + + DEBUG(2, "tcic: 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) || (socket_table[psock].handler == NULL)) + 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) + socket_table[i].handler(socket_table[i].info, events); + } + + /* Schedule next poll, if needed */ + if (((cs_irq == 0) || quick) && (!tcic_timer_pending)) { + poll_timer.expires = jiffies + (quick ? poll_quick : poll_interval); + add_timer(&poll_timer); + tcic_timer_pending = 1; + } + active = 0; + + DEBUG(2, "tcic: interrupt done\n"); + +} /* tcic_interrupt */ + +static void tcic_timer(u_long data) +{ + DEBUG(2, "tcic: tcic_timer()\n"); + tcic_timer_pending = 0; + tcic_interrupt(0, NULL, NULL); +} /* tcic_timer */ + +/*====================================================================*/ + +static int tcic_register_callback(u_short lsock, ss_callback_t *call) +{ + if (call == NULL) { + socket_table[lsock].handler = NULL; + MOD_DEC_USE_COUNT; + } else { + MOD_INC_USE_COUNT; + socket_table[lsock].handler = call->handler; + socket_table[lsock].info = call->info; + } + return 0; +} /* tcic_register_callback */ + +/*====================================================================*/ + +static int tcic_get_status(u_short lsock, u_int *value) +{ + u_short psock = socket_table[lsock].psock; + u_char reg; + + tcic_setl(TCIC_ADDR, (psock << TCIC_ADDR_SS_SHFT) + | TCIC_ADDR_INDREG | TCIC_SCF1(psock)); + reg = tcic_getb(TCIC_SSTAT); + *value = (reg & TCIC_SSTAT_CD) ? SS_DETECT : 0; + *value |= (reg & TCIC_SSTAT_WP) ? SS_WRPROT : 0; + if (tcic_getw(TCIC_DATA) & TCIC_SCF1_IOSTS) { + *value |= (reg & TCIC_SSTAT_LBAT1) ? SS_STSCHG : 0; + } else { + *value |= (reg & TCIC_SSTAT_RDY) ? SS_READY : 0; + *value |= (reg & TCIC_SSTAT_LBAT1) ? SS_BATDEAD : 0; + *value |= (reg & TCIC_SSTAT_LBAT2) ? SS_BATWARN : 0; + } + reg = tcic_getb(TCIC_PWR); + if (reg & (TCIC_PWR_VCC(psock)|TCIC_PWR_VPP(psock))) + *value |= SS_POWERON; + DEBUG(1, "tcic: GetStatus(%d) = %#2.2x\n", lsock, *value); + return 0; +} /* tcic_get_status */ + +/*====================================================================*/ + +static int tcic_inquire_socket(u_short lsock, socket_cap_t *cap) +{ + *cap = tcic_cap; + return 0; +} /* tcic_inquire_socket */ + +/*====================================================================*/ + +static int tcic_get_socket(u_short lsock, socket_state_t *state) +{ + u_short psock = socket_table[lsock].psock; + u_char reg; + u_short scf1, scf2; + + tcic_setl(TCIC_ADDR, (psock << TCIC_ADDR_SS_SHFT) + | TCIC_ADDR_INDREG | TCIC_SCF1(psock)); + scf1 = tcic_getw(TCIC_DATA); + state->flags = (scf1 & TCIC_SCF1_IOSTS) ? SS_IOCARD : 0; + state->flags |= (scf1 & TCIC_SCF1_DMA_MASK) ? SS_DMA_MODE : 0; + state->flags |= (scf1 & TCIC_SCF1_SPKR) ? SS_SPKR_ENA : 0; + if (tcic_getb(TCIC_SCTRL) & TCIC_SCTRL_ENA) + state->flags |= SS_OUTPUT_ENA; + state->io_irq = scf1 & TCIC_SCF1_IRQ_MASK; + if (state->io_irq == 1) state->io_irq = 11; + + reg = tcic_getb(TCIC_PWR); + state->Vcc = state->Vpp = 0; + if (reg & TCIC_PWR_VCC(psock)) { + if (reg & TCIC_PWR_VPP(psock)) + state->Vcc = 50; + else + state->Vcc = state->Vpp = 50; + } else { + if (reg & TCIC_PWR_VPP(psock)) { + state->Vcc = 50; + state->Vpp = 120; + } + } + reg = tcic_aux_getb(TCIC_AUX_ILOCK); + state->flags |= (reg & TCIC_ILOCK_CRESET) ? SS_RESET : 0; + + /* Card status change interrupt mask */ + tcic_setw(TCIC_ADDR, TCIC_SCF2(psock)); + scf2 = tcic_getw(TCIC_DATA); + state->csc_mask = (scf2 & TCIC_SCF2_MCD) ? 0 : SS_DETECT; + if (state->flags & SS_IOCARD) { + state->csc_mask |= (scf2 & TCIC_SCF2_MLBAT1) ? 0 : SS_STSCHG; + } else { + state->csc_mask |= (scf2 & TCIC_SCF2_MLBAT1) ? 0 : SS_BATDEAD; + state->csc_mask |= (scf2 & TCIC_SCF2_MLBAT2) ? 0 : SS_BATWARN; + state->csc_mask |= (scf2 & TCIC_SCF2_MRDY) ? 0 : SS_READY; + } + + DEBUG(1, "tcic: GetSocket(%d) = flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x\n", lsock, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + return 0; +} /* tcic_get_socket */ + +/*====================================================================*/ + +static int tcic_set_socket(u_short lsock, socket_state_t *state) +{ + u_short psock = socket_table[lsock].psock; + u_char reg; + u_short scf1, scf2; + + DEBUG(1, "tcic: SetSocket(%d, flags %#3.3x, Vcc %d, Vpp %d, " + "io_irq %d, csc_mask %#2.2x)\n", lsock, state->flags, + state->Vcc, state->Vpp, state->io_irq, state->csc_mask); + 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_get_io_map(u_short lsock, struct pccard_io_map *io) +{ + u_short psock = socket_table[lsock].psock; + u_short base, ioctl; + u_int addr; + + if (io->map > 1) return -EINVAL; + tcic_setw(TCIC_ADDR+2, TCIC_ADR2_INDREG | (psock << TCIC_SS_SHFT)); + addr = TCIC_IWIN(psock, io->map); + 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) + io->start = io->stop = base; + else { + io->start = base & (base-1); + io->stop = io->start + (base ^ (base-1)); + } + io->speed = to_ns(ioctl & TCIC_ICTL_WSCNT_MASK); + io->flags = (ioctl & TCIC_ICTL_ENA) ? MAP_ACTIVE : 0; + switch (ioctl & TCIC_ICTL_BW_MASK) { + case TCIC_ICTL_BW_DYN: + io->flags |= MAP_AUTOSZ; break; + case TCIC_ICTL_BW_16: + io->flags |= MAP_16BIT; break; + default: + break; + } + DEBUG(1, "tcic: GetIOMap(%d, %d) = %#2.2x, %d ns, " + "%#4.4x-%#4.4x\n", lsock, io->map, io->flags, + io->speed, io->start, io->stop); + return 0; +} /* tcic_get_io_map */ + +/*====================================================================*/ + +static int tcic_set_io_map(u_short lsock, struct pccard_io_map *io) +{ + u_short psock = socket_table[lsock].psock; + u_int addr; + u_short base, len, ioctl; + + DEBUG(1, "tcic: SetIOMap(%d, %d, %#2.2x, %d ns, " + "%#4.4x-%#4.4x)\n", lsock, io->map, io->flags, + io->speed, io->start, io->stop); + if ((io->map > 1) || (io->start > 0xffff) || (io->stop > 0xffff) || + (io->stop < io->start)) return -EINVAL; + tcic_setw(TCIC_ADDR+2, TCIC_ADR2_INDREG | (psock << TCIC_SS_SHFT)); + addr = TCIC_IWIN(psock, io->map); + + base = io->start; len = io->stop - io->start; + /* Check to see that len+1 is power of two, etc */ + if ((len & (len+1)) || (base & len)) return -EINVAL; + base |= (len+1)>>1; + tcic_setw(TCIC_ADDR, addr + TCIC_IBASE_X); + tcic_setw(TCIC_DATA, base); + + ioctl = (psock << TCIC_ICTL_SS_SHFT); + ioctl |= (len == 0) ? TCIC_ICTL_TINY : 0; + ioctl |= (io->flags & MAP_ACTIVE) ? TCIC_ICTL_ENA : 0; + ioctl |= to_cycles(io->speed) & TCIC_ICTL_WSCNT_MASK; + if (!(io->flags & MAP_AUTOSZ)) { + ioctl |= TCIC_ICTL_QUIET; + ioctl |= (io->flags & MAP_16BIT) ? TCIC_ICTL_BW_16 : TCIC_ICTL_BW_8; + } + tcic_setw(TCIC_ADDR, addr + TCIC_ICTL_X); + tcic_setw(TCIC_DATA, ioctl); + + return 0; +} /* tcic_set_io_map */ + +/*====================================================================*/ + +static int tcic_get_mem_map(u_short lsock, struct pccard_mem_map *mem) +{ + u_short psock = socket_table[lsock].psock; + u_short addr, ctl; + u_long base, mmap; + + if (mem->map > 3) return -EINVAL; + tcic_setw(TCIC_ADDR+2, TCIC_ADR2_INDREG | (psock << TCIC_SS_SHFT)); + addr = TCIC_MWIN(psock, mem->map); + + tcic_setw(TCIC_ADDR, addr + TCIC_MBASE_X); + base = tcic_getw(TCIC_DATA); + if (base & TCIC_MBASE_4K_BIT) { + mem->sys_start = base & TCIC_MBASE_HA_MASK; + mem->sys_stop = mem->sys_start; + } else { + base &= TCIC_MBASE_HA_MASK; + mem->sys_start = (base & (base-1)); + mem->sys_stop = mem->sys_start + (base ^ (base-1)); + } + mem->sys_start = mem->sys_start << TCIC_MBASE_HA_SHFT; + mem->sys_stop = (mem->sys_stop << TCIC_MBASE_HA_SHFT) + 0x0fff; + + tcic_setw(TCIC_ADDR, addr + TCIC_MMAP_X); + mmap = tcic_getw(TCIC_DATA); + mem->flags = (mmap & TCIC_MMAP_REG) ? MAP_ATTRIB : 0; + mmap &= TCIC_MMAP_CA_MASK; + mem->card_start = mem->sys_start + (mmap << TCIC_MMAP_CA_SHFT); + mem->card_start &= 0x3ffffff; + + tcic_setw(TCIC_ADDR, addr + TCIC_MCTL_X); + ctl = tcic_getw(TCIC_DATA); + mem->flags |= (ctl & TCIC_MCTL_ENA) ? MAP_ACTIVE : 0; + mem->flags |= (ctl & TCIC_MCTL_B8) ? 0 : MAP_16BIT; + mem->flags |= (ctl & TCIC_MCTL_WP) ? MAP_WRPROT : 0; + mem->speed = to_ns(ctl & TCIC_MCTL_WSCNT_MASK); + + DEBUG(1, "tcic: GetMemMap(%d, %d) = %#2.2x, %d ns, " + "%#5.5lx-%#5.5lx, %#5.5x\n", lsock, mem->map, mem->flags, + mem->speed, mem->sys_start, mem->sys_stop, mem->card_start); + return 0; +} /* tcic_get_mem_map */ + +/*====================================================================*/ + +static int tcic_set_mem_map(u_short lsock, struct pccard_mem_map *mem) +{ + u_short psock = socket_table[lsock].psock; + u_short addr, ctl; + u_long base, len, mmap; + + DEBUG(1, "tcic: SetMemMap(%d, %d, %#2.2x, %d ns, " + "%#5.5lx-%#5.5lx, %#5.5x)\n", lsock, mem->map, mem->flags, + mem->speed, mem->sys_start, mem->sys_stop, mem->card_start); + if ((mem->map > 3) || (mem->card_start > 0x3ffffff) || + (mem->sys_start > 0xffffff) || (mem->sys_stop > 0xffffff) || + (mem->sys_start > mem->sys_stop) || (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->sys_start; len = mem->sys_stop - mem->sys_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->sys_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 */ + +/*====================================================================*/ + +typedef int (*subfn_t)(u_short, void *); + +static subfn_t service_table[] = { + (subfn_t)&tcic_register_callback, + (subfn_t)&tcic_inquire_socket, + (subfn_t)&tcic_get_status, + (subfn_t)&tcic_get_socket, + (subfn_t)&tcic_set_socket, + (subfn_t)&tcic_get_io_map, + (subfn_t)&tcic_set_io_map, + (subfn_t)&tcic_get_mem_map, + (subfn_t)&tcic_set_mem_map, +}; + +#define NFUNC (sizeof(service_table)/sizeof(subfn_t)) + +static int tcic_service(u_int lsock, u_int cmd, void *arg) +{ + int err; + + DEBUG(2, "tcic_service(%d, %d, 0x%p)\n", lsock, cmd, arg); + + if (cmd < NFUNC) + err = service_table[cmd](lsock, arg); + else + err = -EINVAL; + + return err; +} /* tcic_service */ + +/*====================================================================*/ + +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..e33f79531 --- /dev/null +++ b/drivers/pcmcia/tcic.h @@ -0,0 +1,266 @@ +/* + * tcic.h 1.12 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 + * <dhinds@hyper.stanford.edu>. 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 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..758757d99 --- /dev/null +++ b/drivers/pcmcia/ti113x.h @@ -0,0 +1,180 @@ +/* + * ti113x.h 1.15 1999/09/03 16:43:35 + * + * 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 + * <dhinds@hyper.stanford.edu>. 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 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 + +#ifndef PCI_VENDOR_ID_TI +#define PCI_VENDOR_ID_TI 0x104c +#endif + +#ifndef PCI_DEVICE_ID_TI_1130 +#define PCI_DEVICE_ID_TI_1130 0xac12 +#endif +#ifndef PCI_DEVICE_ID_TI_1131 +#define PCI_DEVICE_ID_TI_1131 0xac15 +#endif +#ifndef PCI_DEVICE_ID_TI_1031 +#define PCI_DEVICE_ID_TI_1031 0xac13 +#endif +#ifndef PCI_DEVICE_ID_TI_1250A +#define PCI_DEVICE_ID_TI_1250A 0xac16 +#endif +#ifndef PCI_DEVICE_ID_TI_1220 +#define PCI_DEVICE_ID_TI_1220 0xac17 +#endif +#ifndef PCI_DEVICE_ID_TI_1221 +#define PCI_DEVICE_ID_TI_1221 0xac19 +#endif +#ifndef PCI_DEVICE_ID_TI_1210 +#define PCI_DEVICE_ID_TI_1210 0xac1a +#endif +#ifndef PCI_DEVICE_ID_TI_1450 +#define PCI_DEVICE_ID_TI_1450 0xac1b +#endif +#ifndef PCI_DEVICE_ID_TI_1225 +#define PCI_DEVICE_ID_TI_1225 0xac1c +#endif +#ifndef PCI_DEVICE_ID_TI_1251A +#define PCI_DEVICE_ID_TI_1251A 0xac1d +#endif +#ifndef PCI_DEVICE_ID_TI_1211 +#define PCI_DEVICE_ID_TI_1211 0xac1e +#endif +#ifndef PCI_DEVICE_ID_TI_1251B +#define PCI_DEVICE_ID_TI_1251B 0xac1f +#endif +#ifndef PCI_DEVICE_ID_TI_1420 +#define PCI_DEVICE_ID_TI_1420 0xac51 +#endif + +/* Register definitions for TI 113X PCI-to-CardBus bridges */ + +/* System Control Register */ +#define TI113X_SYSTEM_CONTROL 0x0080 /* 32 bit */ +#define TI113X_SCR_SMIROUTE 0x04000000 +#define TI113X_SCR_SMISTATUS 0x02000000 +#define TI113X_SCR_SMIENB 0x01000000 +#define TI113X_SCR_VCCPROT 0x00200000 +#define TI113X_SCR_REDUCEZV 0x00100000 +#define TI113X_SCR_CDREQEN 0x00080000 +#define TI113X_SCR_CDMACHAN 0x00070000 +#define TI113X_SCR_SOCACTIVE 0x00002000 +#define TI113X_SCR_PWRSTREAM 0x00000800 +#define TI113X_SCR_DELAYUP 0x00000400 +#define TI113X_SCR_DELAYDOWN 0x00000200 +#define TI113X_SCR_INTERROGATE 0x00000100 +#define TI113X_SCR_CLKRUN_SEL 0x00000080 +#define TI113X_SCR_PWRSAVINGS 0x00000040 +#define TI113X_SCR_SUBSYSRW 0x00000020 +#define TI113X_SCR_CB_DPAR 0x00000010 +#define TI113X_SCR_CDMA_EN 0x00000008 +#define TI113X_SCR_ASYNC_IRQ 0x00000004 +#define TI113X_SCR_KEEPCLK 0x00000002 +#define TI113X_SCR_CLKRUN_ENA 0x00000001 + +#define TI122X_SCR_SER_STEP 0xc0000000 +#define TI122X_SCR_INTRTIE 0x20000000 +#define TI122X_SCR_CBRSVD 0x00400000 +#define TI122X_SCR_MRBURSTDN 0x00008000 +#define TI122X_SCR_MRBURSTUP 0x00004000 +#define TI122X_SCR_RIMUX 0x00000001 + +/* Multimedia Control Register */ +#define TI1250_MULTIMEDIA_CTL 0x0084 /* 8 bit */ +#define TI1250_MMC_ZVOUTEN 0x80 +#define TI1250_MMC_PORTSEL 0x40 +#define TI1250_MMC_ZVEN1 0x02 +#define TI1250_MMC_ZVEN0 0x01 + +#define TI1250_GENERAL_STATUS 0x0085 /* 8 bit */ +#define TI1250_GPIO0_CONTROL 0x0088 /* 8 bit */ +#define TI1250_GPIO1_CONTROL 0x0089 /* 8 bit */ +#define TI1250_GPIO2_CONTROL 0x008a /* 8 bit */ +#define TI1250_GPIO3_CONTROL 0x008b /* 8 bit */ +#define TI122X_IRQMUX 0x008c /* 32 bit */ + +/* 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)) + +#endif /* _LINUX_TI113X_H */ + diff --git a/drivers/pcmcia/topic.h b/drivers/pcmcia/topic.h new file mode 100644 index 000000000..57860a023 --- /dev/null +++ b/drivers/pcmcia/topic.h @@ -0,0 +1,96 @@ +/* + * 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 + * <dhinds@hyper.stanford.edu>. 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 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 + +#ifndef PCI_VENDOR_ID_TOSHIBA +#define PCI_VENDOR_ID_TOSHIBA 0x1179 +#endif +#ifndef PCI_DEVICE_ID_TOSHIBA_TOPIC95_A +#define PCI_DEVICE_ID_TOSHIBA_TOPIC95_A 0x0603 +#endif +#ifndef PCI_DEVICE_ID_TOSHIBA_TOPIC95_B +#define PCI_DEVICE_ID_TOSHIBA_TOPIC95_B 0x060a +#endif +#ifndef PCI_DEVICE_ID_TOSHIBA_TOPIC97 +#define PCI_DEVICE_ID_TOSHIBA_TOPIC97 0x060f +#endif + +/* Register definitions for Toshiba ToPIC95 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 + +#endif /* _LINUX_TOPIC_H */ diff --git a/drivers/pcmcia/vg468.h b/drivers/pcmcia/vg468.h new file mode 100644 index 000000000..c19d53d15 --- /dev/null +++ b/drivers/pcmcia/vg468.h @@ -0,0 +1,106 @@ +/* + * vg468.h 1.10 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 + * <dhinds@hyper.stanford.edu>. 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 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/yenta.h b/drivers/pcmcia/yenta.h new file mode 100644 index 000000000..5c09f2978 --- /dev/null +++ b/drivers/pcmcia/yenta.h @@ -0,0 +1,153 @@ +/* + * yenta.h 1.15 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 + * <dhinds@hyper.stanford.edu>. 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 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_YENTA_H +#define _LINUX_YENTA_H + +/* PCI Configuration Registers */ + +#define PCI_STATUS_CAPLIST 0x10 +#define PCI_CB_CAPABILITY_POINTER 0x14 /* 8 bit */ +#define PCI_CAPABILITY_ID 0x00 /* 8 bit */ +#define PCI_CAPABILITY_PM 0x01 +#define PCI_NEXT_CAPABILITY 0x01 /* 8 bit */ +#define PCI_PM_CAPABILITIES 0x02 /* 16 bit */ +#define PCI_PMCAP_PME_D3COLD 0x8000 +#define PCI_PMCAP_PME_D3HOT 0x4000 +#define PCI_PMCAP_PME_D2 0x2000 +#define PCI_PMCAP_PME_D1 0x1000 +#define PCI_PMCAP_PME_D0 0x0800 +#define PCI_PMCAP_D2_CAP 0x0400 +#define PCI_PMCAP_D1_CAP 0x0200 +#define PCI_PMCAP_DYN_DATA 0x0100 +#define PCI_PMCAP_DSI 0x0020 +#define PCI_PMCAP_AUX_PWR 0x0010 +#define PCI_PMCAP_PMECLK 0x0008 +#define PCI_PMCAP_VERSION_MASK 0x0007 +#define PCI_PM_CONTROL_STATUS 0x04 /* 16 bit */ +#define PCI_PMCS_PME_STATUS 0x8000 +#define PCI_PMCS_DATASCALE_MASK 0x6000 +#define PCI_PMCS_DATASCALE_SHIFT 13 +#define PCI_PMCS_DATASEL_MASK 0x1e00 +#define PCI_PMCS_DATASEL_SHIFT 9 +#define PCI_PMCS_PME_ENABLE 0x0100 +#define PCI_PMCS_PWR_STATE_MASK 0x0003 +#define PCI_PMCS_PWR_STATE_D0 0x0000 +#define PCI_PMCS_PWR_STATE_D1 0x0001 +#define PCI_PMCS_PWR_STATE_D2 0x0002 +#define PCI_PMCS_PWR_STATE_D3 0x0003 +#define PCI_PM_BRIDGE_EXT 0x06 /* 8 bit */ +#define PCI_PM_DATA 0x07 /* 8 bit */ + +#define CB_PRIMARY_BUS 0x18 /* 8 bit */ +#define CB_CARDBUS_BUS 0x19 /* 8 bit */ +#define CB_SUBORD_BUS 0x1a /* 8 bit */ +#define CB_LATENCY_TIMER 0x1b /* 8 bit */ + +#define CB_MEM_BASE(m) (0x1c + 8*(m)) +#define CB_MEM_LIMIT(m) (0x20 + 8*(m)) +#define CB_IO_BASE(m) (0x2c + 8*(m)) +#define CB_IO_LIMIT(m) (0x30 + 8*(m)) + +#define CB_BRIDGE_CONTROL 0x3e /* 16 bit */ +#define CB_BCR_PARITY_ENA 0x0001 +#define CB_BCR_SERR_ENA 0x0002 +#define CB_BCR_ISA_ENA 0x0004 +#define CB_BCR_VGA_ENA 0x0008 +#define CB_BCR_MABORT 0x0020 +#define CB_BCR_CB_RESET 0x0040 +#define CB_BCR_ISA_IRQ 0x0080 +#define CB_BCR_PREFETCH(m) (0x0100 << (m)) +#define CB_BCR_WRITE_POST 0x0400 + +#define CB_LEGACY_MODE_BASE 0x44 + +/* Memory mapped registers */ + +#define CB_SOCKET_EVENT 0x0000 +#define CB_SE_CSTSCHG 0x00000001 +#define CB_SE_CCD1 0x00000002 +#define CB_SE_CCD2 0x00000004 +#define CB_SE_PWRCYCLE 0x00000008 + +#define CB_SOCKET_MASK 0x0004 +#define CB_SM_CSTSCHG 0x00000001 +#define CB_SM_CCD 0x00000006 +#define CB_SM_PWRCYCLE 0x00000008 + +#define CB_SOCKET_STATE 0x0008 +#define CB_SS_CSTSCHG 0x00000001 +#define CB_SS_CCD1 0x00000002 +#define CB_SS_CCD2 0x00000004 +#define CB_SS_PWRCYCLE 0x00000008 +#define CB_SS_16BIT 0x00000010 +#define CB_SS_32BIT 0x00000020 +#define CB_SS_CINT 0x00000040 +#define CB_SS_BADCARD 0x00000080 +#define CB_SS_DATALOST 0x00000100 +#define CB_SS_BADVCC 0x00000200 +#define CB_SS_5VCARD 0x00000400 +#define CB_SS_3VCARD 0x00000800 +#define CB_SS_XVCARD 0x00001000 +#define CB_SS_YVCARD 0x00002000 +#define CB_SS_5VSOCKET 0x10000000 +#define CB_SS_3VSOCKET 0x20000000 +#define CB_SS_XVSOCKET 0x40000000 +#define CB_SS_YVSOCKET 0x80000000 + +#define CB_SOCKET_FORCE 0x000c +#define CB_SF_CVSTEST 0x00004000 + +#define CB_SOCKET_CONTROL 0x0010 +#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 0x0020 +#define CB_SP_CLK_CTRL 0x00000001 +#define CB_SP_CLK_CTRL_ENA 0x00010000 +#define CB_SP_CLK_MODE 0x01000000 +#define CB_SP_ACCESS 0x02000000 + +/* Address bits 31..24 for memory windows for 16-bit cards, + accessable only by memory mapping the 16-bit register set */ +#define CB_MEM_PAGE(map) (0x40 + (map)) + +#endif /* _LINUX_YENTA_H */ |