Question regarding IRQF_SHARED and pcmcia driver

Kristoffer Ericson kristoffer.ericson at gmail.com
Fri Jan 11 19:33:19 EST 2008


On Fri, 11 Jan 2008 15:52:18 +0100
Peter Stuge <stuge-linux-pcmcia at cdy.org> wrote:

> On Fri, Jan 11, 2008 at 03:25:23PM -0800, Kristoffer Ericson wrote:
> > > > pcmcia (16bit). hd64461 is a companion chip that provides
> > > > features like pcmcia, lcd,...
> > > 
> > > Ok. Is it actually a PCI device? If not, the driver for it should
> > > probably be a platform driver, like ISA hardware drivers.
> > > 
> > > I had to do some digging to find an example of this in the
> > > kernel, there aren't that many simple ISA drivers left.
> > 
> > Hmmm, I've never actually considerd that. I assumed all PCMCIA
> > systems were PCI similiar.
> 
> CardBus is much like PCI. PCMCIA not so much.
> 

Oki, well I know for sure that the socket only supports 16-bit PCMCIA so might very well be ISA. Since I didn't write the original driver I've mainly focused on re-writing and not checking too much on the datasheet. Ive also documented various functions to be sure what they do.

> 
> > > So then you should be taking care of the interrupt, right?
> > 
> > I install a handler to take care of both interrupts (pccard & slot)
> > but it then looks where the interrupt came from and if its
> > generated by the pccard it returns IRQ_NONE to let the driver
> > interrupt handler take care of it.
> 
> For this to work there must obviously be a card driver loaded when
> the interrupt comes, yes.

Good to know.

> 
> Are you testing this with a card yet? I guess so, otherwise you
> wouldn't be getting card interrupts.
> 
> Until the card driver succeeds in (also) requesting the interrupt
> you'll for sure continue to see the oops.
> 

Oki, so the problem might not be with aquiring the IRQ but rather that no driver is loaded. Sounds like
a more logical answer since I cannot understand why the IRQ would be hard to get since it uses IRQF_SHARED and works
well for the slot.

> 
> > > Also in this case you would already have identified the source as
> > > being the socket rather than a card - which is actually a stronger
> > > indication that your driver needs to take action.
> > 
> > All slot interrupts (card status changed.../../) is detected
> > properly and forwarded through the pcmcia subsystem. However when a
> > pccard interrupt is generated I get a kernel oops "nobody cared".
> 
> Ok, so then your controller driver is pretty much done and you're
> looking at writing a card driver, right?
>

Not sure I follow. The hd64461 pcmcia driver is complete except for the oops when a card is inserted (lol).
It hands out functions to the pcmcia subsystem and updates status whenever a slot interrupt is generated. It should
detect properly MEMORY vs IO cards.
Since I've been using static kernels I can't see any modules be loaded, otherwise this would be a good indication why the interrupt
for card is unhandled.
 
> 
> > >From what I can see the slot is working as it should but no
> > >handler for the pccard interrupt is available.
> 
> "pccard" can be ambiguous, let's try to reserve that term for the
> Linux subsystem so I don't get confused. :)
> 
> Indeed the slot seems to be working properly, and you have an issue
> with a card driver. But then we're not talking about the hd64461
> anymore, right?
> 

Well, I guess you are right. I've basicly assumed that the card drivers are all in functioning order. The old HD64461 PCMCIA driver
worked (with old portmapping and linuxSH tree) in 2.6.17. So unless the card drivers has seen any major changes I suspect the problem is inside the hd64461 pcmcia driver. I've checked with loads of other pcmcia drivers and cannot see anything they do different in
providing the pcmcia subsystem with information (io_offset/mem_base/IRQ attributes/mem_size....).

> 
> > > > This driver is based on an old one which used port-translations,
> > > > you know the crappy PORT 0xblablabla = (real adress) 0xblobloblo,
> > > 
> > > Hm - no?
> > 
> > This existed in LinuxSH tree until 2.6.19, in essence everything
> > was considerd a port number. The number was then translated into
> > the real adress whenever access was needed. No idea why it was
> > implemented.
> > 
> > >From what I gatherd from Paul (linuxsh maintainer) it was to fix
> > >some problems on other archs rather than SuperH specific.
> > 
> > Anyhow this has been removed so now we are working with real
> > adresses. It created alot of issues for me though since I needed to
> > remake the driver, theres always a chance that I translated a port
> > adress wrong.
> 
> I have never ran or even seen a SuperH platform I think, and have not
> been hacking around all that much in pcmcia land either, so I don't
> think I've come across this. Anyway, I understand the problem, may be
> a good idea to double check those translations.
> 

Yes will do that, however I've checked them several times before without luck.

> 
> > > I guess you'll have to add gobs of printk() to find out what is
> > > actually happening.
> > 
> > I was thinking to add printk's to the orinoco driver since I have
> > such a card here. It has worked in the past and could perhaps give
> > me some info during the probing. If it fails to probe the device
> > properly I must have some configuration/memadress wrong.
> 
> Sounds like a good plan!

Thanks!
btw, attached the driver below just in case you want some basic layout info.

> 
> 
> //Peter
> 
> _______________________________________________
> Linux PCMCIA reimplementation list
> http://lists.infradead.org/mailman/listinfo/linux-pcmcia

/*
 * drivers/pcmcia/hd64461_ss.c
 *
 * PCMCIA platform driver for Hitachi HD64461 companion chip
 * Copyright (C) 2006-2007 Kristoffer Ericson <kristoffer.ericson at gmail.com>
 *
 * This driver is based on hd64461_ss.c that was maintained in LinuxSH cvs before merger with mainline
 * by
 * COPYRIGHT (C) 2002-2005 Andriy Skulysh <askulysh at image.kiev.ua>
 * COPYRIGHT (C) ?  Greg Banks <gbanks at pocketpenguins.com>
 * COPYRIGHT (C) 2000 YAEGASHI Takeshi
 *
 * Please note that although the hd64461 chipset supports two sockets (0 & 1) this driver only
 * supports the PCMCIA one. The CF slot cannot handle anything other than memory cards, so its
 * better to leave that to other drivers such as pata.
 *
 */
#include <linux/autoconf.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/ioport.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>

#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/ss.h>
#include <pcmcia/bulkmem.h>
#include <pcmcia/cistpl.h>

#include "cs_internal.h"

#include <asm/io.h>
#include <asm/hd64461.h>
#include <asm/hp6xx.h>

#define MODNAME "HD64461_ss"

/*
 *
 * Our socket implementation
 */
typedef struct hd64461_socket_t {
	u8			cscier;
	unsigned int		irq;
	unsigned long		mem_base;
	socket_state_t		state;
	pccard_mem_map		mem_maps[MAX_WIN];
	unsigned char		IC_memory;
	struct pcmcia_socket	socket;
} hd64461_socket_t;

/* socket declaration */
static hd64461_socket_t hd64461_sockets[CONFIG_HD64461_PCMCIA_SOCKETS];

/*
 * hd64461_set_voltage(int Vcc, int Vpp)
 *
 * returns : %1 on success (no error checking!)
 */
static int hd64461_set_voltage(int Vcc, int Vpp)
{
	u8 gcr, scr;
	u16 stbcr;
	u32 gcr_reg = HD64461_PCC0GCR;
	u32 scr_reg = HD64461_PCC0SCR;
	
	gcr = inb(gcr_reg);
	scr = inb(scr_reg);

	/* Handling voltage control pins */
	switch (Vcc) {
	case 0:
		gcr |= HD64461_PCCGCR_VCC0;
		scr |= HD64461_PCCSCR_VCC1;
		break;
	case 33:
		gcr |= HD64461_PCCGCR_VCC0; 
		scr &= ~HD64461_PCCSCR_VCC1;
		break;
	case 50:
		gcr &= ~HD64461_PCCGCR_VCC0;
		scr &= ~HD64461_PCCSCR_VCC1;
		break;
	}

	outb(gcr, gcr_reg);
	outb(scr, scr_reg);

	stbcr = inw(HD64461_STBCR);

	if (Vcc > 0) {
		stbcr &= ~HD64461_STBCR_SPC0ST;
	} else {
		stbcr |= HD64461_STBCR_SPC0ST;
	}

	outw(stbcr, HD64461_STBCR);

	return 1;
}

static int hd64461_init(struct pcmcia_socket *s)
{
	u16 gpadr;
	hd64461_socket_t *sp = container_of(s, struct hd64461_socket_t, socket);

	printk(KERN_INFO "hd64461_ss: entering hd64461_init from pcmcia_register_socket\n");

	sp->state.Vcc = 0;
	sp->state.Vpp = 0;
	hd64461_set_voltage(0, 0);

	gpadr = inw(HD64461_GPADR);
	gpadr &= ~HD64461_GPADR_PCMCIA0;
	outw(gpadr, HD64461_GPADR);

	return 0;
}

static int hd64461_suspend(struct pcmcia_socket *s)
{
	u16 gpadr;
	u8 gcr;
	u32 gcr_reg = HD64461_PCC0GCR;

	gcr = inb(gcr_reg);
	gcr &= ~HD64461_PCCGCR_DRVE;
	outb(gcr, gcr_reg);
	hd64461_set_voltage(0, 0);

	gpadr = inw(HD64461_GPADR);
	gpadr |= HD64461_GPADR_PCMCIA0;
	outw(gpadr, HD64461_GPADR);
	
	return 0;
}

/*
 * hd64461_get_status(struct pcmcia_socket *s, u32 * value)
 *
 * 
 */
static int hd64461_get_status(struct pcmcia_socket *s, u32 *value)
{
	u8 isr;
	u32 status = 0;
	hd64461_socket_t *sp = container_of(s, struct hd64461_socket_t, socket);

	/* get status of pcmcia socket */
	isr = inb(HD64461_PCC0ISR);
	printk(KERN_INFO "hd64461_get_status: reading HD64461_PCC0ISR status %d\n", isr);

	/* is card inserted and powerd? */
	if (isr & HD64461_PCCISR_PCD_MASK) {
		status |= SS_DETECT;

		/* If its an memory card, lets find out the voltage */
		if (sp->IC_memory) {
			switch (isr & HD64461_PCCISR_BVD_MASK) {
			case HD64461_PCCISR_BVD_BATGOOD:
				break;
			case HD64461_PCCISR_BVD_BATWARN:
				status |= SS_BATWARN;
				break;
			default:
				status |= SS_BATDEAD;
				break;
			}

			if (isr & HD64461_PCCISR_READY)
				status |= SS_READY;
			if (isr & HD64461_PCCISR_MWP)
				status |= SS_WRPROT;
		} else {
			if (isr & HD64461_PCCISR_BVD1)
			printk(KERN_INFO "get_status: status has changed! \n");
		    	status |= SS_STSCHG;
		}

		switch (isr & (HD64461_PCCISR_VS2 | HD64461_PCCISR_VS1)) {
		case HD64461_PCCISR_VS1:
			printk(KERN_INFO MODNAME
			       ": cannot handle X.XV card, ignored\n");
			status = 0;
			break;
		case 0:
		case HD64461_PCCISR_VS2:
			status |= SS_3VCARD;
			break;
		case HD64461_PCCISR_VS2 | HD64461_PCCISR_VS1:
			break;
		}

		if ((sp->state.Vcc != 0) || (sp->state.Vpp != 0))
			status |= SS_POWERON;
	}

	printk(KERN_INFO "get_status: returning status = %d\n", status);
	*value = status;
	return 0;
}

static int hd64461_set_socket(struct pcmcia_socket *s, socket_state_t * state)
{
	u32 flags;
	u32 changed;
	u8 gcr, cscier;
	hd64461_socket_t *sp = container_of(s, struct hd64461_socket_t, socket);
	u32 gcr_reg = HD64461_PCC0GCR;
	u32 cscier_reg = HD64461_PCC0CSCIER;

	printk(KERN_INFO "hd64461: entering hd64461_set_socket\n");

	/* no interruptions please */
	local_irq_save(flags);

	/* compair old power status with new */
	if (state->Vpp != sp->state.Vpp || state->Vcc != sp->state.Vcc) {
		if (!hd64461_set_voltage(state->Vcc, state->Vpp)) {
    			local_irq_restore(flags);
			printk(KERN_INFO "hd64461_set_socket: We've produced an EINVAL\n");
			return -EINVAL;
		}
	}

	/* lets only push the changes */
	changed = sp->state.csc_mask ^ state->csc_mask;
	cscier = inb(cscier_reg);

	/* set it so interrupt occurs when values of CD1 and CD2 are changed */
	if (changed & SS_DETECT) {
		if (state->csc_mask & SS_DETECT)
			cscier |= HD64461_PCCCSCIER_CDE;
		else
			cscier &= ~HD64461_PCCCSCIER_CDE;
	}

	/* set so interrupt occurs when pin changes from low -> high */
	if (changed & SS_READY) {
		if (state->csc_mask & SS_READY)
			cscier |= HD64461_PCCCSCIER_RE;
		else
			cscier &= ~HD64461_PCCCSCIER_RE;
	}

	/* set so interrupt occurs when BVD1 & BVD2 are set to bat_dead */
	if (changed & SS_BATDEAD) {
		if (state->csc_mask & SS_BATDEAD)
			cscier |= HD64461_PCCCSCIER_BDE;
		else
			cscier &= ~HD64461_PCCCSCIER_BDE;
	}

	/* set so interrupt occurs when BVD1 & BVD2 are set to bat_warn */
	if (changed & SS_BATWARN) {
		if (state->csc_mask & SS_BATWARN)
			cscier |= HD64461_PCCCSCIER_BWE;
		else
			cscier &= ~HD64461_PCCCSCIER_BWE;
	}

	/* set so "pccard connection" interrupt initializes PCC0SCR and PCC0GCR */
	if (changed & SS_STSCHG) {
		if (state->csc_mask & SS_STSCHG)
			cscier |= HD64461_PCCCSCIER_SCE;
		else
			cscier &= ~HD64461_PCCCSCIER_SCE;
	}

	outb(cscier, cscier_reg);

	changed = sp->state.flags ^ state->flags;

	gcr = inb(gcr_reg);

	if (changed & SS_IOCARD) {
		if (state->flags & SS_IOCARD) {
			if (s->sock == 1) {
				printk(KERN_INFO
				       "socket 1 can be only IC Memory card\n");
			} else {
				/* Reset the card and set as IO card */
				printk(KERN_INFO "hd64461_set_socket: Lets reset and set to IO card \n");
				gcr |= HD64461_PCCGCR_PCCT;
				sp->IC_memory = 0;
			}
		} else {
			/* Reset and set as memory card */
			gcr &= ~HD64461_PCCGCR_PCCT;
			sp->IC_memory = 1;
		}
	}

	/* if bit 3 = 0 while pccard accessed, output 1 on pccreg */
	if (changed & SS_RESET) {
    	    if (state->flags & SS_RESET)
			gcr |= HD64461_PCCGCR_PCCR;
		else
			gcr &= ~HD64461_PCCGCR_PCCR;
	}

	/* Set low level of external buffer */
	if (changed & SS_OUTPUT_ENA) {
		if (state->flags & SS_OUTPUT_ENA)
			gcr |= HD64461_PCCGCR_DRVE;
		else
			gcr &= ~HD64461_PCCGCR_DRVE;
	}

	outb(gcr, gcr_reg);

	sp->state = *state;

	/* enable interruptions */
	local_irq_restore(flags);

	printk(KERN_INFO "hd64461_set_socket: We've passed the routine successfully\n");

	return 0;
}

static int hd64461_set_io_map(struct pcmcia_socket *s, struct pccard_io_map *io)
{
	/* this is not needed due to static mappings */
	return 0;
}

static int hd64461_set_mem_map(struct pcmcia_socket *s,
			       struct pccard_mem_map *mem)
{
	hd64461_socket_t *sp = container_of(s, struct hd64461_socket_t, socket);
	struct pccard_mem_map *smem;
	int map = mem->map;
	unsigned long saddr;

	printk(KERN_INFO "hd64461: entering hd64461_set_mem_map\n");
	
	if (map >= MAX_WIN)
		return -EINVAL;

	/* lets setup pointers to memory */
	smem = &sp->mem_maps[map];
	saddr = sp->mem_base + mem->card_start;

	if (!(mem->flags & MAP_ATTRIB))
		saddr += HD64461_PCC_WINDOW;

	mem->static_start = saddr;
	*smem = *mem;

	return 0;
}

/*
 *
 * functions needed for operations
 */
static struct pccard_operations hd64461_operations = {
	.init = hd64461_init,
	.suspend = hd64461_suspend,
	.get_status = hd64461_get_status,
	.set_socket = hd64461_set_socket,
	.set_io_map = hd64461_set_io_map,
	.set_mem_map = hd64461_set_mem_map,
};

/* hd64461_interrupt()
 *
 * Here we check what caused the interrupt and
 * pass it on to pcmcia parser
 *
 */
static irqreturn_t hd64461_interrupt(int irq, void *dev)
{
	hd64461_socket_t *sp = (hd64461_socket_t *) dev;
	unsigned events = 0;
	unsigned char cscr,cscr2;
	
	//printk(KERN_INFO "HD64461_SS: Interrupt 78\n");
	cscr = inb(HD64461_PCC0CSCR);

	/* If IREQ pin is in low state */
	if (cscr & HD64461_PCCCSCR_IREQ) {
	    if (!(cscr & HD64461_PCCCSCR_SC)) {
		cscr &= ~HD64461_PCCCSCR_IREQ;
		/* silence interrupt source and hand over interrupt */
		outb(cscr, HD64461_PCC0CSCR);
		cscr2 = inb(HD64461_PCC0CSCR);
		if (!(cscr2 & cscr))
		    printk(KERN_INFO "hd64461_interrupt: Pah! we are in bloody level mode!\n");
    		return IRQ_NONE;
	    }
	}

	/* if both CD1 and CD2 has changed */
	if (cscr & HD64461_PCCCSCR_CDC) {
		printk(KERN_INFO "HD64461_SS: We are inside the CD1/CD2 changed loop\n");
		/* silence it by writing a 0 to bit 3 */
		cscr &= ~HD64461_PCCCSCR_CDC;
		/* we've detected something being inserted or unplugged */
		events |= SS_DETECT;

		/* if CD1 and CD2 don't both display 1, we need to clean up */
		if (((inb(HD64461_PCC0ISR)) & HD64461_PCCISR_PCD_MASK) != 0) {
			cscr &= ~(HD64461_PCCCSCR_RC | HD64461_PCCCSCR_BW |
				  HD64461_PCCCSCR_BD | HD64461_PCCCSCR_SC);
		}
	}

	/* MEMORY CARD */
	if (sp->IC_memory) {
		if (cscr & HD64461_PCCCSCR_RC) {
			/* ? */
			cscr &= ~HD64461_PCCCSCR_RC;
			events |= SS_READY;
		}

		if (cscr & HD64461_PCCCSCR_BW) {
			/* battery warning */
			cscr &= ~HD64461_PCCCSCR_BW;
			events |= SS_BATWARN;
		}

		if (cscr & HD64461_PCCCSCR_BD) {
			/* battery dead */
			cscr &= ~HD64461_PCCCSCR_BD;
			events |= SS_BATDEAD;
		}
	} else { /* IO CARD */
		if (cscr & HD64461_PCCCSCR_SC) {
			/* status changed */
			cscr &= ~HD64461_PCCCSCR_SC;
			events |= SS_STSCHG;
		}
	}

	outb(cscr, HD64461_PCC0CSCR);

	/* make sure we push these changes into pcmcia events */
	if (events) {
		pcmcia_parse_events(&sp->socket, events);
	        printk(KERN_INFO "hd64461_interrupt: we are pushing changed events! events=%d\n", events);
	}


	return IRQ_HANDLED;
}

/*
 *hd_64461_init_socket()
 *
 * Add values to socket and link it with pcmcia operations
 */
int hd64461_init_socket(int sock, int irq, unsigned long mem_base,unsigned long io_offset)
{
	hd64461_socket_t *sp = &hd64461_sockets[sock];
	unsigned gcr_reg = HD64461_PCC0GCR;
	u8 gcr;
	int i, err;

	outb(0, HD64461_PCC0CSCIER);

	memset(sp, 0, sizeof(*sp));
	sp->IC_memory = 0;
	sp->irq = irq;
	//sp->socket.irq_mask = 0;
	sp->mem_base = mem_base;
	sp->socket.features = SS_CAP_PCCARD | SS_CAP_STATIC_MAP | SS_CAP_PAGE_REGS;
	sp->socket.resource_ops = &pccard_static_ops;
	sp->socket.ops = &hd64461_operations;
	sp->socket.map_size = HD64461_PCC_WINDOW;	/* 16MB fixed window size */
	sp->socket.pci_irq = irq;
	sp->socket.io_offset = io_offset;
	sp->socket.owner = THIS_MODULE;

	for (i = 0; i != MAX_WIN; i++)
		sp->mem_maps[i].map = i;

	if ((request_irq(irq, hd64461_interrupt, IRQF_SHARED, "hd64461_ss-irq", sp)) < 0) {
		printk(KERN_INFO "hd64461_init: request for irq %d: failed\n", sp->irq);
		return -1;
	}
	
	gcr = inb(gcr_reg);
	/* continuous 16MB area mode for both memory and I/O operations */
	gcr |= HD64461_PCCGCR_PMMOD;
	/* ??? */
	gcr &= ~(HD64461_PCCGCR_PA25 | HD64461_PCCGCR_PA24);
	outb(gcr, gcr_reg);

	return 0;
}

void hd64461_exit_socket(int sock)
{
	hd64461_socket_t *sp = &hd64461_sockets[0];
	unsigned cscier_reg = HD64461_PCC0CSCIER;

	printk(KERN_INFO "hd64461_exit_socket: cleaning up irq %d and %d\n", sp->irq, sp->socket.pci_irq);

	outb(0, cscier_reg);
	hd64461_suspend(&sp->socket);

}

static int __devexit hd64461_pcmcia_drv_remove(struct platform_device *dev)
{
	/* Libpata handles CF slot, so we only handle PCMCIA slot */
	pcmcia_unregister_socket(&hd64461_sockets[0].socket);
	hd64461_exit_socket(0);

	return 0;
}

#ifdef CONFIG_PM
static int hd64461_pcmcia_drv_suspend(struct platform_device *dev, pm_message_t state)
{
	int ret = 0;
	int i;

	u32 cscier_reg = HD64461_PCC0CSCIER;
	hd64461_sockets[0].cscier = inb(cscier_reg);
	outb(0, cscier_reg);
	ret = pcmcia_socket_dev_suspend(&dev->dev, state);

 	return ret;
 }

static int hd64461_pcmcia_drv_resume(struct platform_device *dev)
{
	int ret = 0;
	int i;

	u32 cscier_reg = HD64461_PCC0CSCIER;
	outb(hd64461_sockets[0].cscier, cscier_reg);
	ret = pcmcia_socket_dev_resume(&dev->dev);

	return ret;
}
#endif

static struct platform_driver hd64461_pcmcia_driver = {
	.remove = __devexit_p(hd64461_pcmcia_drv_remove),
#ifdef CONFIG_PM
	.suspend = hd64461_pcmcia_drv_suspend,
	.resume = hd64461_pcmcia_drv_resume,
#endif
	.driver		= {
		.name	= "hd64461-pcmcia",
	},
};

static struct platform_device *hd64461_pcmcia_device;

static int __init init_hd64461_ss(void)
{
	int i;

	printk(KERN_INFO "hd64461_ss_init: pcmcia driver for hd64461 chipset started\n");

        if (platform_driver_register(&hd64461_pcmcia_driver))
	    goto failed1;

	i = hd64461_init_socket(0, HD64461_IRQ_PCC0, HD64461_PCC0_BASE, HD64461_PCC0_IO);
        if (i < 0) 
	    goto failed2;

        
	hd64461_pcmcia_device = platform_device_alloc("hd64461-pcmcia",-1);
	if(!hd64461_pcmcia_device) {
	    printk(KERN_INFO "hd64461_ss_init: Cannot find pcmcia device!\n");
	    return -ENODEV;
	}

	i = platform_device_add(hd64461_pcmcia_device);
	if (i) {
	    platform_device_put(hd64461_pcmcia_device);
	    printk(KERN_INFO "hd64461_ss_init: Cannot add pcmcia device, return value %d\n",i);
	    return -ENODEV;
	}

	/* Lets register the pcmcia socket */
	printk(KERN_INFO "hd64461_ss_init: setting socket device\n");
	hd64461_sockets[0].socket.dev.parent = &hd64461_pcmcia_device->dev;
	
	i = pcmcia_register_socket(&hd64461_sockets[0].socket);
	printk(KERN_INFO "hd64461_ss_init: return value %d from pcmcia_register_socket\n", i);
	
	return 0;

/* Unregister driver nothing else */
failed1:
	printk(KERN_INFO "hd64461_ss_init: Failed registering pcmcia platform driver\n");
	platform_driver_unregister(&hd64461_pcmcia_driver);
	return -EINVAL;

/* Unregister driver nothing else */
failed2:
	printk(KERN_INFO "hd64461_ss_init: Failed to startup socket 0\n");
	platform_driver_unregister(&hd64461_pcmcia_driver);
	return i;
}

static void __exit exit_hd64461_ss(void)
{
	/* Only remove if there's something to remove */
	if (hd64461_pcmcia_device) {
	    platform_device_unregister(hd64461_pcmcia_device);
	    platform_driver_unregister(&hd64461_pcmcia_driver);
	}
}
module_init(init_hd64461_ss);
module_exit(exit_hd64461_ss);

MODULE_AUTHOR("Kristoffer Ericson <kristoffer.ericson at gmail.com>");
MODULE_DESCRIPTION("PCMCIA driver for Hitachi HD64461 companion chip");
MODULE_LICENSE("GPL");



-- 
Kristoffer Ericson <Kristoffer.Ericson at Gmail.com>



More information about the linux-pcmcia mailing list