HP Jornada 6xx PCMCIA driver

Kristoffer Ericson kristoffer.ericson at gmail.com
Thu Nov 15 18:36:52 EST 2007


Greetings,
Glad to see list back again :)

Im in the process of both bugtracking and rewriting the hp6xx pcmcia driver. It stopped working approx 2.6.17/2.6.18 but was in bad shape
before then so it sure needs a rewrite. Need to fix the bug first though.

Anyhow. When inserting a card I get unexpected IRQ at vector blabla. Im not surprised by this but more in how its suppose to be done.
Please note that my issues are mainly related to IRQ, so thats why Im not adding major code examples.

HP Jornada 6xx has an hd64461 chipset (arch/sh/cchips/hd6446x/hd64461.c) where we have IRQ 36 that symbolizes 16 devices.
We use an demuxer to mask out where the interrupt is actually coming from and then passes it to the correct interrupt handler.

Now PCMCIA has IRQ 78 (demuxed from IRQ 36) usually. Since we want to divide what comes from the slot and pcmcia_card we create a software demuxer. IRQ 78 -> demuxer (if PCMCIA then IRQ=79 otherwise cardstatus IRQ=78).

reference :
------------
static int hd64461_pcmcia_irq_demux(int irq, void *dev)
{
	hd64461_socket_t *sp = (hd64461_socket_t *) dev;
	unsigned char cscr;
	unsigned cscr_reg = HD64461_PCC0CSCR;

	/* irq should be 78 here */
	printk(KERN_INFO "hd64461_pcmcia_irq_demux(irq= %d)\n", irq);

	/* If interrupt was due to pcmcia interrupt, then change it to 79 */
	cscr = inb(cscr_reg);
	if (cscr & HD64461_PCCCSCR_IREQ) {
		cscr &= ~HD64461_PCCCSCR_IREQ;
		outb(cscr, cscr_reg);
		irq = sp->socket.pci_irq;
	}

	printk(KERN_INFO "hd64461_pcmcia_irq_demux(transformed into %d)\n",irq);

	return irq;
}

It doesn't end there though. Since there is a different approach to mask_ack/disable/enable the pcmcia interrupt we set a different
chip type for the 79 IRQ. 

reference:
----------
set_irq_chip(io_irq, &hd64461_ss_irq_chip);

We need to turn off the pcmcia interrupt source and also turn off the hd64461 (36) IRQ. We expect it to go IRQ 79 (off) -> IRQ 78 (off) -> IRQ 36.

Since we've passed the irq_io parameter to the pcmcia driver we expect it to grab that IRQ and install
a proper handler. This doesn't happen, so we end up with a bad IRQ (since we don't have an handler).

I've tried to look at the other drivers to see how they handle stuff but it seems to differ alot and they have very different
solutions. So any help is greatly appreciated! I assume its needed to seperate if interrupt is caused by pcmcia card or pcmcia slot?

Best wishes
Kristoffer Ericson

/*
 * 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>
 * and   
 * COPYRIGHT (C) ?  Greg Banks <gbanks at pocketpenguins.com>
 * and
 * 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 support for that on libata.
 *
 */
#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];

/*
 * .CHIP functions for our handler
 ***************************************/
static void hd64461_enable_int(unsigned int irq)
{
	u8 cscier;
	u32 cscier_reg = HD64461_PCC0CSCIER;

	cscier = inb(cscier_reg);
	cscier &= ~HD64461_PCCCSCIER_IREQE_MASK;  /* 0x60 */
	cscier |= HD64461_PCCCSCIER_IREQE_LEVEL;  /* 0x20 */
	outb(cscier, cscier_reg);
}

static void hd64461_disable_int(unsigned int irq)
{
	u8 cscier;
	u32 cscier_reg = HD64461_PCC0CSCIER;

	cscier = inb(cscier_reg);

	cscier &= ~HD64461_PCCCSCIER_IREQE_MASK;  /* 0x60 */
	outb(cscier, cscier_reg);
}

static void hd64461_enable_irq(unsigned int irq)
{
	hd64461_enable_int(irq);
}

static void hd64461_disable_irq(unsigned int irq)
{
	hd64461_disable_int(irq);
}

static unsigned int hd64461_startup_irq(unsigned int irq)
{
	hd64461_enable_irq(irq);
	return 0;
}

static void hd64461_shutdown_irq(unsigned int irq)
{
	hd64461_disable_irq(irq);
}

static void hd64461_mask_and_ack_irq(unsigned int irq)
{
	hd64461_disable_irq(irq);
}

static void hd64461_end_irq(unsigned int irq)
{
	hd64461_enable_irq(irq);
}

static struct irq_chip hd64461_ss_irq_chip = {
	.name		= "hd64461_ss-irq",
	.startup	= hd64461_startup_irq,
	.shutdown	= hd64461_shutdown_irq,
	.enable		= hd64461_enable_irq,
	.disable	= hd64461_disable_irq,
	.ack		= hd64461_mask_and_ack_irq,
	.end		= hd64461_end_irq
};
/* End of .CHIP functions 
 *************************/

/*
 * 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);

	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);

	/* is card inserted and powerd? */
	if ((isr & HD64461_PCCISR_PCD_MASK) == 0) {
		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)
				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;
	}

	*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;

	/* 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);
			return -EINVAL;
		}
	}

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

	/* set is 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_ERR
				       "socket 1 can be only IC Memory card\n");
			} else {
				/* Reset the card and set as IO card */
				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);

	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;

	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;
}

static int hd64461_pcmcia_irq_demux(int irq, void *dev)
{
	hd64461_socket_t *sp = (hd64461_socket_t *) dev;
	unsigned char cscr;
	unsigned cscr_reg = HD64461_PCC0CSCR;

	/* irq should be 78 here */
	printk(KERN_INFO "hd64461_pcmcia_irq_demux(irq= %d)\n", irq);

	/* If interrupt was due to pcmcia interrupt, then change it to 81 */
	cscr = inb(cscr_reg);
	if (cscr & HD64461_PCCCSCR_IREQ) {
		cscr &= ~HD64461_PCCCSCR_IREQ;
		outb(cscr, cscr_reg);
		irq = sp->socket.pci_irq;
	}

	printk(KERN_INFO "hd64461_pcmcia_irq_demux(transformed into %d)\n",irq);

	return irq;
}

/*
 *
 * 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;
	unsigned cscr_reg = HD64461_PCC0CSCR;

	cscr = inb(cscr_reg);

	/* if both CD1 and CD2 has changed */
	if (cscr & HD64461_PCCCSCR_CDC) {
		/* 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, cscr_reg);

	/* make sure we push these changes into pcmcia events */
	if (events)
		pcmcia_parse_events(&sp->socket, 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, int io_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 = 1;
	sp->irq = irq;
	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.map_size = HD64461_PCC_WINDOW;	/* 16MB fixed window size */
	sp->socket.pci_irq = io_irq;
	sp->socket.io_offset = io_offset;
	sp->socket.owner = THIS_MODULE;
	sp->socket.ops = &hd64461_operations;

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

	if ((request_irq(irq, hd64461_interrupt, IRQF_DISABLED, "hd64461_ss-irq", sp)) < 0) {
		printk(KERN_INFO "hd64461_init: request for irq %d: failed\n", sp->irq);
		return -1;
	}
	
	/* setup io_irq chip data and move it through demux */
	set_irq_chip(io_irq, &hd64461_ss_irq_chip);
	hd64461_register_irq_demux(sp->irq, hd64461_pcmcia_irq_demux,sp);

	gcr = inb(gcr_reg);
	/* set so we get '1' on pin 24 when common memory is accessed */
	gcr |= HD64461_PCCGCR_PMMOD;
	/* set so we get continuous 16MB memory */
	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[sock];
	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);

	if (sp->irq) {
		if (sock == 0)
			hd64461_unregister_irq_demux(sp->irq);
		free_irq(sp->irq, sp);
		if (sock == 0)
			irq_desc[sp->socket.pci_irq].chip = &no_irq_type;
	}
}

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;

	for (i = 0; i != CONFIG_HD64461_PCMCIA_SOCKETS; i++) {
		u32 cscier_reg = HD64461_PCC0CSCIER;
		hd64461_sockets[i].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;

	/* HD64461_IRQ_PCC0 = 64 + 14 = 78         */
	/* HD64461_IRQ_PCC0 + 3 = 64 + 14 + 1 = 79 */
	i = hd64461_init_socket(0, HD64461_IRQ_PCC0, HD64461_IRQ_PCC0 + 1, HD64461_PCC0_BASE, 0xba000000);
        if (i < 0) 
	    goto failed2;

        printk(KERN_INFO "hd64461_ss_init: init_socket returned with %d\n",i);

	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;
	
	printk(KERN_INFO "hd64461_ss_init: registering socket hd64461_sockets[0].socket\n");
	i = pcmcia_register_socket(&hd64461_sockets[0].socket);
	if (i) {
	    printk(KERN_INFO "hd64461_ss_init: registration failed, return value %d\n", i);
	    return 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");



More information about the linux-pcmcia mailing list