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