2019-05-19 20:08:20 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2019-12-13 21:53:06 +08:00
|
|
|
/*
|
2005-04-17 06:20:36 +08:00
|
|
|
* Driver for Intel I82092AA PCI-PCMCIA bridge.
|
|
|
|
*
|
|
|
|
* (C) 2001 Red Hat, Inc.
|
|
|
|
*
|
|
|
|
* Author: Arjan Van De Ven <arjanv@redhat.com>
|
|
|
|
* Loosly based on i82365.c from the pcmcia-cs package
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/pci.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
|
|
|
|
#include <pcmcia/ss.h>
|
|
|
|
|
2019-12-13 21:53:12 +08:00
|
|
|
#include <linux/io.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
#include "i82092aa.h"
|
|
|
|
#include "i82365.h"
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
|
|
/* PCI core routines */
|
2014-07-19 07:58:07 +08:00
|
|
|
static const struct pci_device_id i82092aa_pci_ids[] = {
|
2011-12-27 16:17:46 +08:00
|
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82092AA_0) },
|
|
|
|
{ }
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, i82092aa_pci_ids);
|
|
|
|
|
2008-05-01 19:34:51 +08:00
|
|
|
static struct pci_driver i82092aa_pci_driver = {
|
2019-12-13 21:53:06 +08:00
|
|
|
.name = "i82092aa",
|
|
|
|
.id_table = i82092aa_pci_ids,
|
|
|
|
.probe = i82092aa_pci_probe,
|
|
|
|
.remove = i82092aa_pci_remove,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* the pccard structure and its functions */
|
|
|
|
static struct pccard_operations i82092aa_operations = {
|
2019-12-13 21:53:06 +08:00
|
|
|
.init = i82092aa_init,
|
2005-04-17 06:20:36 +08:00
|
|
|
.get_status = i82092aa_get_status,
|
|
|
|
.set_socket = i82092aa_set_socket,
|
|
|
|
.set_io_map = i82092aa_set_io_map,
|
|
|
|
.set_mem_map = i82092aa_set_mem_map,
|
|
|
|
};
|
|
|
|
|
2011-03-31 09:57:33 +08:00
|
|
|
/* The card can do up to 4 sockets, allocate a structure for each of them */
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
struct socket_info {
|
|
|
|
int number;
|
2019-12-13 21:53:06 +08:00
|
|
|
int card_state;
|
|
|
|
/* 0 = no socket,
|
|
|
|
* 1 = empty socket,
|
|
|
|
* 2 = card but not initialized,
|
|
|
|
* 3 = operational card
|
|
|
|
*/
|
|
|
|
unsigned int io_base; /* base io address of the socket */
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
struct pcmcia_socket socket;
|
|
|
|
struct pci_dev *dev; /* The PCI device for the socket */
|
|
|
|
};
|
|
|
|
|
|
|
|
#define MAX_SOCKETS 4
|
|
|
|
static struct socket_info sockets[MAX_SOCKETS];
|
2019-12-13 21:53:06 +08:00
|
|
|
static int socket_count; /* shortcut */
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
|
2019-12-13 21:53:11 +08:00
|
|
|
static int i82092aa_pci_probe(struct pci_dev *dev,
|
|
|
|
const struct pci_device_id *id)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
unsigned char configbyte;
|
|
|
|
int i, ret;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2019-12-13 21:53:10 +08:00
|
|
|
ret = pci_enable_device(dev);
|
|
|
|
if (ret)
|
2005-04-17 06:20:36 +08:00
|
|
|
return ret;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2019-12-13 21:53:11 +08:00
|
|
|
/* PCI Configuration Control */
|
|
|
|
pci_read_config_byte(dev, 0x40, &configbyte);
|
|
|
|
|
2019-12-13 21:53:06 +08:00
|
|
|
switch (configbyte&6) {
|
2019-12-13 21:53:09 +08:00
|
|
|
case 0:
|
|
|
|
socket_count = 2;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
socket_count = 1;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
case 6:
|
|
|
|
socket_count = 4;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
dev_err(&dev->dev,
|
|
|
|
"Oops, you did something we didn't think of.\n");
|
|
|
|
ret = -EIO;
|
|
|
|
goto err_out_disable;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2019-12-13 21:53:04 +08:00
|
|
|
dev_info(&dev->dev, "configured as a %d socket device.\n",
|
|
|
|
socket_count);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (!request_region(pci_resource_start(dev, 0), 2, "i82092aa")) {
|
|
|
|
ret = -EBUSY;
|
|
|
|
goto err_out_disable;
|
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
|
|
|
for (i = 0; i < socket_count; i++) {
|
2005-04-17 06:20:36 +08:00
|
|
|
sockets[i].card_state = 1; /* 1 = present but empty */
|
|
|
|
sockets[i].io_base = pci_resource_start(dev, 0);
|
2021-06-22 15:11:31 +08:00
|
|
|
sockets[i].dev = dev;
|
2005-04-17 06:20:36 +08:00
|
|
|
sockets[i].socket.features |= SS_CAP_PCCARD;
|
|
|
|
sockets[i].socket.map_size = 0x1000;
|
|
|
|
sockets[i].socket.irq_mask = 0;
|
|
|
|
sockets[i].socket.pci_irq = dev->irq;
|
2010-03-14 00:42:39 +08:00
|
|
|
sockets[i].socket.cb_dev = dev;
|
2005-04-17 06:20:36 +08:00
|
|
|
sockets[i].socket.owner = THIS_MODULE;
|
|
|
|
|
|
|
|
sockets[i].number = i;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (card_present(i)) {
|
|
|
|
sockets[i].card_state = 3;
|
2019-12-13 21:53:04 +08:00
|
|
|
dev_dbg(&dev->dev, "slot %i is occupied\n", i);
|
2005-04-17 06:20:36 +08:00
|
|
|
} else {
|
2019-12-13 21:53:04 +08:00
|
|
|
dev_dbg(&dev->dev, "slot %i is vacant\n", i);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2019-12-13 21:53:11 +08:00
|
|
|
/* Now, specifiy that all interrupts are to be done as PCI interrupts
|
|
|
|
* bitmask, one bit per event, 1 = PCI interrupt, 0 = ISA interrupt
|
|
|
|
*/
|
|
|
|
configbyte = 0xFF;
|
|
|
|
|
|
|
|
/* PCI Interrupt Routing Register */
|
|
|
|
pci_write_config_byte(dev, 0x50, configbyte);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Register the interrupt handler */
|
2019-08-25 13:35:10 +08:00
|
|
|
dev_dbg(&dev->dev, "Requesting interrupt %i\n", dev->irq);
|
2019-12-13 21:53:10 +08:00
|
|
|
ret = request_irq(dev->irq, i82092aa_interrupt, IRQF_SHARED,
|
|
|
|
"i82092aa", i82092aa_interrupt);
|
|
|
|
if (ret) {
|
2019-12-13 21:53:04 +08:00
|
|
|
dev_err(&dev->dev, "Failed to register IRQ %d, aborting\n",
|
|
|
|
dev->irq);
|
2005-04-17 06:20:36 +08:00
|
|
|
goto err_out_free_res;
|
|
|
|
}
|
|
|
|
|
2019-12-13 21:53:06 +08:00
|
|
|
for (i = 0; i < socket_count; i++) {
|
2006-09-12 23:00:10 +08:00
|
|
|
sockets[i].socket.dev.parent = &dev->dev;
|
2005-04-17 06:20:36 +08:00
|
|
|
sockets[i].socket.ops = &i82092aa_operations;
|
|
|
|
sockets[i].socket.resource_ops = &pccard_nonstatic_ops;
|
|
|
|
ret = pcmcia_register_socket(&sockets[i].socket);
|
2019-12-13 21:53:07 +08:00
|
|
|
if (ret)
|
2005-04-17 06:20:36 +08:00
|
|
|
goto err_out_free_sockets;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_out_free_sockets:
|
|
|
|
if (i) {
|
2019-12-13 21:53:07 +08:00
|
|
|
for (i--; i >= 0; i--)
|
2005-04-17 06:20:36 +08:00
|
|
|
pcmcia_unregister_socket(&sockets[i].socket);
|
|
|
|
}
|
|
|
|
free_irq(dev->irq, i82092aa_interrupt);
|
|
|
|
err_out_free_res:
|
|
|
|
release_region(pci_resource_start(dev, 0), 2);
|
|
|
|
err_out_disable:
|
|
|
|
pci_disable_device(dev);
|
2019-12-13 21:53:06 +08:00
|
|
|
return ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2012-11-20 02:26:05 +08:00
|
|
|
static void i82092aa_pci_remove(struct pci_dev *dev)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2012-12-14 23:01:08 +08:00
|
|
|
int i;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
free_irq(dev->irq, i82092aa_interrupt);
|
|
|
|
|
2012-12-14 23:01:08 +08:00
|
|
|
for (i = 0; i < socket_count; i++)
|
|
|
|
pcmcia_unregister_socket(&sockets[i].socket);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static DEFINE_SPINLOCK(port_lock);
|
|
|
|
|
|
|
|
/* basic value read/write functions */
|
|
|
|
|
|
|
|
static unsigned char indirect_read(int socket, unsigned short reg)
|
|
|
|
{
|
|
|
|
unsigned short int port;
|
|
|
|
unsigned char val;
|
|
|
|
unsigned long flags;
|
2019-12-13 21:53:08 +08:00
|
|
|
|
2019-12-13 21:53:06 +08:00
|
|
|
spin_lock_irqsave(&port_lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
reg += socket * 0x40;
|
|
|
|
port = sockets[socket].io_base;
|
2019-12-13 21:53:06 +08:00
|
|
|
outb(reg, port);
|
2005-04-17 06:20:36 +08:00
|
|
|
val = inb(port+1);
|
2019-12-13 21:53:06 +08:00
|
|
|
spin_unlock_irqrestore(&port_lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void indirect_write(int socket, unsigned short reg, unsigned char value)
|
|
|
|
{
|
|
|
|
unsigned short int port;
|
|
|
|
unsigned long flags;
|
2019-12-13 21:53:08 +08:00
|
|
|
|
2019-12-13 21:53:06 +08:00
|
|
|
spin_lock_irqsave(&port_lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
reg = reg + socket * 0x40;
|
2019-12-13 21:53:06 +08:00
|
|
|
port = sockets[socket].io_base;
|
|
|
|
outb(reg, port);
|
|
|
|
outb(value, port+1);
|
|
|
|
spin_unlock_irqrestore(&port_lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void indirect_setbit(int socket, unsigned short reg, unsigned char mask)
|
|
|
|
{
|
|
|
|
unsigned short int port;
|
|
|
|
unsigned char val;
|
|
|
|
unsigned long flags;
|
2019-12-13 21:53:08 +08:00
|
|
|
|
2019-12-13 21:53:06 +08:00
|
|
|
spin_lock_irqsave(&port_lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
reg = reg + socket * 0x40;
|
2019-12-13 21:53:06 +08:00
|
|
|
port = sockets[socket].io_base;
|
|
|
|
outb(reg, port);
|
2005-04-17 06:20:36 +08:00
|
|
|
val = inb(port+1);
|
|
|
|
val |= mask;
|
2019-12-13 21:53:06 +08:00
|
|
|
outb(reg, port);
|
|
|
|
outb(val, port+1);
|
|
|
|
spin_unlock_irqrestore(&port_lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-12-13 21:53:11 +08:00
|
|
|
static void indirect_resetbit(int socket,
|
|
|
|
unsigned short reg, unsigned char mask)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
unsigned short int port;
|
|
|
|
unsigned char val;
|
|
|
|
unsigned long flags;
|
2019-12-13 21:53:08 +08:00
|
|
|
|
2019-12-13 21:53:06 +08:00
|
|
|
spin_lock_irqsave(&port_lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
reg = reg + socket * 0x40;
|
2019-12-13 21:53:06 +08:00
|
|
|
port = sockets[socket].io_base;
|
|
|
|
outb(reg, port);
|
2005-04-17 06:20:36 +08:00
|
|
|
val = inb(port+1);
|
|
|
|
val &= ~mask;
|
2019-12-13 21:53:06 +08:00
|
|
|
outb(reg, port);
|
|
|
|
outb(val, port+1);
|
|
|
|
spin_unlock_irqrestore(&port_lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2019-12-13 21:53:11 +08:00
|
|
|
static void indirect_write16(int socket,
|
|
|
|
unsigned short reg, unsigned short value)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
unsigned short int port;
|
|
|
|
unsigned char val;
|
|
|
|
unsigned long flags;
|
2019-12-13 21:53:08 +08:00
|
|
|
|
2019-12-13 21:53:06 +08:00
|
|
|
spin_lock_irqsave(&port_lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
reg = reg + socket * 0x40;
|
2019-12-13 21:53:06 +08:00
|
|
|
port = sockets[socket].io_base;
|
|
|
|
|
|
|
|
outb(reg, port);
|
2005-04-17 06:20:36 +08:00
|
|
|
val = value & 255;
|
2019-12-13 21:53:06 +08:00
|
|
|
outb(val, port+1);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
reg++;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
|
|
|
outb(reg, port);
|
2005-04-17 06:20:36 +08:00
|
|
|
val = value>>8;
|
2019-12-13 21:53:06 +08:00
|
|
|
outb(val, port+1);
|
|
|
|
spin_unlock_irqrestore(&port_lock, flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* simple helper functions */
|
|
|
|
/* External clock time, in nanoseconds. 120 ns = 8.33 MHz */
|
|
|
|
static int cycle_time = 120;
|
|
|
|
|
|
|
|
static int to_cycles(int ns)
|
|
|
|
{
|
2019-12-13 21:53:06 +08:00
|
|
|
if (cycle_time != 0)
|
2005-04-17 06:20:36 +08:00
|
|
|
return ns/cycle_time;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Interrupt handler functionality */
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
|
|
|
static irqreturn_t i82092aa_interrupt(int irq, void *dev)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int loopcount = 0;
|
|
|
|
int handled = 0;
|
|
|
|
|
2019-12-13 21:53:06 +08:00
|
|
|
unsigned int events, active = 0;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
while (1) {
|
|
|
|
loopcount++;
|
2019-12-13 21:53:06 +08:00
|
|
|
if (loopcount > 20) {
|
2019-12-13 21:53:04 +08:00
|
|
|
pr_err("i82092aa: infinite eventloop in interrupt\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
active = 0;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
|
|
|
for (i = 0; i < socket_count; i++) {
|
2005-04-17 06:20:36 +08:00
|
|
|
int csc;
|
2019-12-13 21:53:08 +08:00
|
|
|
|
2019-12-13 21:53:11 +08:00
|
|
|
/* Inactive socket, should not happen */
|
|
|
|
if (sockets[i].card_state == 0)
|
2019-12-13 21:53:06 +08:00
|
|
|
continue;
|
|
|
|
|
2019-12-13 21:53:11 +08:00
|
|
|
/* card status change register */
|
|
|
|
csc = indirect_read(i, I365_CSC);
|
2019-12-13 21:53:06 +08:00
|
|
|
|
|
|
|
if (csc == 0) /* no events on this socket */
|
2005-04-17 06:20:36 +08:00
|
|
|
continue;
|
|
|
|
handled = 1;
|
|
|
|
events = 0;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (csc & I365_CSC_DETECT) {
|
|
|
|
events |= SS_DETECT;
|
2019-12-13 21:53:04 +08:00
|
|
|
dev_info(&sockets[i].dev->dev,
|
|
|
|
"Card detected in socket %i!\n", i);
|
2019-12-13 21:53:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (indirect_read(i, I365_INTCTL) & I365_PC_IOCARD) {
|
2005-04-17 06:20:36 +08:00
|
|
|
/* For IO/CARDS, bit 0 means "read the card" */
|
2019-12-13 21:53:11 +08:00
|
|
|
if (csc & I365_CSC_STSCHG)
|
|
|
|
events |= SS_STSCHG;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else {
|
|
|
|
/* Check for battery/ready events */
|
2019-12-13 21:53:11 +08:00
|
|
|
if (csc & I365_CSC_BVD1)
|
|
|
|
events |= SS_BATDEAD;
|
|
|
|
if (csc & I365_CSC_BVD2)
|
|
|
|
events |= SS_BATWARN;
|
|
|
|
if (csc & I365_CSC_READY)
|
|
|
|
events |= SS_READY;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2019-12-13 21:53:07 +08:00
|
|
|
if (events)
|
2005-04-17 06:20:36 +08:00
|
|
|
pcmcia_parse_events(&sockets[i].socket, events);
|
|
|
|
active |= events;
|
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
|
|
|
if (active == 0) /* no more events to handle */
|
|
|
|
break;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
return IRQ_RETVAL(handled);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* socket functions */
|
|
|
|
|
|
|
|
static int card_present(int socketno)
|
2019-12-13 21:53:06 +08:00
|
|
|
{
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned int val;
|
2019-12-13 21:53:08 +08:00
|
|
|
|
2019-12-13 21:53:06 +08:00
|
|
|
if ((socketno < 0) || (socketno >= MAX_SOCKETS))
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
if (sockets[socketno].io_base == 0)
|
|
|
|
return 0;
|
|
|
|
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
val = indirect_read(socketno, 1); /* Interface status register */
|
2019-12-13 21:53:13 +08:00
|
|
|
if ((val&12) == 12)
|
2005-04-17 06:20:36 +08:00
|
|
|
return 1;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void set_bridge_state(int sock)
|
|
|
|
{
|
2019-12-13 21:53:06 +08:00
|
|
|
indirect_write(sock, I365_GBLCTL, 0x00);
|
|
|
|
indirect_write(sock, I365_GENCTL, 0x00);
|
|
|
|
|
|
|
|
indirect_setbit(sock, I365_INTCTL, 0x08);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int i82092aa_init(struct pcmcia_socket *sock)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct resource res = { .start = 0, .end = 0x0fff };
|
2019-12-13 21:53:06 +08:00
|
|
|
pccard_io_map io = { 0, 0, 0, 0, 1 };
|
2005-04-17 06:20:36 +08:00
|
|
|
pccard_mem_map mem = { .res = &res, };
|
2019-12-13 21:53:06 +08:00
|
|
|
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
io.map = i;
|
|
|
|
i82092aa_set_io_map(sock, &io);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
for (i = 0; i < 5; i++) {
|
|
|
|
mem.map = i;
|
|
|
|
i82092aa_set_mem_map(sock, &mem);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2019-12-13 21:53:13 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static int i82092aa_get_status(struct pcmcia_socket *socket, u_int *value)
|
|
|
|
{
|
2019-12-13 21:53:11 +08:00
|
|
|
unsigned int sock = container_of(socket,
|
|
|
|
struct socket_info, socket)->number;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned int status;
|
2019-12-13 21:53:11 +08:00
|
|
|
|
|
|
|
/* Interface Status Register */
|
|
|
|
status = indirect_read(sock, I365_STATUS);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
*value = 0;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2019-12-13 21:53:07 +08:00
|
|
|
if ((status & I365_CS_DETECT) == I365_CS_DETECT)
|
2005-04-17 06:20:36 +08:00
|
|
|
*value |= SS_DETECT;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* IO cards have a different meaning of bits 0,1 */
|
|
|
|
/* Also notice the inverse-logic on the bits */
|
2019-12-13 21:53:06 +08:00
|
|
|
if (indirect_read(sock, I365_INTCTL) & I365_PC_IOCARD) {
|
2018-11-01 00:46:02 +08:00
|
|
|
/* IO card */
|
|
|
|
if (!(status & I365_CS_STSCHG))
|
|
|
|
*value |= SS_STSCHG;
|
|
|
|
} else { /* non I/O card */
|
|
|
|
if (!(status & I365_CS_BVD1))
|
|
|
|
*value |= SS_BATDEAD;
|
|
|
|
if (!(status & I365_CS_BVD2))
|
|
|
|
*value |= SS_BATWARN;
|
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2018-11-01 00:46:02 +08:00
|
|
|
if (status & I365_CS_WRPROT)
|
|
|
|
(*value) |= SS_WRPROT; /* card is write protected */
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2018-11-01 00:46:02 +08:00
|
|
|
if (status & I365_CS_READY)
|
|
|
|
(*value) |= SS_READY; /* card is not busy */
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2018-11-01 00:46:02 +08:00
|
|
|
if (status & I365_CS_POWERON)
|
|
|
|
(*value) |= SS_POWERON; /* power is applied to the card */
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-12-13 21:53:11 +08:00
|
|
|
static int i82092aa_set_socket(struct pcmcia_socket *socket,
|
|
|
|
socket_state_t *state)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2019-12-13 21:53:04 +08:00
|
|
|
struct socket_info *sock_info = container_of(socket, struct socket_info,
|
|
|
|
socket);
|
|
|
|
unsigned int sock = sock_info->number;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned char reg;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* First, set the global controller options */
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
set_bridge_state(sock);
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Values for the IGENC register */
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
reg = 0;
|
2019-12-13 21:53:11 +08:00
|
|
|
|
|
|
|
/* The reset bit has "inverse" logic */
|
|
|
|
if (!(state->flags & SS_RESET))
|
2019-12-13 21:53:06 +08:00
|
|
|
reg = reg | I365_PC_RESET;
|
|
|
|
if (state->flags & SS_IOCARD)
|
2005-04-17 06:20:36 +08:00
|
|
|
reg = reg | I365_PC_IOCARD;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2019-12-13 21:53:11 +08:00
|
|
|
/* IGENC, Interrupt and General Control Register */
|
|
|
|
indirect_write(sock, I365_INTCTL, reg);
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Power registers */
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
reg = I365_PWR_NORESET; /* default: disable resetdrv on resume */
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (state->flags & SS_PWR_AUTO) {
|
2019-12-13 21:53:04 +08:00
|
|
|
dev_info(&sock_info->dev->dev, "Auto power\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
reg |= I365_PWR_AUTO; /* automatic power mngmnt */
|
|
|
|
}
|
|
|
|
if (state->flags & SS_OUTPUT_ENA) {
|
2019-12-13 21:53:04 +08:00
|
|
|
dev_info(&sock_info->dev->dev, "Power Enabled\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
reg |= I365_PWR_OUT; /* enable power */
|
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
switch (state->Vcc) {
|
2019-12-13 21:53:09 +08:00
|
|
|
case 0:
|
|
|
|
break;
|
|
|
|
case 50:
|
|
|
|
dev_info(&sock_info->dev->dev,
|
|
|
|
"setting voltage to Vcc to 5V on socket %i\n",
|
|
|
|
sock);
|
|
|
|
reg |= I365_VCC_5V;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev_err(&sock_info->dev->dev,
|
|
|
|
"%s called with invalid VCC power value: %i",
|
|
|
|
__func__, state->Vcc);
|
|
|
|
return -EINVAL;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
switch (state->Vpp) {
|
2019-12-13 21:53:09 +08:00
|
|
|
case 0:
|
|
|
|
dev_info(&sock_info->dev->dev,
|
|
|
|
"not setting Vpp on socket %i\n", sock);
|
|
|
|
break;
|
|
|
|
case 50:
|
|
|
|
dev_info(&sock_info->dev->dev,
|
|
|
|
"setting Vpp to 5.0 for socket %i\n", sock);
|
|
|
|
reg |= I365_VPP1_5V | I365_VPP2_5V;
|
|
|
|
break;
|
|
|
|
case 120:
|
|
|
|
dev_info(&sock_info->dev->dev, "setting Vpp to 12.0\n");
|
|
|
|
reg |= I365_VPP1_12V | I365_VPP2_12V;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev_err(&sock_info->dev->dev,
|
|
|
|
"%s called with invalid VPP power value: %i",
|
|
|
|
__func__, state->Vcc);
|
|
|
|
return -EINVAL;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
|
|
|
if (reg != indirect_read(sock, I365_POWER)) /* only write if changed */
|
|
|
|
indirect_write(sock, I365_POWER, reg);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Enable specific interrupt events */
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
reg = 0x00;
|
2019-12-13 21:53:07 +08:00
|
|
|
if (state->csc_mask & SS_DETECT)
|
2005-04-17 06:20:36 +08:00
|
|
|
reg |= I365_CSC_DETECT;
|
|
|
|
if (state->flags & SS_IOCARD) {
|
|
|
|
if (state->csc_mask & SS_STSCHG)
|
|
|
|
reg |= I365_CSC_STSCHG;
|
|
|
|
} else {
|
2019-12-13 21:53:06 +08:00
|
|
|
if (state->csc_mask & SS_BATDEAD)
|
2005-04-17 06:20:36 +08:00
|
|
|
reg |= I365_CSC_BVD1;
|
2019-12-13 21:53:06 +08:00
|
|
|
if (state->csc_mask & SS_BATWARN)
|
2005-04-17 06:20:36 +08:00
|
|
|
reg |= I365_CSC_BVD2;
|
2019-12-13 21:53:06 +08:00
|
|
|
if (state->csc_mask & SS_READY)
|
|
|
|
reg |= I365_CSC_READY;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2019-12-13 21:53:11 +08:00
|
|
|
/* now write the value and clear the (probably bogus) pending stuff
|
|
|
|
* by doing a dummy read
|
|
|
|
*/
|
2019-12-13 21:53:06 +08:00
|
|
|
|
|
|
|
indirect_write(sock, I365_CSCINT, reg);
|
|
|
|
(void)indirect_read(sock, I365_CSC);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-12-13 21:53:11 +08:00
|
|
|
static int i82092aa_set_io_map(struct pcmcia_socket *socket,
|
|
|
|
struct pccard_io_map *io)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2019-12-13 21:53:04 +08:00
|
|
|
struct socket_info *sock_info = container_of(socket, struct socket_info,
|
|
|
|
socket);
|
|
|
|
unsigned int sock = sock_info->number;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned char map, ioctl;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
map = io->map;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
|
|
|
/* Check error conditions */
|
2019-12-13 21:53:13 +08:00
|
|
|
if (map > 1)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
2019-12-13 21:53:13 +08:00
|
|
|
|
2019-12-13 21:53:11 +08:00
|
|
|
if ((io->start > 0xffff) || (io->stop > 0xffff)
|
2019-12-13 21:53:13 +08:00
|
|
|
|| (io->stop < io->start))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2019-12-13 21:53:06 +08:00
|
|
|
/* Turn off the window before changing anything */
|
2005-04-17 06:20:36 +08:00
|
|
|
if (indirect_read(sock, I365_ADDRWIN) & I365_ENA_IO(map))
|
|
|
|
indirect_resetbit(sock, I365_ADDRWIN, I365_ENA_IO(map));
|
|
|
|
|
|
|
|
/* write the new values */
|
2019-12-13 21:53:06 +08:00
|
|
|
indirect_write16(sock, I365_IO(map)+I365_W_START, io->start);
|
|
|
|
indirect_write16(sock, I365_IO(map)+I365_W_STOP, io->stop);
|
|
|
|
|
|
|
|
ioctl = indirect_read(sock, I365_IOCTL) & ~I365_IOCTL_MASK(map);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (io->flags & (MAP_16BIT|MAP_AUTOSZ))
|
|
|
|
ioctl |= I365_IOCTL_16BIT(map);
|
2019-12-13 21:53:06 +08:00
|
|
|
|
|
|
|
indirect_write(sock, I365_IOCTL, ioctl);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Turn the window back on if needed */
|
|
|
|
if (io->flags & MAP_ACTIVE)
|
2019-12-13 21:53:06 +08:00
|
|
|
indirect_setbit(sock, I365_ADDRWIN, I365_ENA_IO(map));
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-12-13 21:53:11 +08:00
|
|
|
static int i82092aa_set_mem_map(struct pcmcia_socket *socket,
|
|
|
|
struct pccard_mem_map *mem)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2019-12-13 21:53:11 +08:00
|
|
|
struct socket_info *sock_info = container_of(socket, struct socket_info,
|
|
|
|
socket);
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned int sock = sock_info->number;
|
|
|
|
struct pci_bus_region region;
|
|
|
|
unsigned short base, i;
|
|
|
|
unsigned char map;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
PCI: Convert pcibios_resource_to_bus() to take a pci_bus, not a pci_dev
These interfaces:
pcibios_resource_to_bus(struct pci_dev *dev, *bus_region, *resource)
pcibios_bus_to_resource(struct pci_dev *dev, *resource, *bus_region)
took a pci_dev, but they really depend only on the pci_bus. And we want to
use them in resource allocation paths where we have the bus but not a
device, so this patch converts them to take the pci_bus instead of the
pci_dev:
pcibios_resource_to_bus(struct pci_bus *bus, *bus_region, *resource)
pcibios_bus_to_resource(struct pci_bus *bus, *resource, *bus_region)
In fact, with standard PCI-PCI bridges, they only depend on the host
bridge, because that's the only place address translation occurs, but
we aren't going that far yet.
[bhelgaas: changelog]
Signed-off-by: Yinghai Lu <yinghai@kernel.org>
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
2013-12-10 14:54:40 +08:00
|
|
|
pcibios_resource_to_bus(sock_info->dev->bus, ®ion, mem->res);
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
map = mem->map;
|
2019-12-13 21:53:13 +08:00
|
|
|
if (map > 4)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
2019-12-13 21:53:06 +08:00
|
|
|
|
|
|
|
if ((mem->card_start > 0x3ffffff) || (region.start > region.end) ||
|
|
|
|
(mem->speed > 1000)) {
|
2019-12-13 21:53:04 +08:00
|
|
|
dev_err(&sock_info->dev->dev,
|
2019-12-13 21:53:11 +08:00
|
|
|
"invalid mem map for socket %i: %llx to %llx with a start of %x\n",
|
2008-02-05 15:35:48 +08:00
|
|
|
sock,
|
|
|
|
(unsigned long long)region.start,
|
|
|
|
(unsigned long long)region.end,
|
|
|
|
mem->card_start);
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Turn off the window before changing anything */
|
|
|
|
if (indirect_read(sock, I365_ADDRWIN) & I365_ENA_MEM(map))
|
2019-12-13 21:53:06 +08:00
|
|
|
indirect_resetbit(sock, I365_ADDRWIN, I365_ENA_MEM(map));
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* write the start address */
|
|
|
|
base = I365_MEM(map);
|
|
|
|
i = (region.start >> 12) & 0x0fff;
|
2019-12-13 21:53:06 +08:00
|
|
|
if (mem->flags & MAP_16BIT)
|
2005-04-17 06:20:36 +08:00
|
|
|
i |= I365_MEM_16BIT;
|
|
|
|
if (mem->flags & MAP_0WS)
|
2019-12-13 21:53:06 +08:00
|
|
|
i |= I365_MEM_0WS;
|
|
|
|
indirect_write16(sock, base+I365_W_START, i);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* write the stop address */
|
2019-12-13 21:53:06 +08:00
|
|
|
|
|
|
|
i = (region.end >> 12) & 0x0fff;
|
2005-04-17 06:20:36 +08:00
|
|
|
switch (to_cycles(mem->speed)) {
|
2019-12-13 21:53:09 +08:00
|
|
|
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;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2019-12-13 21:53:06 +08:00
|
|
|
|
|
|
|
indirect_write16(sock, base+I365_W_STOP, i);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* card start */
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
i = ((mem->card_start - region.start) >> 12) & 0x3fff;
|
|
|
|
if (mem->flags & MAP_WRPROT)
|
|
|
|
i |= I365_MEM_WRPROT;
|
2019-12-13 21:53:04 +08:00
|
|
|
if (mem->flags & MAP_ATTRIB)
|
2005-04-17 06:20:36 +08:00
|
|
|
i |= I365_MEM_REG;
|
2019-12-13 21:53:06 +08:00
|
|
|
indirect_write16(sock, base+I365_W_OFF, i);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Enable the window if necessary */
|
|
|
|
if (mem->flags & MAP_ACTIVE)
|
|
|
|
indirect_setbit(sock, I365_ADDRWIN, I365_ENA_MEM(map));
|
2019-12-13 21:53:06 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-09-22 19:23:23 +08:00
|
|
|
static int __init i82092aa_module_init(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2008-05-01 19:34:51 +08:00
|
|
|
return pci_register_driver(&i82092aa_pci_driver);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2022-09-22 19:23:23 +08:00
|
|
|
static void __exit i82092aa_module_exit(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2008-05-01 19:34:51 +08:00
|
|
|
pci_unregister_driver(&i82092aa_pci_driver);
|
2019-12-13 21:53:06 +08:00
|
|
|
if (sockets[0].io_base > 0)
|
2019-12-13 21:53:09 +08:00
|
|
|
release_region(sockets[0].io_base, 2);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
module_init(i82092aa_module_init);
|
|
|
|
module_exit(i82092aa_module_exit);
|
|
|
|
|