/*
* Copyright (c) 2004 Charles M. Hannum. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Charles M. Hannum.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*/
/*
* Copyright (c) 2000 Christian E. Hopps. All rights reserved.
* Copyright (c) 1997 Marc Horowitz. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Marc Horowitz.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
if ((ident & PCIC_IDENT_IFTYPE_MASK) != PCIC_IDENT_IFTYPE_MEM_AND_IO) {
#ifdef DIAGNOSTIC
printf("pcic: does not support memory and I/O cards, "
"ignored (ident=%0x)\n", ident);
#endif
return 0;
}
return 1;
}
int
pcic_vendor(struct pcic_handle *h)
{
int reg;
int vendor;
reg = pcic_read(h, PCIC_IDENT);
if ((reg & PCIC_IDENT_REV_MASK) == 0)
return PCIC_VENDOR_NONE;
switch (reg) {
case 0x00:
case 0xff:
return PCIC_VENDOR_NONE;
case PCIC_IDENT_ID_INTEL0:
vendor = PCIC_VENDOR_I82365SLR0;
break;
case PCIC_IDENT_ID_INTEL1:
vendor = PCIC_VENDOR_I82365SLR1;
break;
case PCIC_IDENT_ID_INTEL2:
vendor = PCIC_VENDOR_I82365SL_DF;
break;
case PCIC_IDENT_ID_IBM1:
case PCIC_IDENT_ID_IBM2:
vendor = PCIC_VENDOR_IBM;
break;
case PCIC_IDENT_ID_IBM3:
vendor = PCIC_VENDOR_IBM_KING;
break;
default:
vendor = PCIC_VENDOR_UNKNOWN;
break;
}
if (vendor == PCIC_VENDOR_I82365SLR0 ||
vendor == PCIC_VENDOR_I82365SLR1) {
/*
* Check for Cirrus PD67xx.
* the chip_id of the cirrus toggles between 11 and 00 after a
* write. weird.
*/
pcic_write(h, PCIC_CIRRUS_CHIP_INFO, 0);
reg = pcic_read(h, -1);
if ((reg & PCIC_CIRRUS_CHIP_INFO_CHIP_ID) ==
PCIC_CIRRUS_CHIP_INFO_CHIP_ID) {
reg = pcic_read(h, -1);
if ((reg & PCIC_CIRRUS_CHIP_INFO_CHIP_ID) == 0)
return PCIC_VENDOR_CIRRUS_PD67XX;
}
/*
* check for Ricoh RF5C[23]96
*/
reg = pcic_read(h, PCIC_RICOH_REG_CHIP_ID);
switch (reg) {
case PCIC_RICOH_CHIP_ID_5C296:
return PCIC_VENDOR_RICOH_5C296;
case PCIC_RICOH_CHIP_ID_5C396:
return PCIC_VENDOR_RICOH_5C396;
}
}
return vendor;
}
const char *
pcic_vendor_to_string(int vendor)
{
switch (vendor) {
case PCIC_VENDOR_I82365SLR0:
return "Intel 82365SL Revision 0";
case PCIC_VENDOR_I82365SLR1:
return "Intel 82365SL Revision 1";
case PCIC_VENDOR_CIRRUS_PD67XX:
return "Cirrus PD6710/2X";
case PCIC_VENDOR_I82365SL_DF:
return "Intel 82365SL-DF";
case PCIC_VENDOR_RICOH_5C296:
return "Ricoh RF5C296";
case PCIC_VENDOR_RICOH_5C396:
return "Ricoh RF5C396";
case PCIC_VENDOR_IBM:
return "IBM PCIC";
case PCIC_VENDOR_IBM_KING:
return "IBM KING";
}
return "Unknown controller";
}
void
pcic_attach(struct pcic_softc *sc)
{
int i, reg, chip, socket;
struct pcic_handle *h;
device_t self;
/* need to read vendor -- for cirrus to report no xtra chip */
if (socket == 0) {
h->vendor = pcic_vendor(h);
if (i < __arraycount(sc->handle) - 1)
(h + 1)->vendor = h->vendor;
}
switch (h->vendor) {
case PCIC_VENDOR_NONE:
/* no chip */
continue;
case PCIC_VENDOR_CIRRUS_PD67XX:
reg = pcic_read(h, PCIC_CIRRUS_CHIP_INFO);
if (socket == 0 ||
(reg & PCIC_CIRRUS_CHIP_INFO_SLOTS))
h->flags = PCIC_FLAG_SOCKETP;
break;
default:
/*
* During the socket probe, read the ident register
* twice. I don't understand why, but sometimes the
* clone chips in hpcmips boxes read all-0s the first
* time. -- mycroft
*/
reg = pcic_read(h, PCIC_IDENT);
DPRINTF(("socket %d ident reg 0x%02x\n", i, reg));
reg = pcic_read(h, PCIC_IDENT);
DPRINTF(("socket %d ident reg 0x%02x\n", i, reg));
if (pcic_ident_ok(reg))
h->flags = PCIC_FLAG_SOCKETP;
break;
}
}
for (i = 0; i < __arraycount(sc->handle); i++) {
h = &sc->handle[i];
if (h->flags & PCIC_FLAG_SOCKETP) {
SIMPLEQ_INIT(&h->events);
/* disable interrupts and leave socket in reset */
pcic_write(h, PCIC_INTR, 0);
/* zero out the address windows */
pcic_write(h, PCIC_ADDRWIN_ENABLE, 0);
/* power down the socket */
pcic_write(h, PCIC_PWRCTL, 0);
/*
* check for card insertion or removal during suspend period.
* XXX: the code can't cope with card swap (remove then insert).
* how can we detect such situation?
*/
if (why == PWR_RESUME)
(void)pcic_intr_socket(h);
}
}
/*
* attach a socket -- we don't know about irqs yet
*/
void
pcic_attach_socket(struct pcic_handle *h)
{
struct pcmciabus_attach_args paa;
struct pcic_softc *sc = device_private(h->ph_parent);
int locs[PCMCIABUSCF_NLOCS];
/*
* now finish attaching the sockets, we are ready to allocate
* interrupts
*/
void
pcic_attach_sockets_finish(struct pcic_softc *sc)
{
int i;
for (i = 0; i < __arraycount(sc->handle); i++)
if (sc->handle[i].flags & PCIC_FLAG_SOCKETP)
pcic_attach_socket_finish(&sc->handle[i]);
}
/*
* finishing attaching the socket. Interrupts may now be on
* if so expects the pcic interrupt to be blocked
*/
void
pcic_attach_socket_finish(struct pcic_handle *h)
{
struct pcic_softc *sc = device_private(h->ph_parent);
int reg;
char cs[4];
/*
* Set up a powerhook to ensure it continues to interrupt on
* card detect even after suspend.
* (this works around a bug seen in suspend-to-disk on the
* Sony VAIO Z505; on resume, the CSC_INTR state is not preserved).
*/
powerhook_establish(device_xname(h->ph_parent), pcic_power, h);
/* enable interrupts on card detect, poll for them if no irq avail */
reg = PCIC_CSC_INTR_CD_ENABLE;
if (sc->irq == -1) {
if (sc->poll_established == 0) {
callout_init(&sc->poll_ch, 0);
callout_reset(&sc->poll_ch, hz / 2, pcic_poll_intr, sc);
sc->poll_established = 1;
}
} else
reg |= sc->irq << PCIC_CSC_INTR_IRQ_SHIFT;
pcic_write(h, PCIC_CSC_INTR, reg);
/* steer above mgmt interrupt to configured place */
if (sc->irq == 0)
pcic_write(h, PCIC_INTR, PCIC_INTR_ENABLE);
/* clear possible card detect interrupt */
(void) pcic_read(h, PCIC_CSC);
int
pcic_chip_mem_map(pcmcia_chipset_handle_t pch, int kind, bus_addr_t card_addr,
bus_size_t size, struct pcmcia_mem_handle *pcmhp, bus_size_t *offsetp,
int *windowp)
{
struct pcic_handle *h = (struct pcic_handle *) pch;
bus_addr_t busaddr;
long card_offset;
int i, win;
win = -1;
for (i = 0; i < (sizeof(mem_map_index) / sizeof(mem_map_index[0]));
i++) {
if ((h->memalloc & (1 << i)) == 0) {
win = i;
h->memalloc |= (1 << i);
break;
}
}
if (win == -1)
return 1;
*windowp = win;
/* XXX this is pretty gross */
{
struct pcic_softc *sc = device_private(h->ph_parent);
if (!bus_space_is_equal(sc->memt, pcmhp->memt))
panic("pcic_chip_mem_map memt is bogus");
}
busaddr = pcmhp->addr;
/*
* compute the address offset to the pcmcia address space for the
* pcic. this is intentionally signed. The masks and shifts below
* will cause TRT to happen in the pcic registers. Deal with making
* sure the address is aligned, and return the alignment offset.
*/
static int
pcic_wait_ready(struct pcic_handle *h)
{
uint8_t stat;
int i;
/* wait an initial 10ms for quick cards */
stat = pcic_read(h, PCIC_IF_STATUS);
if (stat & PCIC_IF_STATUS_READY)
return 0;
pcic_delay(h, 10, "pccwr0");
for (i = 0; i < 50; i++) {
stat = pcic_read(h, PCIC_IF_STATUS);
if (stat & PCIC_IF_STATUS_READY)
return 0;
if ((stat & PCIC_IF_STATUS_CARDDETECT_MASK) !=
PCIC_IF_STATUS_CARDDETECT_PRESENT)
return ENXIO;
/* wait .1s (100ms) each iteration now */
pcic_delay(h, 100, "pccwr1");
}
printf("pcic_wait_ready: ready never happened, status=%02x\n", stat);
return EWOULDBLOCK;
}
/*
* Perform long (msec order) delay.
*/
static void
pcic_delay(struct pcic_handle *h, int timo, const char *wmesg)
/* timo: in ms. must not be zero */
{
#ifdef DIAGNOSTIC
if (timo <= 0)
panic("pcic_delay: called with timeout %d", timo);
if (!curlwp)
panic("pcic_delay: called in interrupt context");
if (!h->event_thread)
panic("pcic_delay: no event thread");
#endif
DPRINTF(("pcic_delay: \"%s\" %p, sleep %d ms\n",
wmesg, h->event_thread, timo));
if (doing_shutdown)
delay(timo * 1000);
else
tsleep(pcic_delay, PWAIT, wmesg,
roundup(timo * hz, 1000) / 1000);
}
/* power up the socket */
power |= PCIC_PWRCTL_PWR_ENABLE | PCIC_PWRCTL_VPP1_VCC;
pcic_write(h, PCIC_PWRCTL, power);
/*
* Table 4-18 and figure 4-6 of the PC Card specifiction say:
* Vcc Rising Time (Tpr) = 100ms
* RESET Width (Th (Hi-z RESET)) = 1ms
* RESET Width (Tw (RESET)) = 10us
*
* some machines require some more time to be settled
* (100ms is added here).
*/
pcic_delay(h, 200 + 1, "pccen1");