linux-sg2042/drivers/parport/parport_mfc3.c

371 lines
9.9 KiB
C
Raw Normal View History

/* Low-level parallel port routines for the Multiface 3 card
*
* Author: Joerg Dorchain <joerg@dorchain.net>
*
* (C) The elitist m68k Users(TM)
*
* based on the existing parport_amiga and lp_mfc
*
*
* From the MFC3 documentation:
*
* Miscellaneous PIA Details
* -------------------------
*
* The two open-drain interrupt outputs /IRQA and /IRQB are routed to
* /INT2 of the Z2 bus.
*
* The CPU data bus of the PIA (D0-D7) is connected to D8-D15 on the Z2
* bus. This means that any PIA registers are accessed at even addresses.
*
* Centronics Pin Connections for the PIA
* --------------------------------------
*
* The following table shows the connections between the PIA and the
* Centronics interface connector. These connections implement a single, but
* very complete, Centronics type interface. The Pin column gives the pin
* numbers of the PIA. The Centronics pin numbers can be found in the section
* "Parallel Connectors".
*
*
* Pin | PIA | Dir | Centronics Names
* -------+-----+-----+---------------------------------------------------------
* 19 | CB2 | --> | /STROBE (aka /DRDY)
* 10-17 | PBx | <-> | DATA0 - DATA7
* 18 | CB1 | <-- | /ACK
* 40 | CA1 | <-- | BUSY
* 3 | PA1 | <-- | PAPER-OUT (aka POUT)
* 4 | PA2 | <-- | SELECTED (aka SEL)
* 9 | PA7 | --> | /INIT (aka /RESET or /INPUT-PRIME)
* 6 | PA4 | <-- | /ERROR (aka /FAULT)
* 7 | PA5 | --> | DIR (aka /SELECT-IN)
* 8 | PA6 | --> | /AUTO-FEED-XT
* 39 | CA2 | --> | open
* 5 | PA3 | <-- | /ACK (same as CB1!)
* 2 | PA0 | <-- | BUSY (same as CA1!)
* -------+-----+-----+---------------------------------------------------------
*
* Should be enough to understand some of the driver.
*
* Per convention for normal use the port registers are visible.
* If you need the data direction registers, restore the value in the
* control register.
*/
#include "multiface.h"
#include <linux/module.h>
#include <linux/init.h>
#include <linux/parport.h>
#include <linux/delay.h>
#include <linux/mc6821.h>
#include <linux/zorro.h>
#include <linux/interrupt.h>
#include <asm/setup.h>
#include <asm/amigahw.h>
#include <asm/irq.h>
#include <asm/amigaints.h>
/* Maximum Number of Cards supported */
#define MAX_MFC 5
#undef DEBUG
#ifdef DEBUG
#define DPRINTK printk
#else
static inline int DPRINTK(void *nothing, ...) {return 0;}
#endif
static struct parport *this_port[MAX_MFC] = {NULL, };
static volatile int dummy; /* for trigger readds */
#define pia(dev) ((struct pia *)(dev->base))
static struct parport_operations pp_mfc3_ops;
static void mfc3_write_data(struct parport *p, unsigned char data)
{
DPRINTK(KERN_DEBUG "write_data %c\n",data);
dummy = pia(p)->pprb; /* clears irq bit */
/* Triggers also /STROBE.*/
pia(p)->pprb = data;
}
static unsigned char mfc3_read_data(struct parport *p)
{
/* clears interrupt bit. Triggers also /STROBE. */
return pia(p)->pprb;
}
static unsigned char control_pc_to_mfc3(unsigned char control)
{
unsigned char ret = 32|64;
if (control & PARPORT_CONTROL_SELECT) /* XXX: What is SELECP? */
ret &= ~32; /* /SELECT_IN */
if (control & PARPORT_CONTROL_INIT) /* INITP */
ret |= 128;
if (control & PARPORT_CONTROL_AUTOFD) /* AUTOLF */
ret &= ~64;
if (control & PARPORT_CONTROL_STROBE) /* Strobe */
/* Handled directly by hardware */;
return ret;
}
static unsigned char control_mfc3_to_pc(unsigned char control)
{
unsigned char ret = PARPORT_CONTROL_STROBE
| PARPORT_CONTROL_AUTOFD | PARPORT_CONTROL_SELECT;
if (control & 128) /* /INITP */
ret |= PARPORT_CONTROL_INIT;
if (control & 64) /* /AUTOLF */
ret &= ~PARPORT_CONTROL_AUTOFD;
if (control & 32) /* /SELECT_IN */
ret &= ~PARPORT_CONTROL_SELECT;
return ret;
}
static void mfc3_write_control(struct parport *p, unsigned char control)
{
DPRINTK(KERN_DEBUG "write_control %02x\n",control);
pia(p)->ppra = (pia(p)->ppra & 0x1f) | control_pc_to_mfc3(control);
}
static unsigned char mfc3_read_control( struct parport *p)
{
DPRINTK(KERN_DEBUG "read_control \n");
return control_mfc3_to_pc(pia(p)->ppra & 0xe0);
}
static unsigned char mfc3_frob_control( struct parport *p, unsigned char mask, unsigned char val)
{
unsigned char old;
DPRINTK(KERN_DEBUG "frob_control mask %02x, value %02x\n",mask,val);
old = mfc3_read_control(p);
mfc3_write_control(p, (old & ~mask) ^ val);
return old;
}
static unsigned char status_mfc3_to_pc(unsigned char status)
{
unsigned char ret = PARPORT_STATUS_BUSY;
if (status & 1) /* Busy */
ret &= ~PARPORT_STATUS_BUSY;
if (status & 2) /* PaperOut */
ret |= PARPORT_STATUS_PAPEROUT;
if (status & 4) /* Selected */
ret |= PARPORT_STATUS_SELECT;
if (status & 8) /* Ack */
ret |= PARPORT_STATUS_ACK;
if (status & 16) /* /ERROR */
ret |= PARPORT_STATUS_ERROR;
return ret;
}
static unsigned char mfc3_read_status(struct parport *p)
{
unsigned char status;
status = status_mfc3_to_pc(pia(p)->ppra & 0x1f);
DPRINTK(KERN_DEBUG "read_status %02x\n", status);
return status;
}
static int use_cnt = 0;
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
static irqreturn_t mfc3_interrupt(int irq, void *dev_id)
{
int i;
for( i = 0; i < MAX_MFC; i++)
if (this_port[i] != NULL)
if (pia(this_port[i])->crb & 128) { /* Board caused interrupt */
dummy = pia(this_port[i])->pprb; /* clear irq bit */
parport_generic_irq(this_port[i]);
}
return IRQ_HANDLED;
}
static void mfc3_enable_irq(struct parport *p)
{
pia(p)->crb |= PIA_C1_ENABLE_IRQ;
}
static void mfc3_disable_irq(struct parport *p)
{
pia(p)->crb &= ~PIA_C1_ENABLE_IRQ;
}
static void mfc3_data_forward(struct parport *p)
{
DPRINTK(KERN_DEBUG "forward\n");
pia(p)->crb &= ~PIA_DDR; /* make data direction register visible */
pia(p)->pddrb = 255; /* all pins output */
pia(p)->crb |= PIA_DDR; /* make data register visible - default */
}
static void mfc3_data_reverse(struct parport *p)
{
DPRINTK(KERN_DEBUG "reverse\n");
pia(p)->crb &= ~PIA_DDR; /* make data direction register visible */
pia(p)->pddrb = 0; /* all pins input */
pia(p)->crb |= PIA_DDR; /* make data register visible - default */
}
static void mfc3_init_state(struct pardevice *dev, struct parport_state *s)
{
s->u.amiga.data = 0;
s->u.amiga.datadir = 255;
s->u.amiga.status = 0;
s->u.amiga.statusdir = 0xe0;
}
static void mfc3_save_state(struct parport *p, struct parport_state *s)
{
s->u.amiga.data = pia(p)->pprb;
pia(p)->crb &= ~PIA_DDR;
s->u.amiga.datadir = pia(p)->pddrb;
pia(p)->crb |= PIA_DDR;
s->u.amiga.status = pia(p)->ppra;
pia(p)->cra &= ~PIA_DDR;
s->u.amiga.statusdir = pia(p)->pddrb;
pia(p)->cra |= PIA_DDR;
}
static void mfc3_restore_state(struct parport *p, struct parport_state *s)
{
pia(p)->pprb = s->u.amiga.data;
pia(p)->crb &= ~PIA_DDR;
pia(p)->pddrb = s->u.amiga.datadir;
pia(p)->crb |= PIA_DDR;
pia(p)->ppra = s->u.amiga.status;
pia(p)->cra &= ~PIA_DDR;
pia(p)->pddrb = s->u.amiga.statusdir;
pia(p)->cra |= PIA_DDR;
}
static struct parport_operations pp_mfc3_ops = {
.write_data = mfc3_write_data,
.read_data = mfc3_read_data,
.write_control = mfc3_write_control,
.read_control = mfc3_read_control,
.frob_control = mfc3_frob_control,
.read_status = mfc3_read_status,
.enable_irq = mfc3_enable_irq,
.disable_irq = mfc3_disable_irq,
.data_forward = mfc3_data_forward,
.data_reverse = mfc3_data_reverse,
.init_state = mfc3_init_state,
.save_state = mfc3_save_state,
.restore_state = mfc3_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,
};
/* ----------- Initialisation code --------------------------------- */
static int __init parport_mfc3_init(void)
{
struct parport *p;
int pias = 0;
struct pia *pp;
struct zorro_dev *z = NULL;
if (!MACH_IS_AMIGA)
return -ENODEV;
while ((z = zorro_find_device(ZORRO_PROD_BSC_MULTIFACE_III, z))) {
unsigned long piabase = z->resource.start+PIABASE;
if (!request_mem_region(piabase, sizeof(struct pia), "PIA"))
continue;
pp = (struct pia *)ZTWO_VADDR(piabase);
pp->crb = 0;
pp->pddrb = 255; /* all data pins output */
pp->crb = PIA_DDR|32|8;
dummy = pp->pddrb; /* reading clears interrupt */
pp->cra = 0;
pp->pddra = 0xe0; /* /RESET, /DIR ,/AUTO-FEED output */
pp->cra = PIA_DDR;
pp->ppra = 0; /* reset printer */
udelay(10);
pp->ppra = 128;
p = parport_register_port((unsigned long)pp, IRQ_AMIGA_PORTS,
PARPORT_DMA_NONE, &pp_mfc3_ops);
if (!p)
goto out_port;
if (p->irq != PARPORT_IRQ_NONE) {
if (use_cnt++ == 0)
if (request_irq(IRQ_AMIGA_PORTS, mfc3_interrupt, IRQF_SHARED, p->name, &pp_mfc3_ops))
goto out_irq;
}
p->dev = &z->dev;
this_port[pias++] = p;
printk(KERN_INFO "%s: Multiface III port using irq\n", p->name);
/* XXX: set operating mode */
p->private_data = (void *)piabase;
parport_announce_port (p);
if (pias >= MAX_MFC)
break;
continue;
out_irq:
parport_put_port(p);
out_port:
release_mem_region(piabase, sizeof(struct pia));
}
return pias ? 0 : -ENODEV;
}
static void __exit parport_mfc3_exit(void)
{
int i;
for (i = 0; i < MAX_MFC; i++) {
if (!this_port[i])
continue;
parport_remove_port(this_port[i]);
if (this_port[i]->irq != PARPORT_IRQ_NONE) {
if (--use_cnt == 0)
free_irq(IRQ_AMIGA_PORTS, &pp_mfc3_ops);
}
release_mem_region(ZTWO_PADDR(this_port[i]->private_data), sizeof(struct pia));
parport_put_port(this_port[i]);
}
}
MODULE_AUTHOR("Joerg Dorchain <joerg@dorchain.net>");
MODULE_DESCRIPTION("Parport Driver for Multiface 3 expansion cards Parallel Port");
MODULE_SUPPORTED_DEVICE("Multiface 3 Parallel Port");
MODULE_LICENSE("GPL");
module_init(parport_mfc3_init)
module_exit(parport_mfc3_exit)