kernel 2.6.13 - socket driver set_io_map() no longer called?
Kars de Jong
jongk at linux-m68k.org
Sat Sep 17 12:11:52 EDT 2005
On za, 2005-09-17 at 14:50 +0200, Dominik Brodowski wrote:
> On Thu, Sep 15, 2005 at 10:23:10PM +0200, Kars de Jong wrote:
> > I wrote a socket driver for my Amiga 1200 PCMCIA socket a long time ago,
> > but never got it included in the mainstream kernel yet. I adapted it to
> > the 2.6 model at the beginning of this year and had it working OK with
> > kernel version 2.6.8.1.
>
> Can you share the source with us?
Sure, the driver is included below. It's really simple, because the
Gayle hardware is really dumb. Also, a lot of cards don't work because
the hardware is very broken. They don't get detected, or hang, or reboot
the machine when inserted, etc. The serial_cs and pcnet_cs drivers seem
to work best, and with a hack the 3c589_cs driver works as well.
> > The driver is of type SS_CAP_STATIC_MAP, but (and this is different from
> > all other "static" drivers) the io_offset field is 0.
>
> As all other in-kernel drivers did set io_offset, I missed this alternative
> back when I introduced pccard_*_ops. As the MPC8xx socket driver just got
> added to -mm, I first became aware of this issue, and still need to manage
> it correctly.
OK, no problem.
> > Compared to 2.6.8.1, I have set the resource_ops field to
> > &pccard_static_ops.
>
> As a workaround, you can simply switch to pccard_nonstatic_ops in the
> meantime. pccard_static_ops is, for now, just a shortcut for the
> common SS_CAP_STATIC_MAP and io_offset==0 case. Also, that's the workaround
> used by the MPC8xx socket driver...
OK, I tried that, that works OK :-)
> Can you send me a /proc/ioports output when a card is up and running in the
> socket? Also, please send along a /etc/pcmcia/config.opts and tell me
> whether all ports not marked as "used" in /proc/ioports can be made
> available to PCMCIA.
All ports are available to PCMCIA, since it is the only possible user of
I/O ports on my system, so I never bothered to set
up /etc/pcmcia/config.opts. However, there is some horrible hack in the
m68k code for inb()/outb() and friends which considers ports >= 1024 to
be PCI, so I suppose that should be considered.
Here's /proc/ioports with a pcnet_cs card:
0300-031f : pcmcia_socket0
/etc/pcmcia/config.opts:
include port 0x000-0x3ff
Kind regards,
Kars.
/*
* Device driver for the Amiga Gayle PCMCIA controller
*
* (C) Copyright 2000-2005 Kars de Jong <jongk at linux-m68k.org>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/ss.h>
#include <asm/amigaints.h>
#include <asm/amipcmcia.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kars de Jong <jongk at linux-m68k.org>");
MODULE_DESCRIPTION("Linux PCMCIA Card Services: Amiga Gayle Socket Controller");
struct gayle_socket_info {
struct platform_device *pdev;
struct pcmcia_socket psocket;
u_int csc_mask;
u_char reset_inten;
u_char intena;
u_char iocard;
u_char reset;
u_short speed;
};
static struct gayle_socket_info socket;
static int gayle_pcmcia_init(struct pcmcia_socket *s)
{
return 0;
}
static int gayle_pcmcia_get_status(struct pcmcia_socket *s, u_int *value)
{
u_char status;
u_int val = 0;
status = gayle.cardstatus;
val |= (status & GAYLE_CS_CCDET) ? (SS_DETECT | SS_POWERON) : 0;
if (socket.iocard) {
val |= (status & GAYLE_CS_SC) ? SS_STSCHG : 0;
} else {
val |= (status & GAYLE_CS_WR) ? 0 : SS_WRPROT;
val |= (status & GAYLE_CS_BSY) ? 0 : SS_READY;
val |= (status & GAYLE_CS_BVD1) ? SS_BATDEAD : 0;
val |= (status & GAYLE_CS_BVD2) ? SS_BATWARN : 0;
}
*value = val;
return 0;
}
static int gayle_pcmcia_get_socket(struct pcmcia_socket *s, socket_state_t *state)
{
u_char reg, vpp;
state->flags = SS_PWR_AUTO; /* No power management features */
state->Vcc = 50; /* Only 5V cards */
state->Vpp = 0;
reg = gayle.config;
vpp = reg & GAYLE_VPP_MASK;
if (vpp == GAYLE_CFG_5V)
state->Vpp = 50;
if (vpp == GAYLE_CFG_12V)
state->Vpp = 120;
/* IO card, IO interrupt */
reg = gayle.cardstatus;
if (socket.iocard) {
state->flags |= SS_IOCARD;
if (reg & GAYLE_CS_WR) state->flags |= SS_OUTPUT_ENA;
if (reg & GAYLE_CS_DA) state->flags |= SS_SPKR_ENA;
state->io_irq = socket.psocket.pci_irq;
}
if (socket.reset) state->flags |= SS_RESET;
/* Card status interrupt change mask */
state->csc_mask = socket.csc_mask;
return 0;
}
static int gayle_pcmcia_set_socket(struct pcmcia_socket *s, socket_state_t *state)
{
u_char oldreg, reg;
u_long flags;
u_int changed;
local_irq_save(flags); /* Don't want interrupts happening here */
socket.iocard = (state->flags & SS_IOCARD) ? 1 : 0;
oldreg = reg = gayle.config;
reg &= ~GAYLE_VPP_MASK;
switch (state->Vcc) {
case 0: break;
case 50: break;
default: return -EINVAL;
}
switch (state->Vpp) {
case 0: break;
case 50: reg |= GAYLE_CFG_5V; break;
case 120: reg |= GAYLE_CFG_12V; break;
default: return -EINVAL;
}
if (reg != oldreg)
gayle.config = reg;
if (state->flags & SS_RESET) {
socket.reset_inten = gayle.inten;
gayle.inten = (socket.reset_inten & ~(GAYLE_IRQ_BVD1|GAYLE_IRQ_BVD2|GAYLE_IRQ_WR|GAYLE_IRQ_BSY));
gayle.intreq = 0xff;
socket.reset = 1;
} else if (socket.reset) {
gayle.intreq = 0xfc;
udelay(10);
gayle.intreq = (0xfc & ~(GAYLE_IRQ_BVD1|GAYLE_IRQ_BVD2|GAYLE_IRQ_WR|GAYLE_IRQ_BSY));
gayle.inten = socket.reset_inten;
socket.reset = 0;
}
oldreg = reg = (gayle.cardstatus & (GAYLE_CS_WR|GAYLE_CS_DA|GAYLE_CS_DAEN));
if (socket.iocard) {
if (state->flags & SS_OUTPUT_ENA) {
reg |= GAYLE_CS_WR|GAYLE_CS_DAEN;
}
if (state->flags & SS_SPKR_ENA) {
reg |= GAYLE_CS_DA|GAYLE_CS_DAEN;
}
} else {
reg &= ~(GAYLE_CS_WR|GAYLE_CS_DA|GAYLE_CS_DAEN);
}
if (reg != oldreg) {
gayle.cardstatus = reg;
gayle.intreq = (0xfc & ~(GAYLE_IRQ_WR|GAYLE_IRQ_DA));
}
/* Card status change interrupt mask */
changed = socket.csc_mask ^ state->csc_mask;
socket.csc_mask = state->csc_mask;
oldreg = reg = gayle.inten;
if (changed & SS_DETECT) {
if (state->csc_mask & SS_DETECT)
reg |= GAYLE_IRQ_CCDET;
else
reg &= ~GAYLE_IRQ_CCDET;
}
if (changed & SS_READY) {
if (state->csc_mask & SS_READY)
reg |= GAYLE_IRQ_BSY;
else
reg &= ~GAYLE_IRQ_BSY;
}
if (changed & SS_BATDEAD) {
if (state->csc_mask & SS_BATDEAD)
reg |= GAYLE_IRQ_BVD1;
else
reg &= ~GAYLE_IRQ_BVD1;
}
if (changed & SS_BATWARN) {
if (state->csc_mask & SS_BATWARN)
reg |= GAYLE_IRQ_BVD2;
else
reg &= ~GAYLE_IRQ_BVD2;
}
if (changed & SS_STSCHG) {
if (state->csc_mask & SS_STSCHG)
reg |= GAYLE_IRQ_SC;
else
reg &= ~GAYLE_IRQ_SC;
}
if (reg != oldreg) {
gayle.inten = reg;
socket.intena = (gayle.inten & (GAYLE_IRQ_CCDET|GAYLE_IRQ_BVD1|GAYLE_IRQ_BVD2|GAYLE_IRQ_WR|GAYLE_IRQ_BSY));
}
local_irq_restore(flags);
return 0;
}
static int gayle_pcmcia_set_io_map(struct pcmcia_socket *sock, struct pccard_io_map *map)
{
if (map->map >= MAX_IO_WIN) {
printk(KERN_ERR "%s(): map (%d) out of range\n", __FUNCTION__,
map->map);
return -1;
}
if (map->stop == 1)
map->stop = PAGE_SIZE-1;
gayle_set_io_win(map->map, map->flags, map->start, map->stop);
return 0;
}
static void gayle_pcmcia_set_speed(u_short speed) {
u_char s;
if (speed <= 100)
s = GAYLE_CFG_100NS;
else if (speed <= 150)
s = GAYLE_CFG_150NS;
else if (speed <= 250)
s = GAYLE_CFG_250NS;
else
s = GAYLE_CFG_720NS;
gayle.config = (gayle.config & ~GAYLE_SPEED_MASK) | s;
}
static int gayle_pcmcia_set_mem_map(struct pcmcia_socket *sock, struct pccard_mem_map *map)
{
u_long start;
if (map->map >= MAX_WIN)
return -EINVAL;
gayle_pcmcia_set_speed(map->speed);
if (map->flags & MAP_ATTRIB) {
start = GAYLE_ATTRIBUTE;
if (map->flags & MAP_ACTIVE)
gayle_pcmcia_set_speed(720);
} else {
start = GAYLE_RAM;
}
map->static_start = start + map->card_start;
return 0;
}
static irqreturn_t gayle_pcmcia_interrupt(int irq, void *dev, struct pt_regs *regs)
{
u_char sstat, ints, latch, ack = 0xfc;
u_int events = 0;
ints = gayle.intreq;
sstat = gayle.cardstatus;
latch = ints & socket.intena;
if (latch & GAYLE_IRQ_CCDET) {
/* Check for card removal */
if (!(gayle.cardstatus & GAYLE_CS_CCDET)) {
/* Better clear all ints */
ack &= ~(GAYLE_IRQ_BVD1|GAYLE_IRQ_BVD2|GAYLE_IRQ_WR|GAYLE_IRQ_BSY);
/* Turn off all IO interrupts */
if (socket.iocard) {
gayle.inten &= ~GAYLE_IRQ_IRQ;
gayle.cardstatus = 0;
}
/* Don't do the rest unless a card is present */
latch &= ~(GAYLE_IRQ_BVD1|GAYLE_IRQ_BVD2|GAYLE_IRQ_WR|GAYLE_IRQ_BSY);
}
ack &= ~GAYLE_IRQ_CCDET;
events |= SS_DETECT;
}
if (latch & GAYLE_IRQ_BSY) {
ack &= ~GAYLE_IRQ_BSY;
events |= SS_READY;
}
if (latch & GAYLE_IRQ_BVD2) {
ack &= ~GAYLE_IRQ_BVD2;
events |= SS_BATWARN;
}
if (latch & GAYLE_IRQ_BVD1) {
ack &= ~GAYLE_IRQ_BVD1;
events |= SS_BATDEAD;
}
if (latch & GAYLE_IRQ_SC) {
ack &= ~GAYLE_IRQ_SC;
events |= SS_STSCHG;
}
gayle.intreq = ack;
if (events)
pcmcia_parse_events(&socket.psocket, events);
return IRQ_HANDLED;
}
static struct pccard_operations gayle_pcmcia_operations = {
.init = gayle_pcmcia_init,
.get_status = gayle_pcmcia_get_status,
.get_socket = gayle_pcmcia_get_socket,
.set_socket = gayle_pcmcia_set_socket,
.set_io_map = gayle_pcmcia_set_io_map,
.set_mem_map = gayle_pcmcia_set_mem_map,
};
static int gayle_pcmcia_drv_suspend(struct device *dev, pm_message_t state, u32 level)
{
int ret = 0;
if (level == SUSPEND_SAVE_STATE)
ret = pcmcia_socket_dev_suspend(dev, state);
return ret;
}
static int gayle_pcmcia_drv_resume(struct device *dev, u32 level)
{
int ret = 0;
if (level == RESUME_RESTORE_STATE)
ret = pcmcia_socket_dev_resume(dev);
return ret;
}
static struct device_driver gayle_pcmcia_driver = {
.name = "gayle-pcmcia",
.bus = &platform_bus_type,
.suspend = gayle_pcmcia_drv_suspend,
.resume = gayle_pcmcia_drv_resume,
};
static int __init init_gayle_pcmcia(void)
{
int err;
if (!AMIGAHW_PRESENT(PCMCIA)) {
return -ENODEV;
} else {
printk(KERN_INFO "Amiga Gayle PCMCIA found, 1 socket\n");
}
if (!request_mem_region(GAYLE_RAM, GAYLE_RAMSIZE, "PCMCIA common memory"))
return -EBUSY;
if (!request_mem_region(GAYLE_ATTRIBUTE, GAYLE_ATTRIBUTESIZE,
"PCMCIA attribute memory")) {
release_mem_region(GAYLE_RAM, GAYLE_RAMSIZE);
return -EBUSY;
}
if (!request_mem_region(GAYLE_IO, 2*GAYLE_IOSIZE, "PCMCIA I/O ports")) {
release_mem_region(GAYLE_ATTRIBUTE, GAYLE_ATTRIBUTESIZE);
release_mem_region(GAYLE_RAM, GAYLE_RAMSIZE);
return -EBUSY;
}
if (driver_register(&gayle_pcmcia_driver)) {
release_mem_region(GAYLE_IO, 2*GAYLE_IOSIZE);
release_mem_region(GAYLE_ATTRIBUTE, GAYLE_ATTRIBUTESIZE);
release_mem_region(GAYLE_RAM, GAYLE_RAMSIZE);
return -1;
}
gayle.config = 0;
if ((err = request_irq(IRQ_AMIGA_GAYLE_SOCKET, gayle_pcmcia_interrupt, 0, "Gayle PCMCIA status", &socket)) < 0) {
driver_unregister(&gayle_pcmcia_driver);
release_mem_region(GAYLE_IO, 2*GAYLE_IOSIZE);
release_mem_region(GAYLE_ATTRIBUTE, GAYLE_ATTRIBUTESIZE);
release_mem_region(GAYLE_RAM, GAYLE_RAMSIZE);
return err;
}
printk(KERN_INFO " status change on irq %d\n", IRQ_AMIGA_GAYLE_SOCKET);
socket.psocket.owner = THIS_MODULE;
socket.psocket.ops = &gayle_pcmcia_operations;
socket.psocket.resource_ops = &pccard_nonstatic_ops;
socket.psocket.features = SS_CAP_STATIC_MAP|SS_CAP_PCCARD;
socket.psocket.irq_mask = 0;
socket.psocket.map_size = PAGE_SIZE;
socket.psocket.pci_irq = IRQ_AMIGA_GAYLE_IRQ;
socket.psocket.io_offset = 0;
socket.intena = (gayle.inten & ~GAYLE_IRQ_IDE);
gayle.cardstatus = 0;
gayle.intreq = (0xfc & ~(GAYLE_IRQ_BVD1|GAYLE_IRQ_BVD2|GAYLE_IRQ_WR|GAYLE_IRQ_BSY));
socket.speed = 250;
socket.pdev = platform_device_register_simple("gayle-pcmcia", -1,
NULL, 0);
if (IS_ERR(socket.pdev)) {
free_irq(IRQ_AMIGA_GAYLE_SOCKET, &socket);
driver_unregister(&gayle_pcmcia_driver);
release_mem_region(GAYLE_IO, 2*GAYLE_IOSIZE);
release_mem_region(GAYLE_ATTRIBUTE, GAYLE_ATTRIBUTESIZE);
release_mem_region(GAYLE_RAM, GAYLE_RAMSIZE);
return PTR_ERR(socket.pdev);
}
socket.psocket.dev.dev = &socket.pdev->dev;
if ((err = pcmcia_register_socket(&socket.psocket))) {
platform_device_unregister(socket.pdev);
free_irq(IRQ_AMIGA_GAYLE_SOCKET, &socket);
driver_unregister(&gayle_pcmcia_driver);
release_mem_region(GAYLE_IO, 2*GAYLE_IOSIZE);
release_mem_region(GAYLE_ATTRIBUTE, GAYLE_ATTRIBUTESIZE);
release_mem_region(GAYLE_RAM, GAYLE_RAMSIZE);
return err;
}
return 0;
}
static void __exit exit_gayle_pcmcia(void)
{
u_long flags;
local_irq_save(flags); /* Don't want interrupts happening here */
pcmcia_unregister_socket(&socket.psocket);
gayle.inten &= ~(GAYLE_IRQ_BVD1|GAYLE_IRQ_BVD2|GAYLE_IRQ_WR|GAYLE_IRQ_BSY);
free_irq(IRQ_AMIGA_GAYLE_SOCKET, &socket);
platform_device_unregister(socket.pdev);
driver_unregister(&gayle_pcmcia_driver);
release_mem_region(GAYLE_IO, 2*GAYLE_IOSIZE);
release_mem_region(GAYLE_ATTRIBUTE, GAYLE_ATTRIBUTESIZE);
release_mem_region(GAYLE_RAM, GAYLE_RAMSIZE);
local_irq_restore(flags);
}
module_init(init_gayle_pcmcia);
module_exit(exit_gayle_pcmcia);
More information about the linux-pcmcia
mailing list