| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| |
| /* |
| * Copyright 2008 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| * Copyright (c) 2016 by Delphix. All rights reserved. |
| */ |
| |
| /* |
| * PCIC device/interrupt handler |
| * The "pcic" driver handles the Intel 82365SL, Cirrus Logic |
| * and Toshiba (and possibly other clones) PCMCIA adapter chip |
| * sets. It implements a subset of Socket Services as defined |
| * in the Solaris PCMCIA design documents |
| */ |
| |
| /* |
| * currently defined "properties" |
| * |
| * clock-frequency bus clock frequency |
| * smi system management interrupt override |
| * need-mult-irq need status IRQ for each pair of sockets |
| * disable-audio don't route audio signal to speaker |
| */ |
| |
| |
| #include <sys/types.h> |
| #include <sys/inttypes.h> |
| #include <sys/param.h> |
| #include <sys/systm.h> |
| #include <sys/user.h> |
| #include <sys/buf.h> |
| #include <sys/file.h> |
| #include <sys/uio.h> |
| #include <sys/conf.h> |
| #include <sys/stat.h> |
| #include <sys/autoconf.h> |
| #include <sys/vtoc.h> |
| #include <sys/dkio.h> |
| #include <sys/ddi.h> |
| #include <sys/sunddi.h> |
| #include <sys/sunndi.h> |
| #include <sys/var.h> |
| #include <sys/callb.h> |
| #include <sys/open.h> |
| #include <sys/ddidmareq.h> |
| #include <sys/dma_engine.h> |
| #include <sys/kstat.h> |
| #include <sys/kmem.h> |
| #include <sys/modctl.h> |
| #include <sys/pci.h> |
| #include <sys/pci_impl.h> |
| |
| #include <sys/pctypes.h> |
| #include <sys/pcmcia.h> |
| #include <sys/sservice.h> |
| |
| #include <sys/note.h> |
| |
| #include <sys/pcic_reg.h> |
| #include <sys/pcic_var.h> |
| |
| #if defined(__i386) || defined(__amd64) |
| #include <sys/pci_cfgspace.h> |
| #endif |
| |
| #if defined(__sparc) |
| #include <sys/pci/pci_nexus.h> |
| #endif |
| |
| #include <sys/hotplug/hpcsvc.h> |
| #include "cardbus/cardbus.h" |
| |
| #define SOFTC_SIZE (sizeof (anp_t)) |
| |
| static int pcic_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); |
| static int pcic_attach(dev_info_t *, ddi_attach_cmd_t); |
| static int pcic_detach(dev_info_t *, ddi_detach_cmd_t); |
| static int32_t pcic_quiesce(dev_info_t *); |
| static uint_t pcic_intr(caddr_t, caddr_t); |
| static int pcic_do_io_intr(pcicdev_t *, uint32_t); |
| static int pcic_probe(dev_info_t *); |
| |
| static int pcic_open(dev_t *, int, int, cred_t *); |
| static int pcic_close(dev_t, int, int, cred_t *); |
| static int pcic_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); |
| |
| typedef struct pcm_regs pcm_regs_t; |
| |
| static void pcic_init_assigned(dev_info_t *); |
| static int pcic_apply_avail_ranges(dev_info_t *, pcm_regs_t *, |
| pci_regspec_t *, int); |
| int pci_resource_setup_avail(dev_info_t *, pci_regspec_t *, int); |
| |
| /* |
| * On x86 platforms the ddi_iobp_alloc(9F) and ddi_mem_alloc(9F) calls |
| * are xlated into DMA ctlops. To make this nexus work on x86, we |
| * need to have the default ddi_dma_mctl ctlops in the bus_ops |
| * structure, just to pass the request to the parent. The correct |
| * ctlops should be ddi_no_dma_mctl because so far we don't do DMA. |
| */ |
| static |
| struct bus_ops pcmciabus_ops = { |
| BUSO_REV, |
| pcmcia_bus_map, |
| NULL, |
| NULL, |
| NULL, |
| i_ddi_map_fault, |
| ddi_no_dma_map, |
| ddi_no_dma_allochdl, |
| ddi_no_dma_freehdl, |
| ddi_no_dma_bindhdl, |
| ddi_no_dma_unbindhdl, |
| ddi_no_dma_flush, |
| ddi_no_dma_win, |
| ddi_dma_mctl, |
| pcmcia_ctlops, |
| pcmcia_prop_op, |
| NULL, /* (*bus_get_eventcookie)(); */ |
| NULL, /* (*bus_add_eventcall)(); */ |
| NULL, /* (*bus_remove_eventcall)(); */ |
| NULL, /* (*bus_post_event)(); */ |
| NULL, /* (*bus_intr_ctl)(); */ |
| NULL, /* (*bus_config)(); */ |
| NULL, /* (*bus_unconfig)(); */ |
| NULL, /* (*bus_fm_init)(); */ |
| NULL, /* (*bus_fm_fini)(); */ |
| NULL, /* (*bus_enter)() */ |
| NULL, /* (*bus_exit)() */ |
| NULL, /* (*bus_power)() */ |
| pcmcia_intr_ops /* (*bus_intr_op)(); */ |
| }; |
| |
| static struct cb_ops pcic_cbops = { |
| pcic_open, |
| pcic_close, |
| nodev, |
| nodev, |
| nodev, |
| nodev, |
| nodev, |
| pcic_ioctl, |
| nodev, |
| nodev, |
| nodev, |
| nochpoll, |
| ddi_prop_op, |
| NULL, |
| #ifdef CARDBUS |
| D_NEW | D_MP | D_HOTPLUG |
| #else |
| D_NEW | D_MP |
| #endif |
| }; |
| |
| static struct dev_ops pcic_devops = { |
| DEVO_REV, |
| 0, |
| pcic_getinfo, |
| nulldev, |
| pcic_probe, |
| pcic_attach, |
| pcic_detach, |
| nulldev, |
| &pcic_cbops, |
| &pcmciabus_ops, |
| NULL, |
| pcic_quiesce, /* devo_quiesce */ |
| }; |
| |
| void *pcic_soft_state_p = NULL; |
| static int pcic_maxinst = -1; |
| |
| int pcic_do_insertion = 1; |
| int pcic_do_removal = 1; |
| |
| struct irqmap { |
| int irq; |
| int count; |
| } pcic_irq_map[16]; |
| |
| |
| int pcic_debug = 0x0; |
| static void pcic_err(dev_info_t *dip, int level, const char *fmt, ...); |
| extern void cardbus_dump_pci_config(dev_info_t *dip); |
| extern void cardbus_dump_socket(dev_info_t *dip); |
| extern int cardbus_validate_iline(dev_info_t *dip, ddi_acc_handle_t handle); |
| static void pcic_dump_debqueue(char *msg); |
| |
| #if defined(PCIC_DEBUG) |
| static void xxdmp_all_regs(pcicdev_t *, int, uint32_t); |
| |
| #define pcic_mutex_enter(a) \ |
| { \ |
| pcic_err(NULL, 10, "Set lock at %d\n", __LINE__); \ |
| mutex_enter(a); \ |
| }; |
| |
| #define pcic_mutex_exit(a) \ |
| { \ |
| pcic_err(NULL, 10, "Clear lock at %d\n", __LINE__); \ |
| mutex_exit(a); \ |
| }; |
| |
| #else |
| #define pcic_mutex_enter(a) mutex_enter(a) |
| #define pcic_mutex_exit(a) mutex_exit(a) |
| #endif |
| |
| #define PCIC_VCC_3VLEVEL 1 |
| #define PCIC_VCC_5VLEVEL 2 |
| #define PCIC_VCC_12LEVEL 3 |
| |
| /* bit patterns to select voltage levels */ |
| int pcic_vpp_levels[13] = { |
| 0, 0, 0, |
| 1, /* 3.3V */ |
| 0, |
| 1, /* 5V */ |
| 0, 0, 0, 0, 0, 0, |
| 2 /* 12V */ |
| }; |
| |
| uint8_t pcic_cbv_levels[13] = { |
| 0, 0, 0, |
| 3, /* 3.3V */ |
| 0, |
| 2, /* 5V */ |
| 0, 0, 0, 0, 0, 0, |
| 1 /* 12V */ |
| }; |
| |
| struct power_entry pcic_power[4] = { |
| { |
| 0, VCC|VPP1|VPP2 |
| }, |
| { |
| 33, /* 3.3Volt */ |
| VCC|VPP1|VPP2 |
| }, |
| { |
| 5*10, /* 5Volt */ |
| VCC|VPP1|VPP2 /* currently only know about this */ |
| }, |
| { |
| 12*10, /* 12Volt */ |
| VPP1|VPP2 |
| } |
| }; |
| |
| /* |
| * Base used to allocate ranges of PCI memory on x86 systems |
| * Each instance gets a chunk above the base that is used to map |
| * in the memory and I/O windows for that device. |
| * Pages below the base are also allocated for the EXCA registers, |
| * one per instance. |
| */ |
| #define PCIC_PCI_MEMCHUNK 0x1000000 |
| |
| static int pcic_wait_insert_time = 5000000; /* In micro-seconds */ |
| static int pcic_debounce_time = 200000; /* In micro-seconds */ |
| |
| struct debounce { |
| pcic_socket_t *pcs; |
| clock_t expire; |
| struct debounce *next; |
| }; |
| |
| static struct debounce *pcic_deb_queue = NULL; |
| static kmutex_t pcic_deb_mtx; |
| static kcondvar_t pcic_deb_cv; |
| static kthread_t *pcic_deb_threadid; |
| |
| static inthandler_t *pcic_handlers; |
| |
| static void pcic_setup_adapter(pcicdev_t *); |
| static int pcic_change(pcicdev_t *, int); |
| static int pcic_ll_reset(pcicdev_t *, int); |
| static void pcic_mswait(pcicdev_t *, int, int); |
| static boolean_t pcic_check_ready(pcicdev_t *, int); |
| static void pcic_set_cdtimers(pcicdev_t *, int, uint32_t, int); |
| static void pcic_ready_wait(pcicdev_t *, int); |
| extern int pcmcia_get_intr(dev_info_t *, int); |
| extern int pcmcia_return_intr(dev_info_t *, int); |
| extern void pcmcia_cb_suspended(int); |
| extern void pcmcia_cb_resumed(int); |
| |
| static int pcic_callback(dev_info_t *, int (*)(), int); |
| static int pcic_inquire_adapter(dev_info_t *, inquire_adapter_t *); |
| static int pcic_get_adapter(dev_info_t *, get_adapter_t *); |
| static int pcic_get_page(dev_info_t *, get_page_t *); |
| static int pcic_get_socket(dev_info_t *, get_socket_t *); |
| static int pcic_get_status(dev_info_t *, get_ss_status_t *); |
| static int pcic_get_window(dev_info_t *, get_window_t *); |
| static int pcic_inquire_socket(dev_info_t *, inquire_socket_t *); |
| static int pcic_inquire_window(dev_info_t *, inquire_window_t *); |
| static int pcic_reset_socket(dev_info_t *, int, int); |
| static int pcic_set_page(dev_info_t *, set_page_t *); |
| static int pcic_set_window(dev_info_t *, set_window_t *); |
| static int pcic_set_socket(dev_info_t *, set_socket_t *); |
| static int pcic_set_interrupt(dev_info_t *, set_irq_handler_t *); |
| static int pcic_clear_interrupt(dev_info_t *, clear_irq_handler_t *); |
| static void pcic_pm_detection(void *); |
| static void pcic_iomem_pci_ctl(ddi_acc_handle_t, uchar_t *, unsigned); |
| static int clext_reg_read(pcicdev_t *, int, uchar_t); |
| static void clext_reg_write(pcicdev_t *, int, uchar_t, uchar_t); |
| static int pcic_calc_speed(pcicdev_t *, uint32_t); |
| static int pcic_card_state(pcicdev_t *, pcic_socket_t *); |
| static int pcic_find_pci_type(pcicdev_t *); |
| static void pcic_82092_smiirq_ctl(pcicdev_t *, int, int, int); |
| static void pcic_handle_cd_change(pcicdev_t *, pcic_socket_t *, uint8_t); |
| static uint_t pcic_cd_softint(caddr_t, caddr_t); |
| static uint8_t pcic_getb(pcicdev_t *, int, int); |
| static void pcic_putb(pcicdev_t *, int, int, int8_t); |
| static int pcic_set_vcc_level(pcicdev_t *, set_socket_t *); |
| static uint_t pcic_softintr(caddr_t, caddr_t); |
| |
| static void pcic_debounce(pcic_socket_t *); |
| static void pcic_do_resume(pcicdev_t *); |
| static void *pcic_add_debqueue(pcic_socket_t *, int); |
| static void pcic_rm_debqueue(void *); |
| static void pcic_deb_thread(); |
| |
| static boolean_t pcic_load_cardbus(pcicdev_t *pcic, const pcic_socket_t *sockp); |
| static void pcic_unload_cardbus(pcicdev_t *pcic, const pcic_socket_t *sockp); |
| static uint32_t pcic_getcb(pcicdev_t *pcic, int reg); |
| static void pcic_putcb(pcicdev_t *pcic, int reg, uint32_t value); |
| static void pcic_cb_enable_intr(dev_info_t *); |
| static void pcic_cb_disable_intr(dev_info_t *); |
| static void pcic_enable_io_intr(pcicdev_t *pcic, int socket, int irq); |
| static void pcic_disable_io_intr(pcicdev_t *pcic, int socket); |
| |
| static cb_nexus_cb_t pcic_cbnexus_ops = { |
| pcic_cb_enable_intr, |
| pcic_cb_disable_intr |
| }; |
| |
| static int pcic_exca_powerctl(pcicdev_t *pcic, int socket, int powerlevel); |
| static int pcic_cbus_powerctl(pcicdev_t *pcic, int socket); |
| |
| #if defined(__sparc) |
| static int pcic_fault(enum pci_fault_ops op, void *arg); |
| #endif |
| |
| |
| /* |
| * pcmcia interface operations structure |
| * this is the private interface that is exported to the nexus |
| */ |
| pcmcia_if_t pcic_if_ops = { |
| PCIF_MAGIC, |
| PCIF_VERSION, |
| pcic_callback, |
| pcic_get_adapter, |
| pcic_get_page, |
| pcic_get_socket, |
| pcic_get_status, |
| pcic_get_window, |
| pcic_inquire_adapter, |
| pcic_inquire_socket, |
| pcic_inquire_window, |
| pcic_reset_socket, |
| pcic_set_page, |
| pcic_set_window, |
| pcic_set_socket, |
| pcic_set_interrupt, |
| pcic_clear_interrupt, |
| NULL, |
| }; |
| |
| /* |
| * chip type identification routines |
| * this list of functions is searched until one of them succeeds |
| * or all fail. i82365SL is assumed if failed. |
| */ |
| static int pcic_ci_cirrus(pcicdev_t *); |
| static int pcic_ci_vadem(pcicdev_t *); |
| static int pcic_ci_ricoh(pcicdev_t *); |
| |
| int (*pcic_ci_funcs[])(pcicdev_t *) = { |
| pcic_ci_cirrus, |
| pcic_ci_vadem, |
| pcic_ci_ricoh, |
| NULL |
| }; |
| |
| static struct modldrv modldrv = { |
| &mod_driverops, /* Type of module. This one is a driver */ |
| "PCIC PCMCIA adapter driver", /* Name of the module. */ |
| &pcic_devops, /* driver ops */ |
| }; |
| |
| static struct modlinkage modlinkage = { |
| MODREV_1, (void *)&modldrv, NULL |
| }; |
| |
| int |
| _init() |
| { |
| int stat; |
| |
| /* Allocate soft state */ |
| if ((stat = ddi_soft_state_init(&pcic_soft_state_p, |
| SOFTC_SIZE, 2)) != DDI_SUCCESS) |
| return (stat); |
| |
| if ((stat = mod_install(&modlinkage)) != 0) |
| ddi_soft_state_fini(&pcic_soft_state_p); |
| |
| return (stat); |
| } |
| |
| int |
| _fini() |
| { |
| int stat = 0; |
| |
| if ((stat = mod_remove(&modlinkage)) != 0) |
| return (stat); |
| |
| if (pcic_deb_threadid) { |
| mutex_enter(&pcic_deb_mtx); |
| pcic_deb_threadid = 0; |
| while (!pcic_deb_threadid) |
| cv_wait(&pcic_deb_cv, &pcic_deb_mtx); |
| pcic_deb_threadid = 0; |
| mutex_exit(&pcic_deb_mtx); |
| |
| mutex_destroy(&pcic_deb_mtx); |
| cv_destroy(&pcic_deb_cv); |
| } |
| |
| ddi_soft_state_fini(&pcic_soft_state_p); |
| |
| return (stat); |
| } |
| |
| int |
| _info(struct modinfo *modinfop) |
| { |
| return (mod_info(&modlinkage, modinfop)); |
| } |
| |
| /* |
| * pcic_getinfo() |
| * provide instance/device information about driver |
| */ |
| /*ARGSUSED*/ |
| static int |
| pcic_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result) |
| { |
| anp_t *anp; |
| int error = DDI_SUCCESS; |
| minor_t minor; |
| |
| switch (cmd) { |
| case DDI_INFO_DEVT2DEVINFO: |
| minor = getminor((dev_t)arg); |
| minor &= 0x7f; |
| if (!(anp = ddi_get_soft_state(pcic_soft_state_p, minor))) |
| *result = NULL; |
| else |
| *result = anp->an_dip; |
| break; |
| case DDI_INFO_DEVT2INSTANCE: |
| minor = getminor((dev_t)arg); |
| minor &= 0x7f; |
| *result = (void *)((long)minor); |
| break; |
| default: |
| error = DDI_FAILURE; |
| break; |
| } |
| return (error); |
| } |
| |
| static int |
| pcic_probe(dev_info_t *dip) |
| { |
| int value; |
| ddi_device_acc_attr_t attr; |
| ddi_acc_handle_t handle; |
| uchar_t *index, *data; |
| |
| if (ddi_dev_is_sid(dip) == DDI_SUCCESS) |
| return (DDI_PROBE_DONTCARE); |
| |
| /* |
| * find a PCIC device (any vendor) |
| * while there can be up to 4 such devices in |
| * a system, we currently only look for 1 |
| * per probe. There will be up to 2 chips per |
| * instance since they share I/O space |
| */ |
| attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; |
| attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC; |
| attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; |
| |
| if (ddi_regs_map_setup(dip, PCIC_ISA_CONTROL_REG_NUM, |
| (caddr_t *)&index, |
| PCIC_ISA_CONTROL_REG_OFFSET, |
| PCIC_ISA_CONTROL_REG_LENGTH, |
| &attr, &handle) != DDI_SUCCESS) |
| return (DDI_PROBE_FAILURE); |
| |
| data = index + 1; |
| |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) |
| cmn_err(CE_CONT, "pcic_probe: entered\n"); |
| if (pcic_debug) |
| cmn_err(CE_CONT, "\tindex=%p\n", (void *)index); |
| #endif |
| ddi_put8(handle, index, PCIC_CHIP_REVISION); |
| ddi_put8(handle, data, 0); |
| value = ddi_get8(handle, data); |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) |
| cmn_err(CE_CONT, "\tchip revision register = %x\n", value); |
| #endif |
| if ((value & PCIC_REV_MASK) >= PCIC_REV_LEVEL_LOW && |
| (value & 0x30) == 0) { |
| /* |
| * we probably have a PCIC chip in the system |
| * do a little more checking. If we find one, |
| * reset everything in case of softboot |
| */ |
| ddi_put8(handle, index, PCIC_MAPPING_ENABLE); |
| ddi_put8(handle, data, 0); |
| value = ddi_get8(handle, data); |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) |
| cmn_err(CE_CONT, "\tzero test = %x\n", value); |
| #endif |
| /* should read back as zero */ |
| if (value == 0) { |
| /* |
| * we do have one and it is off the bus |
| */ |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) |
| cmn_err(CE_CONT, "pcic_probe: success\n"); |
| #endif |
| ddi_regs_map_free(&handle); |
| return (DDI_PROBE_SUCCESS); |
| } |
| } |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) |
| cmn_err(CE_CONT, "pcic_probe: failed\n"); |
| #endif |
| ddi_regs_map_free(&handle); |
| return (DDI_PROBE_FAILURE); |
| } |
| |
| /* |
| * These are just defaults they can also be changed via a property in the |
| * conf file. |
| */ |
| static int pci_config_reg_num = PCIC_PCI_CONFIG_REG_NUM; |
| static int pci_control_reg_num = PCIC_PCI_CONTROL_REG_NUM; |
| static int pcic_do_pcmcia_sr = 1; |
| static int pcic_use_cbpwrctl = PCF_CBPWRCTL; |
| |
| /* |
| * enable insertion/removal interrupt for 32bit cards |
| */ |
| static int |
| cardbus_enable_cd_intr(dev_info_t *dip) |
| { |
| ddi_acc_handle_t iohandle; |
| caddr_t ioaddr; |
| ddi_device_acc_attr_t attr; |
| attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; |
| attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; |
| attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; |
| (void) ddi_regs_map_setup(dip, 1, |
| (caddr_t *)&ioaddr, |
| 0, |
| 4096, |
| &attr, &iohandle); |
| |
| /* CSC Interrupt: Card detect interrupt on */ |
| ddi_put32(iohandle, (uint32_t *)(ioaddr+CB_STATUS_MASK), |
| ddi_get32(iohandle, |
| (uint32_t *)(ioaddr+CB_STATUS_MASK)) | CB_SE_CCDMASK); |
| |
| ddi_put32(iohandle, (uint32_t *)(ioaddr+CB_STATUS_EVENT), |
| ddi_get32(iohandle, (uint32_t *)(ioaddr+CB_STATUS_EVENT))); |
| |
| ddi_regs_map_free(&iohandle); |
| return (1); |
| } |
| |
| /* |
| * quiesce(9E) entry point. |
| * |
| * This function is called when the system is single-threaded at high |
| * PIL with preemption disabled. Therefore, this function must not be |
| * blocked. |
| * |
| * This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure. |
| * DDI_FAILURE indicates an error condition and should almost never happen. |
| */ |
| static int32_t |
| pcic_quiesce(dev_info_t *dip) |
| { |
| anp_t *anp = ddi_get_driver_private(dip); |
| pcicdev_t *pcic = anp->an_private; |
| int i; |
| |
| for (i = 0; i < pcic->pc_numsockets; i++) { |
| pcic_putb(pcic, i, PCIC_MANAGEMENT_INT, 0); |
| pcic_putb(pcic, i, PCIC_CARD_DETECT, 0); |
| pcic_putb(pcic, i, PCIC_MAPPING_ENABLE, 0); |
| /* disable interrupts and put card into RESET */ |
| pcic_putb(pcic, i, PCIC_INTERRUPT, 0); |
| /* poweroff socket */ |
| pcic_putb(pcic, i, PCIC_POWER_CONTROL, 0); |
| pcic_putcb(pcic, CB_CONTROL, 0); |
| } |
| |
| return (DDI_SUCCESS); |
| } |
| |
| /* |
| * pcic_attach() |
| * attach the PCIC (Intel 82365SL/CirrusLogic/Toshiba) driver |
| * to the system. This is a child of "sysbus" since that is where |
| * the hardware lives, but it provides services to the "pcmcia" |
| * nexus driver. It gives a pointer back via its private data |
| * structure which contains both the dip and socket services entry |
| * points |
| */ |
| static int |
| pcic_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) |
| { |
| anp_t *pcic_nexus; |
| pcicdev_t *pcic; |
| int irqlevel, value; |
| int pci_cfrn, pci_ctrn; |
| int i, j, smi, actual; |
| char *typename; |
| char bus_type[16] = "(unknown)"; |
| int len = sizeof (bus_type); |
| ddi_device_acc_attr_t attr; |
| anp_t *anp = ddi_get_driver_private(dip); |
| uint_t pri; |
| |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) { |
| cmn_err(CE_CONT, "pcic_attach: entered\n"); |
| } |
| #endif |
| switch (cmd) { |
| case DDI_ATTACH: |
| break; |
| case DDI_RESUME: |
| pcic = anp->an_private; |
| /* |
| * for now, this is a simulated resume. |
| * a real one may need different things. |
| */ |
| if (pcic != NULL && pcic->pc_flags & PCF_SUSPENDED) { |
| mutex_enter(&pcic->pc_lock); |
| /* should probe for new sockets showing up */ |
| pcic_setup_adapter(pcic); |
| pcic->pc_flags &= ~PCF_SUSPENDED; |
| mutex_exit(&pcic->pc_lock); |
| (void) pcmcia_begin_resume(dip); |
| |
| pcic_do_resume(pcic); |
| #ifdef CARDBUS |
| cardbus_restore_children(ddi_get_child(dip)); |
| #endif |
| |
| /* |
| * for complete implementation need END_RESUME (later) |
| */ |
| return (DDI_SUCCESS); |
| |
| } |
| return (DDI_SUCCESS); |
| default: |
| return (DDI_FAILURE); |
| } |
| |
| /* |
| * Allocate soft state associated with this instance. |
| */ |
| if (ddi_soft_state_zalloc(pcic_soft_state_p, |
| ddi_get_instance(dip)) != DDI_SUCCESS) { |
| cmn_err(CE_CONT, "pcic%d: Unable to alloc state\n", |
| ddi_get_instance(dip)); |
| return (DDI_FAILURE); |
| } |
| |
| pcic_nexus = ddi_get_soft_state(pcic_soft_state_p, |
| ddi_get_instance(dip)); |
| |
| pcic = kmem_zalloc(sizeof (pcicdev_t), KM_SLEEP); |
| |
| pcic->dip = dip; |
| pcic_nexus->an_dip = dip; |
| pcic_nexus->an_if = &pcic_if_ops; |
| pcic_nexus->an_private = pcic; |
| pcic->pc_numpower = sizeof (pcic_power)/sizeof (pcic_power[0]); |
| pcic->pc_power = pcic_power; |
| |
| pci_ctrn = ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_CANSLEEP, |
| "pci-control-reg-number", pci_control_reg_num); |
| pci_cfrn = ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_CANSLEEP, |
| "pci-config-reg-number", pci_config_reg_num); |
| |
| ddi_set_driver_private(dip, pcic_nexus); |
| |
| /* |
| * pcic->pc_irq is really the IPL level we want to run at |
| * set the default values here and override from intr spec |
| */ |
| pcic->pc_irq = ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_CANSLEEP, |
| "interrupt-priorities", -1); |
| |
| if (pcic->pc_irq == -1) { |
| int actual; |
| uint_t pri; |
| ddi_intr_handle_t hdl; |
| |
| /* see if intrspec tells us different */ |
| if (ddi_intr_alloc(dip, &hdl, DDI_INTR_TYPE_FIXED, |
| 0, 1, &actual, DDI_INTR_ALLOC_NORMAL) == DDI_SUCCESS) { |
| if (ddi_intr_get_pri(hdl, &pri) == DDI_SUCCESS) |
| pcic->pc_irq = pri; |
| else |
| pcic->pc_irq = LOCK_LEVEL + 1; |
| (void) ddi_intr_free(hdl); |
| } |
| } |
| pcic_nexus->an_ipl = pcic->pc_irq; |
| |
| /* |
| * Check our parent bus type. We do different things based on which |
| * bus we're on. |
| */ |
| if (ddi_prop_op(DDI_DEV_T_ANY, ddi_get_parent(dip), |
| PROP_LEN_AND_VAL_BUF, DDI_PROP_CANSLEEP, |
| "device_type", (caddr_t)&bus_type[0], &len) != |
| DDI_PROP_SUCCESS) { |
| if (ddi_prop_op(DDI_DEV_T_ANY, ddi_get_parent(dip), |
| PROP_LEN_AND_VAL_BUF, DDI_PROP_CANSLEEP, |
| "bus-type", (caddr_t)&bus_type[0], &len) != |
| DDI_PROP_SUCCESS) { |
| |
| cmn_err(CE_CONT, |
| "pcic%d: can't find parent bus type\n", |
| ddi_get_instance(dip)); |
| |
| kmem_free(pcic, sizeof (pcicdev_t)); |
| ddi_soft_state_free(pcic_soft_state_p, |
| ddi_get_instance(dip)); |
| return (DDI_FAILURE); |
| } |
| } /* ddi_prop_op("device_type") */ |
| |
| if (strcmp(bus_type, DEVI_PCI_NEXNAME) == 0 || |
| strcmp(bus_type, DEVI_PCIEX_NEXNAME) == 0) { |
| pcic->pc_flags = PCF_PCIBUS; |
| } else { |
| cmn_err(CE_WARN, "!pcic%d: non-pci mode (%s) not supported, " |
| "set BIOS to yenta mode if applicable\n", |
| ddi_get_instance(dip), bus_type); |
| kmem_free(pcic, sizeof (pcicdev_t)); |
| ddi_soft_state_free(pcic_soft_state_p, |
| ddi_get_instance(dip)); |
| return (DDI_FAILURE); |
| } |
| |
| if ((pcic->bus_speed = ddi_getprop(DDI_DEV_T_ANY, ddi_get_parent(dip), |
| DDI_PROP_CANSLEEP, |
| "clock-frequency", 0)) == 0) { |
| if (pcic->pc_flags & PCF_PCIBUS) |
| pcic->bus_speed = PCIC_PCI_DEF_SYSCLK; |
| else |
| pcic->bus_speed = PCIC_ISA_DEF_SYSCLK; |
| } else { |
| /* |
| * OBP can declare the speed in Hz... |
| */ |
| if (pcic->bus_speed > 1000000) |
| pcic->bus_speed /= 1000000; |
| } /* ddi_prop_op("clock-frequency") */ |
| |
| pcic->pc_io_type = PCIC_IO_TYPE_82365SL; /* default mode */ |
| |
| #ifdef PCIC_DEBUG |
| if (pcic_debug) { |
| cmn_err(CE_CONT, |
| "pcic%d: parent bus type = [%s], speed = %d MHz\n", |
| ddi_get_instance(dip), |
| bus_type, pcic->bus_speed); |
| } |
| #endif |
| |
| /* |
| * The reg properties on a PCI node are different than those |
| * on a non-PCI node. Handle that difference here. |
| * If it turns out to be a CardBus chip, we have even more |
| * differences. |
| */ |
| if (pcic->pc_flags & PCF_PCIBUS) { |
| int class_code; |
| #if defined(__i386) || defined(__amd64) |
| pcic->pc_base = 0x1000000; |
| pcic->pc_bound = (uint32_t)~0; |
| pcic->pc_iobase = 0x1000; |
| pcic->pc_iobound = 0xefff; |
| #elif defined(__sparc) |
| pcic->pc_base = 0x0; |
| pcic->pc_bound = (uint32_t)~0; |
| pcic->pc_iobase = 0x00000; |
| pcic->pc_iobound = 0xffff; |
| #endif |
| |
| /* usually need to get at config space so map first */ |
| attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; |
| attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; |
| attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; |
| |
| if (ddi_regs_map_setup(dip, pci_cfrn, |
| (caddr_t *)&pcic->cfgaddr, |
| PCIC_PCI_CONFIG_REG_OFFSET, |
| PCIC_PCI_CONFIG_REG_LENGTH, |
| &attr, |
| &pcic->cfg_handle) != |
| DDI_SUCCESS) { |
| cmn_err(CE_CONT, |
| "pcic%d: unable to map config space" |
| "regs\n", |
| ddi_get_instance(dip)); |
| |
| kmem_free(pcic, sizeof (pcicdev_t)); |
| return (DDI_FAILURE); |
| } /* ddi_regs_map_setup */ |
| |
| class_code = ddi_getprop(DDI_DEV_T_ANY, dip, |
| DDI_PROP_CANSLEEP|DDI_PROP_DONTPASS, |
| "class-code", -1); |
| #ifdef PCIC_DEBUG |
| if (pcic_debug) { |
| cmn_err(CE_CONT, "pcic_attach class_code=%x\n", |
| class_code); |
| } |
| #endif |
| |
| switch (class_code) { |
| case PCIC_PCI_CARDBUS: |
| pcic->pc_flags |= PCF_CARDBUS; |
| pcic->pc_io_type = PCIC_IO_TYPE_YENTA; |
| /* |
| * Get access to the adapter registers on the |
| * PCI bus. A 4K memory page |
| */ |
| #if defined(PCIC_DEBUG) |
| pcic_err(dip, 8, "Is Cardbus device\n"); |
| if (pcic_debug) { |
| int nr; |
| long rs; |
| (void) ddi_dev_nregs(dip, &nr); |
| pcic_err(dip, 9, "\tdev, cfgaddr 0x%p," |
| "cfghndl 0x%p nregs %d", |
| (void *)pcic->cfgaddr, |
| (void *)pcic->cfg_handle, nr); |
| |
| (void) ddi_dev_regsize(dip, |
| PCIC_PCI_CONTROL_REG_NUM, &rs); |
| |
| pcic_err(dip, 9, "\tsize of reg %d is 0x%x\n", |
| PCIC_PCI_CONTROL_REG_NUM, (int)rs); |
| } |
| #endif |
| attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; |
| attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; |
| attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; |
| |
| if (ddi_regs_map_setup(dip, pci_ctrn, |
| (caddr_t *)&pcic->ioaddr, |
| PCIC_PCI_CONTROL_REG_OFFSET, |
| PCIC_CB_CONTROL_REG_LENGTH, |
| &attr, &pcic->handle) != |
| DDI_SUCCESS) { |
| cmn_err(CE_CONT, |
| "pcic%d: unable to map PCI regs\n", |
| ddi_get_instance(dip)); |
| ddi_regs_map_free(&pcic->cfg_handle); |
| kmem_free(pcic, sizeof (pcicdev_t)); |
| return (DDI_FAILURE); |
| } /* ddi_regs_map_setup */ |
| |
| /* |
| * Find out the chip type - If we're on a PCI bus, |
| * the adapter has that information in the PCI |
| * config space. |
| * Note that we call pcic_find_pci_type here since |
| * it needs a valid mapped pcic->handle to |
| * access some of the adapter registers in |
| * some cases. |
| */ |
| if (pcic_find_pci_type(pcic) != DDI_SUCCESS) { |
| ddi_regs_map_free(&pcic->handle); |
| ddi_regs_map_free(&pcic->cfg_handle); |
| kmem_free(pcic, sizeof (pcicdev_t)); |
| cmn_err(CE_WARN, "pcic: %s: unsupported " |
| "bridge\n", ddi_get_name_addr(dip)); |
| return (DDI_FAILURE); |
| } |
| break; |
| |
| default: |
| case PCIC_PCI_PCMCIA: |
| /* |
| * Get access to the adapter IO registers on the |
| * PCI bus config space. |
| */ |
| attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; |
| attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; |
| attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; |
| |
| /* |
| * We need a default mapping to the adapter's IO |
| * control register space. For most adapters |
| * that are of class PCIC_PCI_PCMCIA (or of |
| * a default class) the control registers |
| * will be using the 82365-type control/data |
| * format. |
| */ |
| if (ddi_regs_map_setup(dip, pci_ctrn, |
| (caddr_t *)&pcic->ioaddr, |
| PCIC_PCI_CONTROL_REG_OFFSET, |
| PCIC_PCI_CONTROL_REG_LENGTH, |
| &attr, |
| &pcic->handle) != DDI_SUCCESS) { |
| cmn_err(CE_CONT, |
| "pcic%d: unable to map PCI regs\n", |
| ddi_get_instance(dip)); |
| ddi_regs_map_free(&pcic->cfg_handle); |
| kmem_free(pcic, sizeof (pcicdev_t)); |
| return (DDI_FAILURE); |
| } /* ddi_regs_map_setup */ |
| |
| /* |
| * Find out the chip type - If we're on a PCI bus, |
| * the adapter has that information in the PCI |
| * config space. |
| * Note that we call pcic_find_pci_type here since |
| * it needs a valid mapped pcic->handle to |
| * access some of the adapter registers in |
| * some cases. |
| */ |
| if (pcic_find_pci_type(pcic) != DDI_SUCCESS) { |
| ddi_regs_map_free(&pcic->handle); |
| ddi_regs_map_free(&pcic->cfg_handle); |
| kmem_free(pcic, sizeof (pcicdev_t)); |
| cmn_err(CE_WARN, "pcic: %s: unsupported " |
| "bridge\n", |
| ddi_get_name_addr(dip)); |
| return (DDI_FAILURE); |
| } |
| |
| /* |
| * Some PCI-PCMCIA(R2) adapters are Yenta-compliant |
| * for extended registers even though they are |
| * not CardBus adapters. For those adapters, |
| * re-map pcic->handle to be large enough to |
| * encompass the Yenta registers. |
| */ |
| switch (pcic->pc_type) { |
| case PCIC_TI_PCI1031: |
| ddi_regs_map_free(&pcic->handle); |
| |
| if (ddi_regs_map_setup(dip, |
| PCIC_PCI_CONTROL_REG_NUM, |
| (caddr_t *)&pcic->ioaddr, |
| PCIC_PCI_CONTROL_REG_OFFSET, |
| PCIC_CB_CONTROL_REG_LENGTH, |
| &attr, |
| &pcic->handle) != DDI_SUCCESS) { |
| cmn_err(CE_CONT, |
| "pcic%d: unable to map " |
| "PCI regs\n", |
| ddi_get_instance(dip)); |
| ddi_regs_map_free(&pcic->cfg_handle); |
| kmem_free(pcic, sizeof (pcicdev_t)); |
| return (DDI_FAILURE); |
| } /* ddi_regs_map_setup */ |
| break; |
| default: |
| break; |
| } /* switch (pcic->pc_type) */ |
| break; |
| } /* switch (class_code) */ |
| } else { |
| /* |
| * We're not on a PCI bus, so assume an ISA bus type |
| * register property. Get access to the adapter IO |
| * registers on a non-PCI bus. |
| */ |
| attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; |
| attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC; |
| attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; |
| pcic->mem_reg_num = PCIC_ISA_MEM_REG_NUM; |
| pcic->io_reg_num = PCIC_ISA_IO_REG_NUM; |
| |
| if (ddi_regs_map_setup(dip, PCIC_ISA_CONTROL_REG_NUM, |
| (caddr_t *)&pcic->ioaddr, |
| PCIC_ISA_CONTROL_REG_OFFSET, |
| PCIC_ISA_CONTROL_REG_LENGTH, |
| &attr, |
| &pcic->handle) != DDI_SUCCESS) { |
| cmn_err(CE_CONT, |
| "pcic%d: unable to map ISA registers\n", |
| ddi_get_instance(dip)); |
| |
| kmem_free(pcic, sizeof (pcicdev_t)); |
| return (DDI_FAILURE); |
| } /* ddi_regs_map_setup */ |
| |
| /* ISA bus is limited to 24-bits, but not first 640K */ |
| pcic->pc_base = 0xd0000; |
| pcic->pc_bound = (uint32_t)~0; |
| pcic->pc_iobase = 0x1000; |
| pcic->pc_iobound = 0xefff; |
| } /* !PCF_PCIBUS */ |
| |
| #ifdef PCIC_DEBUG |
| if (pcic_debug) { |
| cmn_err(CE_CONT, "pcic_attach pc_flags=%x pc_type=%x\n", |
| pcic->pc_flags, pcic->pc_type); |
| } |
| #endif |
| |
| /* |
| * Setup various adapter registers for the PCI case. For the |
| * non-PCI case, find out the chip type. |
| */ |
| if (pcic->pc_flags & PCF_PCIBUS) { |
| int iline; |
| #if defined(__sparc) |
| iline = 0; |
| #else |
| iline = cardbus_validate_iline(dip, pcic->cfg_handle); |
| #endif |
| |
| /* set flags and socket counts based on chip type */ |
| switch (pcic->pc_type) { |
| uint32_t cfg; |
| case PCIC_INTEL_i82092: |
| cfg = ddi_get8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_82092_PCICON); |
| /* we can only support 4 Socket version */ |
| if (cfg & PCIC_82092_4_SOCKETS) { |
| pcic->pc_numsockets = 4; |
| pcic->pc_type = PCIC_INTEL_i82092; |
| if (iline != 0xFF) |
| pcic->pc_intr_mode = |
| PCIC_INTR_MODE_PCI_1; |
| else |
| pcic->pc_intr_mode = PCIC_INTR_MODE_ISA; |
| } else { |
| cmn_err(CE_CONT, |
| "pcic%d: Intel 82092 adapter " |
| "in unsupported configuration: 0x%x", |
| ddi_get_instance(pcic->dip), cfg); |
| pcic->pc_numsockets = 0; |
| } /* PCIC_82092_4_SOCKETS */ |
| break; |
| case PCIC_CL_PD6730: |
| case PCIC_CL_PD6729: |
| pcic->pc_intr_mode = PCIC_INTR_MODE_PCI_1; |
| cfg = ddi_getprop(DDI_DEV_T_ANY, dip, |
| DDI_PROP_CANSLEEP, |
| "interrupts", 0); |
| /* if not interrupt pin then must use ISA style IRQs */ |
| if (cfg == 0 || iline == 0xFF) |
| pcic->pc_intr_mode = PCIC_INTR_MODE_ISA; |
| else { |
| /* |
| * we have the option to use PCI interrupts. |
| * this might not be optimal but in some cases |
| * is the only thing possible (sparc case). |
| * we now deterine what is possible. |
| */ |
| pcic->pc_intr_mode = PCIC_INTR_MODE_PCI_1; |
| } |
| pcic->pc_numsockets = 2; |
| pcic->pc_flags |= PCF_IO_REMAP; |
| break; |
| case PCIC_TI_PCI1031: |
| /* this chip doesn't do CardBus but looks like one */ |
| pcic->pc_flags &= ~PCF_CARDBUS; |
| /* FALLTHROUGH */ |
| default: |
| pcic->pc_flags |= PCF_IO_REMAP; |
| /* FALLTHROUGH */ |
| /* indicate feature even if not supported */ |
| pcic->pc_flags |= PCF_DMA | PCF_ZV; |
| /* Not sure if these apply to all these chips */ |
| pcic->pc_flags |= (PCF_VPPX|PCF_33VCAP); |
| pcic->pc_flags |= pcic_use_cbpwrctl; |
| |
| pcic->pc_numsockets = 1; /* one per function */ |
| if (iline != 0xFF) { |
| uint8_t cfg; |
| pcic->pc_intr_mode = PCIC_INTR_MODE_PCI_1; |
| |
| cfg = ddi_get8(pcic->cfg_handle, |
| (pcic->cfgaddr + PCIC_BRIDGE_CTL_REG)); |
| cfg &= (~PCIC_FUN_INT_MOD_ISA); |
| ddi_put8(pcic->cfg_handle, (pcic->cfgaddr + |
| PCIC_BRIDGE_CTL_REG), cfg); |
| } |
| else |
| pcic->pc_intr_mode = PCIC_INTR_MODE_ISA; |
| pcic->pc_io_type = PCIC_IOTYPE_YENTA; |
| break; |
| } |
| } else { |
| /* |
| * We're not on a PCI bus so do some more |
| * checking for adapter type here. |
| * For the non-PCI bus case: |
| * It could be any one of a number of different chips |
| * If we can't determine anything else, it is assumed |
| * to be an Intel 82365SL. The Cirrus Logic PD6710 |
| * has an extension register that provides unique |
| * identification. Toshiba chip isn't detailed as yet. |
| */ |
| |
| /* Init the CL id mode */ |
| pcic_putb(pcic, 0, PCIC_CHIP_INFO, 0); |
| value = pcic_getb(pcic, 0, PCIC_CHIP_INFO); |
| |
| /* default to Intel i82365SL and then refine */ |
| pcic->pc_type = PCIC_I82365SL; |
| pcic->pc_chipname = PCIC_TYPE_I82365SL; |
| for (value = 0; pcic_ci_funcs[value] != NULL; value++) { |
| /* go until one succeeds or none left */ |
| if (pcic_ci_funcs[value](pcic)) |
| break; |
| } |
| |
| /* any chip specific flags get set here */ |
| switch (pcic->pc_type) { |
| case PCIC_CL_PD6722: |
| pcic->pc_flags |= PCF_DMA; |
| } |
| |
| for (i = 0; i < PCIC_MAX_SOCKETS; i++) { |
| /* |
| * look for total number of sockets. |
| * basically check each possible socket for |
| * presence like in probe |
| */ |
| |
| /* turn all windows off */ |
| pcic_putb(pcic, i, PCIC_MAPPING_ENABLE, 0); |
| value = pcic_getb(pcic, i, PCIC_MAPPING_ENABLE); |
| |
| /* |
| * if a zero is read back, then this socket |
| * might be present. It would be except for |
| * some systems that map the secondary PCIC |
| * chip space back to the first. |
| */ |
| if (value != 0) { |
| /* definitely not so skip */ |
| /* note: this is for Compaq support */ |
| continue; |
| } |
| |
| /* further tests */ |
| value = pcic_getb(pcic, i, PCIC_CHIP_REVISION) & |
| PCIC_REV_MASK; |
| if (!(value >= PCIC_REV_LEVEL_LOW && |
| value <= PCIC_REV_LEVEL_HI)) |
| break; |
| |
| pcic_putb(pcic, i, PCIC_SYSMEM_0_STARTLOW, 0xaa); |
| pcic_putb(pcic, i, PCIC_SYSMEM_1_STARTLOW, 0x55); |
| value = pcic_getb(pcic, i, PCIC_SYSMEM_0_STARTLOW); |
| |
| j = pcic_getb(pcic, i, PCIC_SYSMEM_1_STARTLOW); |
| if (value != 0xaa || j != 0x55) |
| break; |
| |
| /* |
| * at this point we know if we have hardware |
| * of some type and not just the bus holding |
| * a pattern for us. We still have to determine |
| * the case where more than 2 sockets are |
| * really the same due to peculiar mappings of |
| * hardware. |
| */ |
| j = pcic->pc_numsockets++; |
| pcic->pc_sockets[j].pcs_flags = 0; |
| pcic->pc_sockets[j].pcs_io = pcic->ioaddr; |
| pcic->pc_sockets[j].pcs_socket = i; |
| |
| /* put PC Card into RESET, just in case */ |
| value = pcic_getb(pcic, i, PCIC_INTERRUPT); |
| pcic_putb(pcic, i, PCIC_INTERRUPT, |
| value & ~PCIC_RESET); |
| } |
| |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) |
| cmn_err(CE_CONT, "num sockets = %d\n", |
| pcic->pc_numsockets); |
| #endif |
| if (pcic->pc_numsockets == 0) { |
| ddi_regs_map_free(&pcic->handle); |
| kmem_free(pcic, sizeof (pcicdev_t)); |
| return (DDI_FAILURE); |
| } |
| |
| /* |
| * need to think this through again in light of |
| * Compaq not following the model that all the |
| * chip vendors recommend. IBM 755 seems to be |
| * afflicted as well. Basically, if the vendor |
| * wired things wrong, socket 0 responds for socket 2 |
| * accesses, etc. |
| */ |
| if (pcic->pc_numsockets > 2) { |
| int count = pcic->pc_numsockets / 4; |
| for (i = 0; i < count; i++) { |
| /* put pattern into socket 0 */ |
| pcic_putb(pcic, i, |
| PCIC_SYSMEM_0_STARTLOW, 0x11); |
| |
| /* put pattern into socket 2 */ |
| pcic_putb(pcic, i + 2, |
| PCIC_SYSMEM_0_STARTLOW, 0x33); |
| |
| /* read back socket 0 */ |
| value = pcic_getb(pcic, i, |
| PCIC_SYSMEM_0_STARTLOW); |
| |
| /* read back chip 1 socket 0 */ |
| j = pcic_getb(pcic, i + 2, |
| PCIC_SYSMEM_0_STARTLOW); |
| if (j == value) { |
| pcic->pc_numsockets -= 2; |
| } |
| } |
| } |
| |
| smi = 0xff; /* no more override */ |
| |
| if (ddi_getprop(DDI_DEV_T_NONE, dip, |
| DDI_PROP_DONTPASS, "need-mult-irq", |
| 0xffff) != 0xffff) |
| pcic->pc_flags |= PCF_MULT_IRQ; |
| |
| } /* !PCF_PCIBUS */ |
| |
| /* |
| * some platforms/busses need to have resources setup |
| * this is temporary until a real resource allocator is |
| * implemented. |
| */ |
| |
| pcic_init_assigned(dip); |
| |
| typename = pcic->pc_chipname; |
| |
| #ifdef PCIC_DEBUG |
| if (pcic_debug) { |
| int nregs, nintrs; |
| |
| if (ddi_dev_nregs(dip, &nregs) != DDI_SUCCESS) |
| nregs = 0; |
| |
| if (ddi_dev_nintrs(dip, &nintrs) != DDI_SUCCESS) |
| nintrs = 0; |
| |
| cmn_err(CE_CONT, |
| "pcic%d: %d register sets, %d interrupts\n", |
| ddi_get_instance(dip), nregs, nintrs); |
| |
| nintrs = 0; |
| while (nregs--) { |
| off_t size; |
| |
| if (ddi_dev_regsize(dip, nintrs, &size) == |
| DDI_SUCCESS) { |
| cmn_err(CE_CONT, |
| "\tregnum %d size %ld (0x%lx)" |
| "bytes", |
| nintrs, size, size); |
| if (nintrs == |
| (pcic->pc_io_type == PCIC_IO_TYPE_82365SL ? |
| PCIC_ISA_CONTROL_REG_NUM : |
| PCIC_PCI_CONTROL_REG_NUM)) |
| cmn_err(CE_CONT, |
| " mapped at: 0x%p\n", |
| (void *)pcic->ioaddr); |
| else |
| cmn_err(CE_CONT, "\n"); |
| } else { |
| cmn_err(CE_CONT, |
| "\tddi_dev_regsize(rnumber" |
| "= %d) returns DDI_FAILURE\n", |
| nintrs); |
| } |
| nintrs++; |
| } /* while */ |
| } /* if (pcic_debug) */ |
| #endif |
| |
| cv_init(&pcic->pm_cv, NULL, CV_DRIVER, NULL); |
| |
| if (!ddi_getprop(DDI_DEV_T_NONE, dip, DDI_PROP_DONTPASS, |
| "disable-audio", 0)) |
| pcic->pc_flags |= PCF_AUDIO; |
| |
| if (ddi_getprop(DDI_DEV_T_ANY, dip, DDI_PROP_CANSLEEP, |
| "disable-cardbus", 0)) |
| pcic->pc_flags &= ~PCF_CARDBUS; |
| |
| (void) ddi_prop_update_string(DDI_DEV_T_NONE, dip, PCICPROP_CTL, |
| typename); |
| |
| /* |
| * Init all socket SMI levels to 0 (no SMI) |
| */ |
| for (i = 0; i < PCIC_MAX_SOCKETS; i++) { |
| pcic->pc_sockets[i].pcs_smi = 0; |
| pcic->pc_sockets[i].pcs_debounce_id = 0; |
| pcic->pc_sockets[i].pcs_pcic = pcic; |
| } |
| pcic->pc_lastreg = -1; /* just to make sure we are in sync */ |
| |
| /* |
| * Setup the IRQ handler(s) |
| */ |
| switch (pcic->pc_intr_mode) { |
| int xx; |
| case PCIC_INTR_MODE_ISA: |
| /* |
| * On a non-PCI bus, we just use whatever SMI IRQ level was |
| * specified above, and the IO IRQ levels are allocated |
| * dynamically. |
| */ |
| for (xx = 15, smi = 0; xx >= 0; xx--) { |
| if (PCIC_IRQ(xx) & |
| PCIC_AVAIL_IRQS) { |
| smi = pcmcia_get_intr(dip, xx); |
| if (smi >= 0) |
| break; |
| } |
| } |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) |
| cmn_err(CE_NOTE, "\tselected IRQ %d as SMI\n", smi); |
| #endif |
| /* init to same so share is easy */ |
| for (i = 0; i < pcic->pc_numsockets; i++) |
| pcic->pc_sockets[i].pcs_smi = smi; |
| /* any special handling of IRQ levels */ |
| if (pcic->pc_flags & PCF_MULT_IRQ) { |
| for (i = 2; i < pcic->pc_numsockets; i++) { |
| if ((i & 1) == 0) { |
| int xx; |
| for (xx = 15, smi = 0; xx >= 0; xx--) { |
| if (PCIC_IRQ(xx) & |
| PCIC_AVAIL_IRQS) { |
| smi = |
| pcmcia_get_intr(dip, |
| xx); |
| if (smi >= 0) |
| break; |
| } |
| } |
| } |
| if (smi >= 0) |
| pcic->pc_sockets[i].pcs_smi = smi; |
| } |
| } |
| pcic->pc_intr_htblp = kmem_alloc(pcic->pc_numsockets * |
| sizeof (ddi_intr_handle_t), KM_SLEEP); |
| for (i = 0, irqlevel = -1; i < pcic->pc_numsockets; i++) { |
| struct intrspec *ispecp; |
| struct ddi_parent_private_data *pdp; |
| |
| if (irqlevel == pcic->pc_sockets[i].pcs_smi) |
| continue; |
| else { |
| irqlevel = pcic->pc_sockets[i].pcs_smi; |
| } |
| /* |
| * now convert the allocated IRQ into an intrspec |
| * and ask our parent to add it. Don't use |
| * the ddi_add_intr since we don't have a |
| * default intrspec in all cases. |
| * |
| * note: this sort of violates DDI but we don't |
| * get hardware intrspecs for many of the devices. |
| * at the same time, we know how to allocate them |
| * so we do the right thing. |
| */ |
| if (ddi_intr_alloc(dip, &pcic->pc_intr_htblp[i], |
| DDI_INTR_TYPE_FIXED, 0, 1, &actual, |
| DDI_INTR_ALLOC_NORMAL) != DDI_SUCCESS) { |
| cmn_err(CE_WARN, "%s: ddi_intr_alloc failed", |
| ddi_get_name(dip)); |
| goto isa_exit1; |
| } |
| |
| /* |
| * See earlier note: |
| * Since some devices don't have 'intrspec' |
| * we make one up in rootnex. |
| * |
| * However, it is not properly initialized as |
| * the data it needs is present in this driver |
| * and there is no interface to pass that up. |
| * Specially 'irqlevel' is very important and |
| * it is part of pcic struct. |
| * |
| * Set 'intrspec' up here; otherwise adding the |
| * interrupt will fail. |
| */ |
| pdp = ddi_get_parent_data(dip); |
| ispecp = (struct intrspec *)&pdp->par_intr[0]; |
| ispecp->intrspec_vec = irqlevel; |
| ispecp->intrspec_pri = pcic->pc_irq; |
| |
| /* Stay compatible w/ PCMCIA */ |
| pcic->pc_pri = (ddi_iblock_cookie_t) |
| (uintptr_t)pcic->pc_irq; |
| pcic->pc_dcookie.idev_priority = |
| (uintptr_t)pcic->pc_pri; |
| pcic->pc_dcookie.idev_vector = (ushort_t)irqlevel; |
| |
| (void) ddi_intr_set_pri(pcic->pc_intr_htblp[i], |
| pcic->pc_irq); |
| |
| if (i == 0) { |
| mutex_init(&pcic->intr_lock, NULL, MUTEX_DRIVER, |
| DDI_INTR_PRI(pcic->pc_irq)); |
| mutex_init(&pcic->pc_lock, NULL, MUTEX_DRIVER, |
| NULL); |
| } |
| |
| if (ddi_intr_add_handler(pcic->pc_intr_htblp[i], |
| pcic_intr, (caddr_t)pcic, NULL)) { |
| cmn_err(CE_WARN, |
| "%s: ddi_intr_add_handler failed", |
| ddi_get_name(dip)); |
| goto isa_exit2; |
| } |
| |
| if (ddi_intr_enable(pcic->pc_intr_htblp[i])) { |
| cmn_err(CE_WARN, "%s: ddi_intr_enable failed", |
| ddi_get_name(dip)); |
| for (j = i; j < 0; j--) |
| (void) ddi_intr_remove_handler( |
| pcic->pc_intr_htblp[j]); |
| goto isa_exit2; |
| } |
| } |
| break; |
| case PCIC_INTR_MODE_PCI_1: |
| case PCIC_INTR_MODE_PCI: |
| /* |
| * If we're on a PCI bus, we route all interrupts, both SMI |
| * and IO interrupts, through a single interrupt line. |
| * Assign the SMI IRQ level to the IO IRQ level here. |
| */ |
| pcic->pc_pci_intr_hdlp = kmem_alloc(sizeof (ddi_intr_handle_t), |
| KM_SLEEP); |
| if (ddi_intr_alloc(dip, pcic->pc_pci_intr_hdlp, |
| DDI_INTR_TYPE_FIXED, 0, 1, &actual, |
| DDI_INTR_ALLOC_NORMAL) != DDI_SUCCESS) |
| goto pci_exit1; |
| |
| if (ddi_intr_get_pri(pcic->pc_pci_intr_hdlp[0], |
| &pri) != DDI_SUCCESS) { |
| (void) ddi_intr_free(pcic->pc_pci_intr_hdlp[0]); |
| goto pci_exit1; |
| } |
| |
| pcic->pc_pri = (void *)(uintptr_t)pri; |
| mutex_init(&pcic->intr_lock, NULL, MUTEX_DRIVER, pcic->pc_pri); |
| mutex_init(&pcic->pc_lock, NULL, MUTEX_DRIVER, NULL); |
| |
| if (ddi_intr_add_handler(pcic->pc_pci_intr_hdlp[0], |
| pcic_intr, (caddr_t)pcic, NULL)) |
| goto pci_exit2; |
| |
| if (ddi_intr_enable(pcic->pc_pci_intr_hdlp[0])) { |
| (void) ddi_intr_remove_handler( |
| pcic->pc_pci_intr_hdlp[0]); |
| goto pci_exit2; |
| } |
| |
| /* Stay compatible w/ PCMCIA */ |
| pcic->pc_dcookie.idev_priority = (ushort_t)pri; |
| |
| /* init to same (PCI) so share is easy */ |
| for (i = 0; i < pcic->pc_numsockets; i++) |
| pcic->pc_sockets[i].pcs_smi = 0xF; /* any valid */ |
| break; |
| } |
| |
| /* |
| * Setup the adapter hardware to some reasonable defaults. |
| */ |
| mutex_enter(&pcic->pc_lock); |
| /* mark the driver state as attached */ |
| pcic->pc_flags |= PCF_ATTACHED; |
| pcic_setup_adapter(pcic); |
| |
| for (j = 0; j < pcic->pc_numsockets; j++) |
| if (ddi_intr_add_softint(dip, |
| &pcic->pc_sockets[j].pcs_cd_softint_hdl, |
| PCIC_SOFTINT_PRI_VAL, pcic_cd_softint, |
| (caddr_t)&pcic->pc_sockets[j]) != DDI_SUCCESS) |
| goto pci_exit2; |
| |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) |
| cmn_err(CE_CONT, "type = %s sockets = %d\n", typename, |
| pcic->pc_numsockets); |
| #endif |
| |
| pcic_nexus->an_iblock = &pcic->pc_pri; |
| pcic_nexus->an_idev = &pcic->pc_dcookie; |
| |
| mutex_exit(&pcic->pc_lock); |
| |
| #ifdef CARDBUS |
| (void) cardbus_enable_cd_intr(dip); |
| if (pcic_debug) { |
| |
| cardbus_dump_pci_config(dip); |
| cardbus_dump_socket(dip); |
| } |
| |
| /* |
| * Give the Cardbus misc module a chance to do it's per-adapter |
| * instance setup. Note that there is no corresponding detach() |
| * call. |
| */ |
| if (pcic->pc_flags & PCF_CARDBUS) |
| if (cardbus_attach(dip, &pcic_cbnexus_ops) != DDI_SUCCESS) { |
| cmn_err(CE_CONT, |
| "pcic_attach: cardbus_attach failed\n"); |
| goto pci_exit2; |
| } |
| #endif |
| |
| /* |
| * Give the PCMCIA misc module a chance to do it's per-adapter |
| * instance setup. |
| */ |
| if ((i = pcmcia_attach(dip, pcic_nexus)) != DDI_SUCCESS) |
| goto pci_exit2; |
| |
| if (pcic_maxinst == -1) { |
| /* This assumes that all instances run at the same IPL. */ |
| mutex_init(&pcic_deb_mtx, NULL, MUTEX_DRIVER, NULL); |
| cv_init(&pcic_deb_cv, NULL, CV_DRIVER, NULL); |
| pcic_deb_threadid = thread_create((caddr_t)NULL, 0, |
| pcic_deb_thread, (caddr_t)NULL, 0, &p0, TS_RUN, |
| v.v_maxsyspri - 2); |
| } |
| pcic_maxinst = max(pcic_maxinst, ddi_get_instance(dip)); |
| /* |
| * Setup a debounce timeout to do an initial card detect |
| * and enable interrupts. |
| */ |
| for (j = 0; j < pcic->pc_numsockets; j++) { |
| pcic->pc_sockets[j].pcs_debounce_id = |
| pcic_add_debqueue(&pcic->pc_sockets[j], |
| drv_usectohz(pcic_debounce_time)); |
| } |
| |
| return (i); |
| |
| isa_exit2: |
| mutex_destroy(&pcic->intr_lock); |
| mutex_destroy(&pcic->pc_lock); |
| for (j = i; j < 0; j--) |
| (void) ddi_intr_free(pcic->pc_intr_htblp[j]); |
| isa_exit1: |
| (void) pcmcia_return_intr(dip, pcic->pc_sockets[i].pcs_smi); |
| ddi_regs_map_free(&pcic->handle); |
| if (pcic->pc_flags & PCF_PCIBUS) |
| ddi_regs_map_free(&pcic->cfg_handle); |
| kmem_free(pcic->pc_intr_htblp, pcic->pc_numsockets * |
| sizeof (ddi_intr_handle_t)); |
| kmem_free(pcic, sizeof (pcicdev_t)); |
| return (DDI_FAILURE); |
| |
| pci_exit2: |
| mutex_destroy(&pcic->intr_lock); |
| mutex_destroy(&pcic->pc_lock); |
| (void) ddi_intr_free(pcic->pc_pci_intr_hdlp[0]); |
| pci_exit1: |
| ddi_regs_map_free(&pcic->handle); |
| if (pcic->pc_flags & PCF_PCIBUS) |
| ddi_regs_map_free(&pcic->cfg_handle); |
| kmem_free(pcic->pc_pci_intr_hdlp, sizeof (ddi_intr_handle_t)); |
| kmem_free(pcic, sizeof (pcicdev_t)); |
| return (DDI_FAILURE); |
| } |
| |
| /* |
| * pcic_detach() |
| * request to detach from the system |
| */ |
| static int |
| pcic_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) |
| { |
| anp_t *anp = ddi_get_driver_private(dip); |
| pcicdev_t *pcic = anp->an_private; |
| int i; |
| |
| switch (cmd) { |
| case DDI_DETACH: |
| /* don't detach if the nexus still talks to us */ |
| if (pcic->pc_callback != NULL) |
| return (DDI_FAILURE); |
| |
| /* kill off the pm simulation */ |
| if (pcic->pc_pmtimer) |
| (void) untimeout(pcic->pc_pmtimer); |
| |
| /* turn everything off for all sockets and chips */ |
| for (i = 0; i < pcic->pc_numsockets; i++) { |
| if (pcic->pc_sockets[i].pcs_debounce_id) |
| pcic_rm_debqueue( |
| pcic->pc_sockets[i].pcs_debounce_id); |
| pcic->pc_sockets[i].pcs_debounce_id = 0; |
| |
| pcic_putb(pcic, i, PCIC_MANAGEMENT_INT, 0); |
| pcic_putb(pcic, i, PCIC_CARD_DETECT, 0); |
| pcic_putb(pcic, i, PCIC_MAPPING_ENABLE, 0); |
| /* disable interrupts and put card into RESET */ |
| pcic_putb(pcic, i, PCIC_INTERRUPT, 0); |
| } |
| (void) ddi_intr_disable(pcic->pc_pci_intr_hdlp[0]); |
| (void) ddi_intr_remove_handler(pcic->pc_pci_intr_hdlp[0]); |
| (void) ddi_intr_free(pcic->pc_pci_intr_hdlp[0]); |
| kmem_free(pcic->pc_pci_intr_hdlp, sizeof (ddi_intr_handle_t)); |
| pcic->pc_flags = 0; |
| mutex_destroy(&pcic->pc_lock); |
| mutex_destroy(&pcic->intr_lock); |
| cv_destroy(&pcic->pm_cv); |
| if (pcic->pc_flags & PCF_PCIBUS) |
| ddi_regs_map_free(&pcic->cfg_handle); |
| if (pcic->handle) |
| ddi_regs_map_free(&pcic->handle); |
| kmem_free(pcic, sizeof (pcicdev_t)); |
| ddi_soft_state_free(pcic_soft_state_p, ddi_get_instance(dip)); |
| return (DDI_SUCCESS); |
| |
| case DDI_SUSPEND: |
| case DDI_PM_SUSPEND: |
| /* |
| * we got a suspend event (either real or imagined) |
| * so notify the nexus proper that all existing cards |
| * should go away. |
| */ |
| mutex_enter(&pcic->pc_lock); |
| #ifdef CARDBUS |
| if (pcic->pc_flags & PCF_CARDBUS) { |
| for (i = 0; i < pcic->pc_numsockets; i++) { |
| if ((pcic->pc_sockets[i].pcs_flags & |
| (PCS_CARD_PRESENT|PCS_CARD_ISCARDBUS)) == |
| (PCS_CARD_PRESENT|PCS_CARD_ISCARDBUS)) { |
| |
| pcmcia_cb_suspended( |
| pcic->pc_sockets[i].pcs_socket); |
| } |
| } |
| |
| cardbus_save_children(ddi_get_child(dip)); |
| } |
| #endif |
| /* turn everything off for all sockets and chips */ |
| for (i = 0; i < pcic->pc_numsockets; i++) { |
| if (pcic->pc_sockets[i].pcs_debounce_id) |
| pcic_rm_debqueue( |
| pcic->pc_sockets[i].pcs_debounce_id); |
| pcic->pc_sockets[i].pcs_debounce_id = 0; |
| |
| pcic_putb(pcic, i, PCIC_MANAGEMENT_INT, 0); |
| pcic_putb(pcic, i, PCIC_CARD_DETECT, 0); |
| pcic_putb(pcic, i, PCIC_MAPPING_ENABLE, 0); |
| /* disable interrupts and put card into RESET */ |
| pcic_putb(pcic, i, PCIC_INTERRUPT, 0); |
| pcic_putb(pcic, i, PCIC_POWER_CONTROL, 0); |
| if (pcic->pc_flags & PCF_CBPWRCTL) |
| pcic_putcb(pcic, CB_CONTROL, 0); |
| |
| if (pcic->pc_sockets[i].pcs_flags & PCS_CARD_PRESENT) { |
| pcic->pc_sockets[i].pcs_flags = PCS_STARTING; |
| /* |
| * Because we are half way through a save |
| * all this does is schedule a removal event |
| * to cs for when the system comes back. |
| * This doesn't actually matter. |
| */ |
| if (!pcic_do_pcmcia_sr && pcic_do_removal && |
| pcic->pc_callback) { |
| PC_CALLBACK(pcic->dip, pcic->pc_cb_arg, |
| PCE_CARD_REMOVAL, |
| pcic->pc_sockets[i].pcs_socket); |
| } |
| } |
| } |
| |
| pcic->pc_flags |= PCF_SUSPENDED; |
| mutex_exit(&pcic->pc_lock); |
| |
| /* |
| * when true power management exists, save the adapter |
| * state here to enable a recovery. For the emulation |
| * condition, the state is gone |
| */ |
| return (DDI_SUCCESS); |
| |
| default: |
| return (EINVAL); |
| } |
| } |
| |
| static uint32_t pcic_tisysctl_onbits = ((1<<27) | (1<<15) | (1<<14)); |
| static uint32_t pcic_tisysctl_offbits = 0; |
| static uint32_t pcic_default_latency = 0x40; |
| |
| static void |
| pcic_setup_adapter(pcicdev_t *pcic) |
| { |
| int i; |
| int value, flags; |
| |
| #if defined(__i386) || defined(__amd64) |
| pci_regspec_t *reg; |
| uchar_t bus, dev, func; |
| uint_t classcode; |
| int length; |
| #endif |
| |
| if (pcic->pc_flags & PCF_PCIBUS) { |
| /* |
| * all PCI-to-PCMCIA bus bridges need memory and I/O enabled |
| */ |
| flags = (PCIC_ENABLE_IO | PCIC_ENABLE_MEM); |
| pcic_iomem_pci_ctl(pcic->cfg_handle, pcic->cfgaddr, flags); |
| } |
| /* enable each socket */ |
| for (i = 0; i < pcic->pc_numsockets; i++) { |
| pcic->pc_sockets[i].pcs_flags = 0; |
| /* find out the socket capabilities (I/O vs memory) */ |
| value = pcic_getb(pcic, i, |
| PCIC_CHIP_REVISION) & PCIC_REV_ID_MASK; |
| if (value == PCIC_REV_ID_IO || value == PCIC_REV_ID_BOTH) |
| pcic->pc_sockets[i].pcs_flags |= PCS_SOCKET_IO; |
| |
| /* disable all windows just in case */ |
| pcic_putb(pcic, i, PCIC_MAPPING_ENABLE, 0); |
| |
| switch (pcic->pc_type) { |
| uint32_t cfg32; |
| uint16_t cfg16; |
| uint8_t cfg; |
| |
| /* enable extended registers for Vadem */ |
| case PCIC_VADEM_VG469: |
| case PCIC_VADEM: |
| |
| /* enable card status change interrupt for socket */ |
| break; |
| |
| case PCIC_I82365SL: |
| break; |
| |
| case PCIC_CL_PD6710: |
| pcic_putb(pcic, 0, PCIC_MISC_CTL_2, PCIC_LED_ENABLE); |
| break; |
| |
| /* |
| * On the CL_6730, we need to set up the interrupt |
| * signalling mode (PCI mode) and set the SMI and |
| * IRQ interrupt lines to PCI/level-mode. |
| */ |
| case PCIC_CL_PD6730: |
| switch (pcic->pc_intr_mode) { |
| case PCIC_INTR_MODE_PCI_1: |
| clext_reg_write(pcic, i, PCIC_CLEXT_MISC_CTL_3, |
| ((clext_reg_read(pcic, i, |
| PCIC_CLEXT_MISC_CTL_3) & |
| ~PCIC_CLEXT_INT_PCI) | |
| PCIC_CLEXT_INT_PCI)); |
| clext_reg_write(pcic, i, PCIC_CLEXT_EXT_CTL_1, |
| (PCIC_CLEXT_IRQ_LVL_MODE | |
| PCIC_CLEXT_SMI_LVL_MODE)); |
| cfg = PCIC_CL_LP_DYN_MODE; |
| pcic_putb(pcic, i, PCIC_MISC_CTL_2, cfg); |
| break; |
| case PCIC_INTR_MODE_ISA: |
| break; |
| } |
| break; |
| /* |
| * On the CL_6729, we set the SMI and IRQ interrupt |
| * lines to PCI/level-mode. as well as program the |
| * correct clock speed divider bit. |
| */ |
| case PCIC_CL_PD6729: |
| switch (pcic->pc_intr_mode) { |
| case PCIC_INTR_MODE_PCI_1: |
| clext_reg_write(pcic, i, PCIC_CLEXT_EXT_CTL_1, |
| (PCIC_CLEXT_IRQ_LVL_MODE | |
| PCIC_CLEXT_SMI_LVL_MODE)); |
| |
| break; |
| case PCIC_INTR_MODE_ISA: |
| break; |
| } |
| if (pcic->bus_speed > PCIC_PCI_25MHZ && i == 0) { |
| cfg = 0; |
| cfg |= PCIC_CL_TIMER_CLK_DIV; |
| pcic_putb(pcic, i, PCIC_MISC_CTL_2, cfg); |
| } |
| break; |
| case PCIC_INTEL_i82092: |
| cfg = PCIC_82092_EN_TIMING; |
| if (pcic->bus_speed < PCIC_SYSCLK_33MHZ) |
| cfg |= PCIC_82092_PCICLK_25MHZ; |
| ddi_put8(pcic->cfg_handle, pcic->cfgaddr + |
| PCIC_82092_PCICON, cfg); |
| break; |
| case PCIC_TI_PCI1130: |
| case PCIC_TI_PCI1131: |
| case PCIC_TI_PCI1250: |
| case PCIC_TI_PCI1031: |
| cfg = ddi_get8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DEVCTL_REG); |
| cfg &= ~PCIC_DEVCTL_INTR_MASK; |
| switch (pcic->pc_intr_mode) { |
| case PCIC_INTR_MODE_ISA: |
| cfg |= PCIC_DEVCTL_INTR_ISA; |
| break; |
| } |
| #ifdef PCIC_DEBUG |
| if (pcic_debug) { |
| cmn_err(CE_CONT, "pcic_setup_adapter: " |
| "write reg 0x%x=%x \n", |
| PCIC_DEVCTL_REG, cfg); |
| } |
| #endif |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DEVCTL_REG, |
| cfg); |
| |
| cfg = ddi_get8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_CRDCTL_REG); |
| cfg &= ~(PCIC_CRDCTL_PCIINTR|PCIC_CRDCTL_PCICSC| |
| PCIC_CRDCTL_PCIFUNC); |
| switch (pcic->pc_intr_mode) { |
| case PCIC_INTR_MODE_PCI_1: |
| cfg |= PCIC_CRDCTL_PCIINTR | |
| PCIC_CRDCTL_PCICSC | |
| PCIC_CRDCTL_PCIFUNC; |
| pcic->pc_flags |= PCF_USE_SMI; |
| break; |
| } |
| #ifdef PCIC_DEBUG |
| if (pcic_debug) { |
| cmn_err(CE_CONT, "pcic_setup_adapter: " |
| " write reg 0x%x=%x \n", |
| PCIC_CRDCTL_REG, cfg); |
| } |
| #endif |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_CRDCTL_REG, |
| cfg); |
| break; |
| case PCIC_TI_PCI1221: |
| case PCIC_TI_PCI1225: |
| cfg = ddi_get8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DEVCTL_REG); |
| cfg |= (PCIC_DEVCTL_INTR_DFLT | PCIC_DEVCTL_3VCAPABLE); |
| #ifdef PCIC_DEBUG |
| if (pcic_debug) { |
| cmn_err(CE_CONT, "pcic_setup_adapter: " |
| " write reg 0x%x=%x \n", |
| PCIC_DEVCTL_REG, cfg); |
| } |
| #endif |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DEVCTL_REG, cfg); |
| |
| cfg = ddi_get8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DIAG_REG); |
| if (pcic->pc_type == PCIC_TI_PCI1225) { |
| cfg |= (PCIC_DIAG_CSC | PCIC_DIAG_ASYNC); |
| } else { |
| cfg |= PCIC_DIAG_ASYNC; |
| } |
| pcic->pc_flags |= PCF_USE_SMI; |
| #ifdef PCIC_DEBUG |
| if (pcic_debug) { |
| cmn_err(CE_CONT, "pcic_setup_adapter: " |
| " write reg 0x%x=%x \n", |
| PCIC_DIAG_REG, cfg); |
| } |
| #endif |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DIAG_REG, cfg); |
| break; |
| case PCIC_TI_PCI1520: |
| case PCIC_TI_PCI1510: |
| case PCIC_TI_VENDOR: |
| if (pcic->pc_intr_mode == PCIC_INTR_MODE_ISA) { |
| /* functional intr routed by ExCA register */ |
| cfg = ddi_get8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_BRIDGE_CTL_REG); |
| cfg |= PCIC_FUN_INT_MOD_ISA; |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_BRIDGE_CTL_REG, |
| cfg); |
| |
| /* IRQ serialized interrupts */ |
| cfg = ddi_get8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DEVCTL_REG); |
| cfg &= ~PCIC_DEVCTL_INTR_MASK; |
| cfg |= PCIC_DEVCTL_INTR_ISA; |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DEVCTL_REG, |
| cfg); |
| break; |
| } |
| |
| /* CSC interrupt routed to PCI */ |
| cfg = ddi_get8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DIAG_REG); |
| cfg |= (PCIC_DIAG_CSC | PCIC_DIAG_ASYNC); |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DIAG_REG, cfg); |
| |
| #if defined(__i386) || defined(__amd64) |
| /* |
| * Some TI chips have 2 cardbus slots(function0 and |
| * function1), and others may have just 1 cardbus slot. |
| * The interrupt routing register is shared between the |
| * 2 functions and can only be accessed through |
| * function0. Here we check the presence of the second |
| * cardbus slot and do the right thing. |
| */ |
| |
| if (ddi_getlongprop(DDI_DEV_T_ANY, pcic->dip, |
| DDI_PROP_DONTPASS, "reg", (caddr_t)®, |
| &length) != DDI_PROP_SUCCESS) { |
| cmn_err(CE_WARN, |
| "pcic_setup_adapter(), failed to" |
| " read reg property\n"); |
| break; |
| } |
| |
| bus = PCI_REG_BUS_G(reg->pci_phys_hi); |
| dev = PCI_REG_DEV_G(reg->pci_phys_hi); |
| func = PCI_REG_FUNC_G(reg->pci_phys_hi); |
| kmem_free((caddr_t)reg, length); |
| |
| if (func != 0) { |
| break; |
| } |
| |
| classcode = (*pci_getl_func)(bus, dev, 1, |
| PCI_CONF_REVID); |
| classcode >>= 8; |
| if (classcode != 0x060700 && |
| classcode != 0x060500) { |
| break; |
| } |
| |
| /* Parallel PCI interrupts only */ |
| cfg = ddi_get8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DEVCTL_REG); |
| cfg &= ~PCIC_DEVCTL_INTR_MASK; |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DEVCTL_REG, |
| cfg); |
| |
| /* tie INTA and INTB together */ |
| cfg = ddi_get8(pcic->cfg_handle, |
| (pcic->cfgaddr + PCIC_SYSCTL_REG + 3)); |
| cfg |= PCIC_SYSCTL_INTRTIE; |
| ddi_put8(pcic->cfg_handle, (pcic->cfgaddr + |
| PCIC_SYSCTL_REG + 3), cfg); |
| #endif |
| |
| break; |
| case PCIC_TI_PCI1410: |
| cfg = ddi_get8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DIAG_REG); |
| cfg |= (PCIC_DIAG_CSC | PCIC_DIAG_ASYNC); |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_DIAG_REG, cfg); |
| break; |
| case PCIC_TOSHIBA_TOPIC100: |
| case PCIC_TOSHIBA_TOPIC95: |
| case PCIC_TOSHIBA_VENDOR: |
| cfg = ddi_get8(pcic->cfg_handle, pcic->cfgaddr + |
| PCIC_TOSHIBA_SLOT_CTL_REG); |
| cfg |= (PCIC_TOSHIBA_SCR_SLOTON | |
| PCIC_TOSHIBA_SCR_SLOTEN); |
| cfg &= (~PCIC_TOSHIBA_SCR_PRT_MASK); |
| cfg |= PCIC_TOSHIBA_SCR_PRT_3E2; |
| ddi_put8(pcic->cfg_handle, pcic->cfgaddr + |
| PCIC_TOSHIBA_SLOT_CTL_REG, cfg); |
| cfg = ddi_get8(pcic->cfg_handle, pcic->cfgaddr + |
| PCIC_TOSHIBA_INTR_CTL_REG); |
| switch (pcic->pc_intr_mode) { |
| case PCIC_INTR_MODE_ISA: |
| cfg &= ~PCIC_TOSHIBA_ICR_SRC; |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + |
| PCIC_TOSHIBA_INTR_CTL_REG, cfg); |
| |
| cfg = ddi_get8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_BRIDGE_CTL_REG); |
| cfg |= PCIC_FUN_INT_MOD_ISA; |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_BRIDGE_CTL_REG, |
| cfg); |
| break; |
| case PCIC_INTR_MODE_PCI_1: |
| cfg |= PCIC_TOSHIBA_ICR_SRC; |
| cfg &= (~PCIC_TOSHIBA_ICR_PIN_MASK); |
| cfg |= PCIC_TOSHIBA_ICR_PIN_INTA; |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + |
| PCIC_TOSHIBA_INTR_CTL_REG, cfg); |
| break; |
| } |
| break; |
| case PCIC_O2MICRO_VENDOR: |
| cfg32 = ddi_get32(pcic->cfg_handle, |
| (uint32_t *)(pcic->cfgaddr + |
| PCIC_O2MICRO_MISC_CTL)); |
| switch (pcic->pc_intr_mode) { |
| case PCIC_INTR_MODE_ISA: |
| cfg32 |= (PCIC_O2MICRO_ISA_LEGACY | |
| PCIC_O2MICRO_INT_MOD_PCI); |
| ddi_put32(pcic->cfg_handle, |
| (uint32_t *)(pcic->cfgaddr + |
| PCIC_O2MICRO_MISC_CTL), |
| cfg32); |
| cfg = ddi_get8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_BRIDGE_CTL_REG); |
| cfg |= PCIC_FUN_INT_MOD_ISA; |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_BRIDGE_CTL_REG, |
| cfg); |
| break; |
| case PCIC_INTR_MODE_PCI_1: |
| cfg32 &= ~PCIC_O2MICRO_ISA_LEGACY; |
| cfg32 |= PCIC_O2MICRO_INT_MOD_PCI; |
| ddi_put32(pcic->cfg_handle, |
| (uint32_t *)(pcic->cfgaddr + |
| PCIC_O2MICRO_MISC_CTL), |
| cfg32); |
| break; |
| } |
| break; |
| case PCIC_RICOH_VENDOR: |
| if (pcic->pc_intr_mode == PCIC_INTR_MODE_ISA) { |
| cfg16 = ddi_get16(pcic->cfg_handle, |
| (uint16_t *)(pcic->cfgaddr + |
| PCIC_RICOH_MISC_CTL_2)); |
| cfg16 |= (PCIC_RICOH_CSC_INT_MOD | |
| PCIC_RICOH_FUN_INT_MOD); |
| ddi_put16(pcic->cfg_handle, |
| (uint16_t *)(pcic->cfgaddr + |
| PCIC_RICOH_MISC_CTL_2), |
| cfg16); |
| |
| cfg16 = ddi_get16(pcic->cfg_handle, |
| (uint16_t *)(pcic->cfgaddr + |
| PCIC_RICOH_MISC_CTL)); |
| cfg16 |= PCIC_RICOH_SIRQ_EN; |
| ddi_put16(pcic->cfg_handle, |
| (uint16_t *)(pcic->cfgaddr + |
| PCIC_RICOH_MISC_CTL), |
| cfg16); |
| |
| cfg = ddi_get8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_BRIDGE_CTL_REG); |
| cfg |= PCIC_FUN_INT_MOD_ISA; |
| ddi_put8(pcic->cfg_handle, |
| pcic->cfgaddr + PCIC_BRIDGE_CTL_REG, |
| cfg); |
| } |
| break; |
| default: |
| break; |
| } /* switch */ |
| |
| /* |
| * The default value in the EEPROM (loaded on reset) for |
| * MFUNC0/MFUNC1 may be incorrect. Here we make sure that |
| * MFUNC0 is connected to INTA, and MFUNC1 is connected to |
| * INTB. This applies to all TI CardBus controllers. |
| */ |
| if ((pcic->pc_type >> 16) == PCIC_TI_VENDORID && |
| pcic->pc_intr_mode == PCIC_INTR_MODE_PCI_1) { |
| value = ddi_get32(pcic->cfg_handle, |
| (uint32_t *)(pcic->cfgaddr + PCIC_MFROUTE_REG)); |
| value &= ~0xff; |
| ddi_put32(pcic->cfg_handle, (uint32_t *)(pcic->cfgaddr + |
| PCIC_MFROUTE_REG), value|PCIC_TI_MFUNC_SEL); |
| } |
| |
| /* setup general card status change interrupt */ |
| switch (pcic->pc_type) { |
| case PCIC_TI_PCI1225: |
| case PCIC_TI_PCI1221: |
| case PCIC_TI_PCI1031: |
| case PCIC_TI_PCI1520: |
| case PCIC_TI_PCI1410: |
| pcic_putb(pcic, i, PCIC_MANAGEMENT_INT, |
| PCIC_CHANGE_DEFAULT); |
| break; |
| default: |
| if (pcic->pc_intr_mode == |
| PCIC_INTR_MODE_PCI_1) { |
| pcic_putb(pcic, i, PCIC_MANAGEMENT_INT, |
| PCIC_CHANGE_DEFAULT); |
| break; |
| } else { |
| pcic_putb(pcic, i, PCIC_MANAGEMENT_INT, |
| PCIC_CHANGE_DEFAULT | |
| (pcic->pc_sockets[i].pcs_smi << 4)); |
| break; |
| } |
| } |
| |
| pcic->pc_flags |= PCF_INTRENAB; |
| |
| /* take card out of RESET */ |
| pcic_putb(pcic, i, PCIC_INTERRUPT, PCIC_RESET); |
| /* turn power off and let CS do this */ |
| pcic_putb(pcic, i, PCIC_POWER_CONTROL, 0); |
| |
| /* final chip specific initialization */ |
| switch (pcic->pc_type) { |
| case PCIC_VADEM: |
| pcic_putb(pcic, i, PCIC_VG_CONTROL, |
| PCIC_VC_DELAYENABLE); |
| pcic->pc_flags |= PCF_DEBOUNCE; |
| /* FALLTHROUGH */ |
| case PCIC_I82365SL: |
| pcic_putb(pcic, i, PCIC_GLOBAL_CONTROL, |
| PCIC_GC_CSC_WRITE); |
| /* clear any pending interrupts */ |
| value = pcic_getb(pcic, i, PCIC_CARD_STATUS_CHANGE); |
| pcic_putb(pcic, i, PCIC_CARD_STATUS_CHANGE, value); |
| break; |
| /* The 82092 uses PCI config space to enable interrupts */ |
| case PCIC_INTEL_i82092: |
| pcic_82092_smiirq_ctl(pcic, i, PCIC_82092_CTL_SMI, |
| PCIC_82092_INT_ENABLE); |
| break; |
| case PCIC_CL_PD6729: |
| if (pcic->bus_speed >= PCIC_PCI_DEF_SYSCLK && i == 0) { |
| value = pcic_getb(pcic, i, PCIC_MISC_CTL_2); |
| pcic_putb(pcic, i, PCIC_MISC_CTL_2, |
| value | PCIC_CL_TIMER_CLK_DIV); |
| } |
| break; |
| } /* switch */ |
| |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) |
| cmn_err(CE_CONT, |
| "socket %d value=%x, flags = %x (%s)\n", |
| i, value, pcic->pc_sockets[i].pcs_flags, |
| (pcic->pc_sockets[i].pcs_flags & |
| PCS_CARD_PRESENT) ? |
| "card present" : "no card"); |
| #endif |
| } |
| } |
| |
| /* |
| * pcic_intr(caddr_t, caddr_t) |
| * interrupt handler for the PCIC style adapter |
| * handles all basic interrupts and also checks |
| * for status changes and notifies the nexus if |
| * necessary |
| * |
| * On PCI bus adapters, also handles all card |
| * IO interrupts. |
| */ |
| /*ARGSUSED*/ |
| uint32_t |
| pcic_intr(caddr_t arg1, caddr_t arg2) |
| { |
| pcicdev_t *pcic = (pcicdev_t *)arg1; |
| int value = 0, i, ret = DDI_INTR_UNCLAIMED; |
| uint8_t status; |
| uint_t io_ints; |
| |
| #if defined(PCIC_DEBUG) |
| pcic_err(pcic->dip, 0xf, |
| "pcic_intr: enter pc_flags=0x%x PCF_ATTACHED=0x%x" |
| " pc_numsockets=%d \n", |
| pcic->pc_flags, PCF_ATTACHED, pcic->pc_numsockets); |
| #endif |
| |
| if (!(pcic->pc_flags & PCF_ATTACHED)) |
| return (DDI_INTR_UNCLAIMED); |
| |
| mutex_enter(&pcic->intr_lock); |
| |
| if (pcic->pc_flags & PCF_SUSPENDED) { |
| mutex_exit(&pcic->intr_lock); |
| return (ret); |
| } |
| |
| /* |
| * need to change to only ACK and touch the slot that |
| * actually caused the interrupt. Currently everything |
| * is acked |
| * |
| * we need to look at all known sockets to determine |
| * what might have happened, so step through the list |
| * of them |
| */ |
| |
| /* |
| * Set the bitmask for IO interrupts to initially include all sockets |
| */ |
| io_ints = (1 << pcic->pc_numsockets) - 1; |
| |
| for (i = 0; i < pcic->pc_numsockets; i++) { |
| int card_type; |
| pcic_socket_t *sockp; |
| int value_cb = 0; |
| |
| sockp = &pcic->pc_sockets[i]; |
| /* get the socket's I/O addresses */ |
| |
| if (sockp->pcs_flags & PCS_WAITING) { |
| io_ints &= ~(1 << i); |
| continue; |
| } |
| |
| if (sockp->pcs_flags & PCS_CARD_IO) |
| card_type = IF_IO; |
| else |
| card_type = IF_MEMORY; |
| |
| if (pcic->pc_io_type == PCIC_IO_TYPE_YENTA) |
| value_cb = pcic_getcb(pcic, CB_STATUS_EVENT); |
| |
| value = pcic_change(pcic, i); |
| |
| if ((value != 0) || (value_cb != 0)) { |
| int x = pcic->pc_cb_arg; |
| |
| ret = DDI_INTR_CLAIMED; |
| |
| #if defined(PCIC_DEBUG) |
| pcic_err(pcic->dip, 0x9, |
| "card_type = %d, value_cb = 0x%x\n", |
| card_type, |
| value_cb ? value_cb : |
| pcic_getcb(pcic, CB_STATUS_EVENT)); |
| if (pcic_debug) |
| cmn_err(CE_CONT, |
| "\tchange on socket %d (%x)\n", i, |
| value); |
| #endif |
| /* find out what happened */ |
| status = pcic_getb(pcic, i, PCIC_INTERFACE_STATUS); |
| |
| /* acknowledge the interrupt */ |
| if (value_cb) |
| pcic_putcb(pcic, CB_STATUS_EVENT, value_cb); |
| |
| if (value) |
| pcic_putb(pcic, i, PCIC_CARD_STATUS_CHANGE, |
| value); |
| |
| if (pcic->pc_callback == NULL) { |
| /* if not callback handler, nothing to do */ |
| continue; |
| } |
| |
| /* Card Detect */ |
| if (value & PCIC_CD_DETECT || |
| value_cb & CB_PS_CCDMASK) { |
| uint8_t irq; |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) |
| cmn_err(CE_CONT, |
| "\tcd_detect: status=%x," |
| " flags=%x\n", |
| status, sockp->pcs_flags); |
| #else |
| #ifdef lint |
| if (status == 0) |
| status++; |
| #endif |
| #endif |
| /* |
| * Turn off all interrupts for this socket here. |
| */ |
| irq = pcic_getb(pcic, sockp->pcs_socket, |
| PCIC_MANAGEMENT_INT); |
| irq &= ~PCIC_CHANGE_MASK; |
| pcic_putb(pcic, sockp->pcs_socket, |
| PCIC_MANAGEMENT_INT, irq); |
| |
| pcic_putcb(pcic, CB_STATUS_MASK, 0x0); |
| |
| /* |
| * Put the socket in debouncing state so that |
| * the leaf driver won't receive interrupts. |
| * Crucial for handling surprise-removal. |
| */ |
| sockp->pcs_flags |= PCS_DEBOUNCING; |
| |
| if (!sockp->pcs_cd_softint_flg) { |
| sockp->pcs_cd_softint_flg = 1; |
| (void) ddi_intr_trigger_softint( |
| sockp->pcs_cd_softint_hdl, NULL); |
| } |
| |
| io_ints &= ~(1 << i); |
| } /* PCIC_CD_DETECT */ |
| |
| /* Ready/Change Detect */ |
| sockp->pcs_state ^= SBM_RDYBSY; |
| if (card_type == IF_MEMORY && value & PCIC_RD_DETECT) { |
| sockp->pcs_flags |= PCS_READY; |
| PC_CALLBACK(pcic->dip, x, PCE_CARD_READY, i); |
| } |
| |
| /* Battery Warn Detect */ |
| if (card_type == IF_MEMORY && |
| value & PCIC_BW_DETECT && |
| !(sockp->pcs_state & SBM_BVD2)) { |
| sockp->pcs_state |= SBM_BVD2; |
| PC_CALLBACK(pcic->dip, x, |
| PCE_CARD_BATTERY_WARN, i); |
| } |
| |
| /* Battery Dead Detect */ |
| if (value & PCIC_BD_DETECT) { |
| /* |
| * need to work out event if RI not enabled |
| * and card_type == IF_IO |
| */ |
| if (card_type == IF_MEMORY && |
| !(sockp->pcs_state & SBM_BVD1)) { |
| sockp->pcs_state |= SBM_BVD1; |
| PC_CALLBACK(pcic->dip, x, |
| PCE_CARD_BATTERY_DEAD, |
| i); |
| } else { |
| /* |
| * information in pin replacement |
| * register if one is available |
| */ |
| PC_CALLBACK(pcic->dip, x, |
| PCE_CARD_STATUS_CHANGE, |
| i); |
| } /* IF_MEMORY */ |
| } /* PCIC_BD_DETECT */ |
| } /* if pcic_change */ |
| /* |
| * for any controllers that we can detect whether a socket |
| * had an interrupt for the PC Card, we should sort that out |
| * here. |
| */ |
| } /* for pc_numsockets */ |
| |
| /* |
| * If we're on a PCI bus, we may need to cycle through each IO |
| * interrupt handler that is registered since they all |
| * share the same interrupt line. |
| */ |
| |
| |
| #if defined(PCIC_DEBUG) |
| pcic_err(pcic->dip, 0xf, |
| "pcic_intr: pc_intr_mode=%d pc_type=%x io_ints=0x%x\n", |
| pcic->pc_intr_mode, pcic->pc_type, io_ints); |
| #endif |
| |
| if (io_ints) { |
| if (pcic_do_io_intr(pcic, io_ints) == DDI_INTR_CLAIMED) |
| ret = DDI_INTR_CLAIMED; |
| } |
| |
| mutex_exit(&pcic->intr_lock); |
| |
| #if defined(PCIC_DEBUG) |
| pcic_err(pcic->dip, 0xf, |
| "pcic_intr: ret=%d value=%d DDI_INTR_CLAIMED=%d\n", |
| ret, value, DDI_INTR_CLAIMED); |
| #endif |
| |
| return (ret); |
| } |
| |
| /* |
| * pcic_change() |
| * check to see if this socket had a change in state |
| * by checking the status change register |
| */ |
| static int |
| pcic_change(pcicdev_t *pcic, int socket) |
| { |
| return (pcic_getb(pcic, socket, PCIC_CARD_STATUS_CHANGE)); |
| } |
| |
| /* |
| * pcic_do_io_intr - calls client interrupt handlers |
| */ |
| static int |
| pcic_do_io_intr(pcicdev_t *pcic, uint32_t sockets) |
| { |
| inthandler_t *tmp; |
| int ret = DDI_INTR_UNCLAIMED; |
| |
| #if defined(PCIC_DEBUG) |
| pcic_err(pcic->dip, 0xf, |
| "pcic_do_io_intr: pcic=%p sockets=%d irq_top=%p\n", |
| (void *)pcic, (int)sockets, (void *)pcic->irq_top); |
| #endif |
| |
| if (pcic->irq_top != NULL) { |
| tmp = pcic->irq_current; |
| |
| do { |
| int cur = pcic->irq_current->socket; |
| pcic_socket_t *sockp = |
| &pcic->pc_sockets[cur]; |
| |
| #if defined(PCIC_DEBUG) |
| pcic_err(pcic->dip, 0xf, |
| "\t pcs_flags=0x%x PCS_CARD_PRESENT=0x%x\n", |
| sockp->pcs_flags, PCS_CARD_PRESENT); |
| pcic_err(pcic->dip, 0xf, |
| "\t sockets=%d cur=%d intr=%p arg1=%p " |
| "arg2=%p\n", |
| sockets, cur, (void *)pcic->irq_current->intr, |
| pcic->irq_current->arg1, |
| pcic->irq_current->arg2); |
| #endif |
| if ((sockp->pcs_flags & PCS_CARD_PRESENT) && |
| !(sockp->pcs_flags & PCS_DEBOUNCING) && |
| (sockets & (1 << cur))) { |
| |
| if ((*pcic->irq_current->intr)(pcic->irq_current->arg1, |
| pcic->irq_current->arg2) == DDI_INTR_CLAIMED) |
| ret = DDI_INTR_CLAIMED; |
| |
| #if defined(PCIC_DEBUG) |
| pcic_err(pcic->dip, 0xf, |
| "\t ret=%d DDI_INTR_CLAIMED=%d\n", |
| ret, DDI_INTR_CLAIMED); |
| #endif |
| } |
| |
| |
| if ((pcic->irq_current = pcic->irq_current->next) == NULL) |
| pcic->irq_current = pcic->irq_top; |
| |
| } while (pcic->irq_current != tmp); |
| |
| if ((pcic->irq_current = pcic->irq_current->next) == NULL) |
| pcic->irq_current = pcic->irq_top; |
| |
| } else { |
| ret = DDI_INTR_UNCLAIMED; |
| } |
| |
| #if defined(PCIC_DEBUG) |
| pcic_err(pcic->dip, 0xf, |
| "pcic_do_io_intr: exit ret=%d DDI_INTR_CLAIMED=%d\n", |
| ret, DDI_INTR_CLAIMED); |
| #endif |
| |
| return (ret); |
| |
| } |
| |
| /* |
| * pcic_inquire_adapter() |
| * SocketServices InquireAdapter function |
| * get characteristics of the physical adapter |
| */ |
| /*ARGSUSED*/ |
| static int |
| pcic_inquire_adapter(dev_info_t *dip, inquire_adapter_t *config) |
| { |
| anp_t *anp = ddi_get_driver_private(dip); |
| pcicdev_t *pcic = anp->an_private; |
| |
| config->NumSockets = pcic->pc_numsockets; |
| config->NumWindows = pcic->pc_numsockets * PCIC_NUMWINSOCK; |
| config->NumEDCs = 0; |
| config->AdpCaps = 0; |
| config->ActiveHigh = 0; |
| config->ActiveLow = PCIC_AVAIL_IRQS; |
| config->NumPower = pcic->pc_numpower; |
| config->power_entry = pcic->pc_power; /* until we resolve this */ |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) { |
| cmn_err(CE_CONT, "pcic_inquire_adapter:\n"); |
| cmn_err(CE_CONT, "\tNumSockets=%d\n", config->NumSockets); |
| cmn_err(CE_CONT, "\tNumWindows=%d\n", config->NumWindows); |
| } |
| #endif |
| config->ResourceFlags = 0; |
| switch (pcic->pc_intr_mode) { |
| case PCIC_INTR_MODE_PCI_1: |
| config->ResourceFlags |= RES_OWN_IRQ | RES_IRQ_NEXUS | |
| RES_IRQ_SHAREABLE; |
| break; |
| } |
| return (SUCCESS); |
| } |
| |
| /* |
| * pcic_callback() |
| * The PCMCIA nexus calls us via this function |
| * in order to set the callback function we are |
| * to call the nexus with |
| */ |
| /*ARGSUSED*/ |
| static int |
| pcic_callback(dev_info_t *dip, int (*handler)(), int arg) |
| { |
| anp_t *anp = ddi_get_driver_private(dip); |
| pcicdev_t *pcic = anp->an_private; |
| |
| if (handler != NULL) { |
| pcic->pc_callback = handler; |
| pcic->pc_cb_arg = arg; |
| pcic->pc_flags |= PCF_CALLBACK; |
| } else { |
| pcic->pc_callback = NULL; |
| pcic->pc_cb_arg = 0; |
| pcic->pc_flags &= ~PCF_CALLBACK; |
| } |
| /* |
| * we're now registered with the nexus |
| * it is acceptable to do callbacks at this point. |
| * don't call back from here though since it could block |
| */ |
| return (PC_SUCCESS); |
| } |
| |
| /* |
| * pcic_calc_speed (pcicdev_t *pcic, uint32_t speed) |
| * calculate the speed bits from the specified memory speed |
| * there may be more to do here |
| */ |
| |
| static int |
| pcic_calc_speed(pcicdev_t *pcic, uint32_t speed) |
| { |
| uint32_t wspeed = 1; /* assume 1 wait state when unknown */ |
| uint32_t bspeed = PCIC_ISA_DEF_SYSCLK; |
| |
| switch (pcic->pc_type) { |
| case PCIC_I82365SL: |
| case PCIC_VADEM: |
| case PCIC_VADEM_VG469: |
| default: |
| /* Intel chip wants it in waitstates */ |
| wspeed = mhztons(PCIC_ISA_DEF_SYSCLK) * 3; |
| if (speed <= wspeed) |
| wspeed = 0; |
| else if (speed <= (wspeed += mhztons(bspeed))) |
| wspeed = 1; |
| else if (speed <= (wspeed += mhztons(bspeed))) |
| wspeed = 2; |
| else |
| wspeed = 3; |
| wspeed <<= 6; /* put in right bit positions */ |
| break; |
| |
| case PCIC_INTEL_i82092: |
| wspeed = SYSMEM_82092_80NS; |
| if (speed > 80) |
| wspeed = SYSMEM_82092_100NS; |
| if (speed > 100) |
| wspeed = SYSMEM_82092_150NS; |
| if (speed > 150) |
| wspeed = SYSMEM_82092_200NS; |
| if (speed > 200) |
| wspeed = SYSMEM_82092_250NS; |
| if (speed > 250) |
| wspeed = SYSMEM_82092_600NS; |
| wspeed <<= 5; /* put in right bit positions */ |
| break; |
| |
| } /* switch */ |
| |
| return (wspeed); |
| } |
| |
| /* |
| * These values are taken from the PC Card Standard Electrical Specification. |
| * Generally the larger value is taken if 2 are possible. |
| */ |
| static struct pcic_card_times { |
| uint16_t cycle; /* Speed as found in the atribute space of the card. */ |
| uint16_t setup; /* Corresponding address setup time. */ |
| uint16_t width; /* Corresponding width, OE or WE. */ |
| uint16_t hold; /* Corresponding data or address hold time. */ |
| } pcic_card_times[] = { |
| |
| /* |
| * Note: The rounded up times for 250, 200 & 150 have been increased |
| * due to problems with the 3-Com ethernet cards (pcelx) on UBIIi. |
| * See BugID 00663. |
| */ |
| |
| /* |
| * Rounded up times Original times from |
| * that add up to the the PCMCIA Spec. |
| * cycle time. |
| */ |
| {600, 180, 370, 140}, /* 100, 300, 70 */ |
| {400, 120, 300, 90}, /* Made this one up */ |
| {250, 100, 190, 70}, /* 30, 150, 30 */ |
| {200, 80, 170, 70}, /* 20, 120, 30 */ |
| {150, 50, 110, 40}, /* 20, 80, 20 */ |
| {100, 40, 80, 40}, /* 10, 60, 15 */ |
| {0, 10, 60, 15} /* 10, 60, 15 */ |
| }; |
| |
| /* |
| * pcic_set_cdtimers |
| * This is specific to several Cirrus Logic chips |
| */ |
| static void |
| pcic_set_cdtimers(pcicdev_t *pcic, int socket, uint32_t speed, int tset) |
| { |
| int cmd, set, rec, offset, clk_pulse; |
| struct pcic_card_times *ctp; |
| |
| if ((tset == IOMEM_CLTIMER_SET_1) || (tset == SYSMEM_CLTIMER_SET_1)) |
| offset = 3; |
| else |
| offset = 0; |
| |
| clk_pulse = mhztons(pcic->bus_speed); |
| for (ctp = pcic_card_times; speed < ctp->cycle; ctp++) |
| ; |
| |
| /* |
| * Add (clk_pulse/2) and an extra 1 to account for rounding errors. |
| */ |
| set = ((ctp->setup + 10 + 1 + (clk_pulse/2))/clk_pulse) - 1; |
| if (set < 0) |
| set = 0; |
| |
| cmd = ((ctp->width + 10 + 1 + (clk_pulse/2))/clk_pulse) - 1; |
| if (cmd < 0) |
| cmd = 0; |
| |
| rec = ((ctp->hold + 10 + 1 + (clk_pulse/2))/clk_pulse) - 2; |
| if (rec < 0) |
| rec = 0; |
| |
| #if defined(PCIC_DEBUG) |
| pcic_err(pcic->dip, 8, "pcic_set_cdtimers(%d, Timer Set %d)\n" |
| "ct=%d, cp=%d, cmd=0x%x, setup=0x%x, rec=0x%x\n", |
| (unsigned)speed, offset == 3 ? 1 : 0, |
| ctp->cycle, clk_pulse, cmd, set, rec); |
| #endif |
| |
| pcic_putb(pcic, socket, PCIC_TIME_COMMAND_0 + offset, cmd); |
| pcic_putb(pcic, socket, PCIC_TIME_SETUP_0 + offset, set); |
| pcic_putb(pcic, socket, PCIC_TIME_RECOVER_0 + offset, rec); |
| } |
| |
| /* |
| * pcic_set_window |
| * essentially the same as the Socket Services specification |
| * We use socket and not adapter since they are identifiable |
| * but the rest is the same |
| * |
| * dip pcic driver's device information |
| * window parameters for the request |
| */ |
| static int |
| pcic_set_window(dev_info_t *dip, set_window_t *window) |
| { |
| anp_t *anp = ddi_get_driver_private(dip); |
| pcicdev_t *pcic = anp->an_private; |
| int select; |
| int socket, pages, which, ret; |
| pcic_socket_t *sockp = &pcic->pc_sockets[window->socket]; |
| ra_return_t res; |
| ndi_ra_request_t req; |
| uint32_t base = window->base; |
| |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) { |
| cmn_err(CE_CONT, "pcic_set_window: entered\n"); |
| cmn_err(CE_CONT, |
| "\twindow=%d, socket=%d, WindowSize=%d, speed=%d\n", |
| window->window, window->socket, window->WindowSize, |
| window->speed); |
| cmn_err(CE_CONT, |
| "\tbase=%x, state=%x\n", (unsigned)window->base, |
| (unsigned)window->state); |
| } |
| #endif |
| |
| /* |
| * do some basic sanity checking on what we support |
| * we don't do paged mode |
| */ |
| if (window->state & WS_PAGED) { |
| cmn_err(CE_WARN, "pcic_set_window: BAD_ATTRIBUTE\n"); |
| return (BAD_ATTRIBUTE); |
| } |
| |
| /* |
| * we don't care about previous mappings. |
| * Card Services will deal with that so don't |
| * even check |
| */ |
| |
| socket = window->socket; |
| |
| if (!(window->state & WS_IO)) { |
| int win, tmp; |
| pcs_memwin_t *memp; |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) |
| cmn_err(CE_CONT, "\twindow type is memory\n"); |
| #endif |
| /* this is memory window mapping */ |
| win = window->window % PCIC_NUMWINSOCK; |
| tmp = window->window / PCIC_NUMWINSOCK; |
| |
| /* only windows 2-6 can do memory mapping */ |
| if (tmp != window->socket || win < PCIC_IOWINDOWS) { |
| cmn_err(CE_CONT, |
| "\tattempt to map to non-mem window\n"); |
| return (BAD_WINDOW); |
| } |
| |
| if (window->WindowSize == 0) |
| window->WindowSize = MEM_MIN; |
| else if ((window->WindowSize & (PCIC_PAGE-1)) != 0) { |
| cmn_err(CE_WARN, "pcic_set_window: BAD_SIZE\n"); |
| return (BAD_SIZE); |
| } |
| |
| mutex_enter(&pcic->pc_lock); /* protect the registers */ |
| |
| memp = &sockp->pcs_windows[win].mem; |
| memp->pcw_speed = window->speed; |
| |
| win -= PCIC_IOWINDOWS; /* put in right range */ |
| |
| if (window->WindowSize != memp->pcw_len) |
| which = memp->pcw_len; |
| else |
| which = 0; |
| |
| if (window->state & WS_ENABLED) { |
| uint32_t wspeed; |
| #if defined(PCIC_DEBUG) |
| if (pcic_debug) { |
| cmn_err(CE_CONT, |
| "\tbase=%x, win=%d\n", (unsigned)base, |
| win); |
| if (which) |
| cmn_err(CE_CONT, |
| "\tneed to remap window\n"); |
| } |
| #endif |
| |
| if (which && (memp->pcw_status & PCW_MAPPED)) { |
| ddi_regs_map_free(&memp->pcw_handle); |
|