summaryrefslogtreecommitdiffstats
path: root/kernel/drivers/parport/parport_ax88796.c
blob: 8f8c9f3aa6910fe80ea92a9acdc905571518e540 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#ifndef __LINUX_SPINLOCK_UP_H
#define __LINUX_SPINLOCK_UP_H

#ifndef __LINUX_SPINLOCK_H
# error "please don't include this file directly"
#endif

#include <asm/processor.h>	/* for cpu_relax() */

/*
 * include/linux/spinlock_up.h - UP-debug version of spinlocks.
 *
 * portions Copyright 2005, Red Hat, Inc., Ingo Molnar
 * Released under the General Public License (GPL).
 *
 * In the debug case, 1 means unlocked, 0 means locked. (the values
 * are inverted, to catch initialization bugs)
 *
 * No atomicity anywhere, we are on UP. However, we still need
 * the compiler barriers, because we do not want the compiler to
 * move potentially faulting instructions (notably user accesses)
 * into the locked sequence, resulting in non-atomic execution.
 */

#ifdef CONFIG_DEBUG_SPINLOCK
#define arch_spin_is_locked(x)		((x)->slock == 0)

static inline void arch_spin_lock(arch_spinlock_t *lock)
{
	lock->slock = 0;
	barrier();
}

static inline void
arch_spin_lock_flags(arch_spinlock_t *lock, unsigned long flags)
{
	local_irq_save(flags);
	lock->slock = 0;
	barrier();
}

static inline int arch_spin_trylock(arch_spinlock_t *lock)
{
	char oldval = lock->slock;

	lock->slock = 0;
	barrier();

	return oldval > 0;
}

static inline void arch_spin_unlock(arch_spinlock_t *lock)
{
	barrier();
	lock->slock = 1;
}

/*
 * Read-write spinlocks. No debug version.
 */
#define arch_read_lock(lock)		do { barrier(); (void)(lock); } while (0)
#define arch_write_lock(lock)		do { barrier(); (void)(lock); } while (0)
#define arch_read_trylock(lock)	({ barrier(); (void)(lock); 1; })
#define arch_write_trylock(lock)	({ barrier(); (void)(lock); 1; })
#define arch_read_unlock(lock)		do { barrier(); (void)(lock); } while (0)
#define arch_write_unlock(lock)	do { barrier(); (void)(lock); } while (0)

#else /* DEBUG_SPINLOCK */
#define arch_spin_is_locked(lock)	((void)(lock), 0)
/* for sched/core.c and kernel_lock.c: */
# define arch_spin_lock(lock)		do { barrier(); (void)(lock); } while (0)
# define arch_spin_lock_flags(lock, flags)	do { barrier(); (void)(lock); } while (0)
# define arch_spin_unlock(lock)	do { barrier(); (void)(lock); } while (0)
# define arch_spin_trylock(lock)	({ barrier(); (void)(lock); 1; })
#endif /* DEBUG_SPINLOCK */

#define arch_spin_is_contended(lock)	(((void)(lock), 0))

#define arch_read_can_lock(lock)	(((void)(lock), 1))
#define arch_write_can_lock(lock)	(((void)(lock), 1))

#define arch_spin_unlock_wait(lock) \
		do { cpu_relax(); } while (arch_spin_is_locked(lock))

#endif /* __LINUX_SPINLOCK_UP_H */
round-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/* linux/drivers/parport/parport_ax88796.c
 *
 * (c) 2005,2006 Simtec Electronics
 *	Ben Dooks <ben@simtec.co.uk>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
*/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/parport.h>
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

#include <asm/io.h>
#include <asm/irq.h>

#define AX_SPR_BUSY		(1<<7)
#define AX_SPR_ACK		(1<<6)
#define AX_SPR_PE		(1<<5)
#define AX_SPR_SLCT		(1<<4)
#define AX_SPR_ERR		(1<<3)

#define AX_CPR_nDOE		(1<<5)
#define AX_CPR_SLCTIN		(1<<3)
#define AX_CPR_nINIT		(1<<2)
#define AX_CPR_ATFD		(1<<1)
#define AX_CPR_STRB		(1<<0)

struct ax_drvdata {
	struct parport		*parport;
	struct parport_state	 suspend;

	struct device		*dev;
	struct resource		*io;

	unsigned char		 irq_enabled;

	void __iomem		*base;
	void __iomem		*spp_data;
	void __iomem		*spp_spr;
	void __iomem		*spp_cpr;
};

static inline struct ax_drvdata *pp_to_drv(struct parport *p)
{
	return p->private_data;
}

static unsigned char
parport_ax88796_read_data(struct parport *p)
{
	struct ax_drvdata *dd = pp_to_drv(p);

	return readb(dd->spp_data);
}

static void
parport_ax88796_write_data(struct parport *p, unsigned char data)
{
	struct ax_drvdata *dd = pp_to_drv(p);

	writeb(data, dd->spp_data);
}

static unsigned char
parport_ax88796_read_control(struct parport *p)
{
	struct ax_drvdata *dd = pp_to_drv(p);
	unsigned int cpr = readb(dd->spp_cpr);
	unsigned int ret = 0;

	if (!(cpr & AX_CPR_STRB))
		ret |= PARPORT_CONTROL_STROBE;

	if (!(cpr & AX_CPR_ATFD))
		ret |= PARPORT_CONTROL_AUTOFD;

	if (cpr & AX_CPR_nINIT)
		ret |= PARPORT_CONTROL_INIT;

	if (!(cpr & AX_CPR_SLCTIN))
		ret |= PARPORT_CONTROL_SELECT;

	return ret;
}

static void
parport_ax88796_write_control(struct parport *p, unsigned char control)
{
	struct ax_drvdata *dd = pp_to_drv(p);
	unsigned int cpr = readb(dd->spp_cpr);

	cpr &= AX_CPR_nDOE;

	if (!(control & PARPORT_CONTROL_STROBE))
		cpr |= AX_CPR_STRB;

	if (!(control & PARPORT_CONTROL_AUTOFD))
		cpr |= AX_CPR_ATFD;

	if (control & PARPORT_CONTROL_INIT)
		cpr |= AX_CPR_nINIT;

	if (!(control & PARPORT_CONTROL_SELECT))
		cpr |= AX_CPR_SLCTIN;

	dev_dbg(dd->dev, "write_control: ctrl=%02x, cpr=%02x\n", control, cpr);
	writeb(cpr, dd->spp_cpr);

	if (parport_ax88796_read_control(p) != control) {
		dev_err(dd->dev, "write_control: read != set (%02x, %02x)\n",
			parport_ax88796_read_control(p), control);
	}
}

static unsigned char
parport_ax88796_read_status(struct parport *p)
{
	struct ax_drvdata *dd = pp_to_drv(p);
	unsigned int status = readb(dd->spp_spr);
	unsigned int ret = 0;

	if (status & AX_SPR_BUSY)
		ret |= PARPORT_STATUS_BUSY;

	if (status & AX_SPR_ACK)
		ret |= PARPORT_STATUS_ACK;

	if (status & AX_SPR_ERR)
		ret |= PARPORT_STATUS_ERROR;

	if (status & AX_SPR_SLCT)
		ret |= PARPORT_STATUS_SELECT;

	if (status & AX_SPR_PE)
		ret |= PARPORT_STATUS_PAPEROUT;

	return ret;
}

static unsigned char
parport_ax88796_frob_control(struct parport *p, unsigned char mask,
			     unsigned char val)
{
	struct ax_drvdata *dd = pp_to_drv(p);
	unsigned char old = parport_ax88796_read_control(p);

	dev_dbg(dd->dev, "frob: mask=%02x, val=%02x, old=%02x\n",
		mask, val, old);

	parport_ax88796_write_control(p, (old & ~mask) | val);
	return old;
}

static void
parport_ax88796_enable_irq(struct parport *p)
{
	struct ax_drvdata *dd = pp_to_drv(p);
	unsigned long flags;

	local_irq_save(flags);
	if (!dd->irq_enabled) {
		enable_irq(p->irq);
		dd->irq_enabled = 1;
	}
	local_irq_restore(flags);
}

static void
parport_ax88796_disable_irq(struct parport *p)
{
	struct ax_drvdata *dd = pp_to_drv(p);
	unsigned long flags;

	local_irq_save(flags);
	if (dd->irq_enabled) {
		disable_irq(p->irq);
		dd->irq_enabled = 0;
	}
	local_irq_restore(flags);
}

static void
parport_ax88796_data_forward(struct parport *p)
{
	struct ax_drvdata *dd = pp_to_drv(p);
	void __iomem *cpr = dd->spp_cpr;

	writeb((readb(cpr) & ~AX_CPR_nDOE), cpr);
}

static void
parport_ax88796_data_reverse(struct parport *p)
{
	struct ax_drvdata *dd = pp_to_drv(p);
	void __iomem *cpr = dd->spp_cpr;

	writeb(readb(cpr) | AX_CPR_nDOE, cpr);
}

static void
parport_ax88796_init_state(struct pardevice *d, struct parport_state *s)
{
	struct ax_drvdata *dd = pp_to_drv(d->port);

	memset(s, 0, sizeof(struct parport_state));

	dev_dbg(dd->dev, "init_state: %p: state=%p\n", d, s);
	s->u.ax88796.cpr = readb(dd->spp_cpr);
}

static void
parport_ax88796_save_state(struct parport *p, struct parport_state *s)
{
	struct ax_drvdata *dd = pp_to_drv(p);

	dev_dbg(dd->dev, "save_state: %p: state=%p\n", p, s);
	s->u.ax88796.cpr = readb(dd->spp_cpr);
}

static void
parport_ax88796_restore_state(struct parport *p, struct parport_state *s)
{
	struct ax_drvdata *dd = pp_to_drv(p);

	dev_dbg(dd->dev, "restore_state: %p: state=%p\n", p, s);
	writeb(s->u.ax88796.cpr, dd->spp_cpr);
}

static struct parport_operations parport_ax88796_ops = {
	.write_data	= parport_ax88796_write_data,
	.read_data	= parport_ax88796_read_data,

	.write_control	= parport_ax88796_write_control,
	.read_control	= parport_ax88796_read_control,
	.frob_control	= parport_ax88796_frob_control,

	.read_status	= parport_ax88796_read_status,

	.enable_irq	= parport_ax88796_enable_irq,
	.disable_irq	= parport_ax88796_disable_irq,

	.data_forward	= parport_ax88796_data_forward,
	.data_reverse	= parport_ax88796_data_reverse,

	.init_state	= parport_ax88796_init_state,
	.save_state	= parport_ax88796_save_state,
	.restore_state	= parport_ax88796_restore_state,

	.epp_write_data	= parport_ieee1284_epp_write_data,
	.epp_read_data	= parport_ieee1284_epp_read_data,
	.epp_write_addr	= parport_ieee1284_epp_write_addr,
	.epp_read_addr	= parport_ieee1284_epp_read_addr,

	.ecp_write_data	= parport_ieee1284_ecp_write_data,
	.ecp_read_data	= parport_ieee1284_ecp_read_data,
	.ecp_write_addr	= parport_ieee1284_ecp_write_addr,

	.compat_write_data	= parport_ieee1284_write_compat,
	.nibble_read_data	= parport_ieee1284_read_nibble,
	.byte_read_data		= parport_ieee1284_read_byte,

	.owner		= THIS_MODULE,
};

static int parport_ax88796_probe(struct platform_device *pdev)
{
	struct device *_dev = &pdev->dev;
	struct ax_drvdata *dd;
	struct parport *pp = NULL;
	struct resource *res;
	unsigned long size;
	int spacing;
	int irq;
	int ret;

	dd = kzalloc(sizeof(struct ax_drvdata), GFP_KERNEL);
	if (dd == NULL) {
		dev_err(_dev, "no memory for private data\n");
		return -ENOMEM;
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		dev_err(_dev, "no MEM specified\n");
		ret = -ENXIO;
		goto exit_mem;
	}

	size = resource_size(res);
	spacing = size / 3;

	dd->io = request_mem_region(res->start, size, pdev->name);
	if (dd->io == NULL) {
		dev_err(_dev, "cannot reserve memory\n");
		ret = -ENXIO;
		goto exit_mem;
	}

	dd->base = ioremap(res->start, size);
	if (dd->base == NULL) {
		dev_err(_dev, "cannot ioremap region\n");
		ret = -ENXIO;
		goto exit_res;
	}

	irq = platform_get_irq(pdev, 0);
	if (irq <= 0)
		irq = PARPORT_IRQ_NONE;

	pp = parport_register_port((unsigned long)dd->base, irq,
				   PARPORT_DMA_NONE,
				   &parport_ax88796_ops);

	if (pp == NULL) {
		dev_err(_dev, "failed to register parallel port\n");
		ret = -ENOMEM;
		goto exit_unmap;
	}

	pp->private_data = dd;
	dd->parport = pp;
	dd->dev = _dev;

	dd->spp_data = dd->base;
	dd->spp_spr  = dd->base + (spacing * 1);
	dd->spp_cpr  = dd->base + (spacing * 2);

	/* initialise the port controls */
	writeb(AX_CPR_STRB, dd->spp_cpr);

	if (irq >= 0) {
		/* request irq */
		ret = request_irq(irq, parport_irq_handler,
				  IRQF_TRIGGER_FALLING, pdev->name, pp);

		if (ret < 0)
			goto exit_port;

		dd->irq_enabled = 1;
	}

	platform_set_drvdata(pdev, pp);

	dev_info(_dev, "attached parallel port driver\n");
	parport_announce_port(pp);

	return 0;

 exit_port:
	parport_remove_port(pp);
 exit_unmap:
	iounmap(dd->base);
 exit_res:
	release_resource(dd->io);
	kfree(dd->io);
 exit_mem:
	kfree(dd);
	return ret;
}

static int parport_ax88796_remove(struct platform_device *pdev)
{
	struct parport *p = platform_get_drvdata(pdev);
	struct ax_drvdata *dd = pp_to_drv(p);

	free_irq(p->irq, p);
	parport_remove_port(p);
	iounmap(dd->base);
	release_resource(dd->io);
	kfree(dd->io);
	kfree(dd);

	return 0;
}

#ifdef CONFIG_PM

static int parport_ax88796_suspend(struct platform_device *dev,
				   pm_message_t state)
{
	struct parport *p = platform_get_drvdata(dev);
	struct ax_drvdata *dd = pp_to_drv(p);

	parport_ax88796_save_state(p, &dd->suspend);
	writeb(AX_CPR_nDOE | AX_CPR_STRB, dd->spp_cpr);
	return 0;
}

static int parport_ax88796_resume(struct platform_device *dev)
{
	struct parport *p = platform_get_drvdata(dev);
	struct ax_drvdata *dd = pp_to_drv(p);

	parport_ax88796_restore_state(p, &dd->suspend);
	return 0;
}

#else
#define parport_ax88796_suspend NULL
#define parport_ax88796_resume  NULL
#endif

MODULE_ALIAS("platform:ax88796-pp");

static struct platform_driver axdrv = {
	.driver		= {
		.name	= "ax88796-pp",
	},
	.probe		= parport_ax88796_probe,
	.remove		= parport_ax88796_remove,
	.suspend	= parport_ax88796_suspend,
	.resume		= parport_ax88796_resume,
};

module_platform_driver(axdrv);

MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
MODULE_DESCRIPTION("AX88796 Parport parallel port driver");
MODULE_LICENSE("GPL");