162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * HP i8042-based System Device Controller driver.
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (c) 2001 Brian S. Julin
562306a36Sopenharmony_ci * All rights reserved.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Redistribution and use in source and binary forms, with or without
862306a36Sopenharmony_ci * modification, are permitted provided that the following conditions
962306a36Sopenharmony_ci * are met:
1062306a36Sopenharmony_ci * 1. Redistributions of source code must retain the above copyright
1162306a36Sopenharmony_ci *    notice, this list of conditions, and the following disclaimer,
1262306a36Sopenharmony_ci *    without modification.
1362306a36Sopenharmony_ci * 2. The name of the author may not be used to endorse or promote products
1462306a36Sopenharmony_ci *    derived from this software without specific prior written permission.
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * Alternatively, this software may be distributed under the terms of the
1762306a36Sopenharmony_ci * GNU General Public License ("GPL").
1862306a36Sopenharmony_ci *
1962306a36Sopenharmony_ci * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
2062306a36Sopenharmony_ci * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2162306a36Sopenharmony_ci * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2262306a36Sopenharmony_ci * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
2362306a36Sopenharmony_ci * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2462306a36Sopenharmony_ci * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2562306a36Sopenharmony_ci * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2662306a36Sopenharmony_ci * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2762306a36Sopenharmony_ci * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2862306a36Sopenharmony_ci *
2962306a36Sopenharmony_ci * References:
3062306a36Sopenharmony_ci * System Device Controller Microprocessor Firmware Theory of Operation
3162306a36Sopenharmony_ci *      for Part Number 1820-4784 Revision B.  Dwg No. A-1820-4784-2
3262306a36Sopenharmony_ci * Helge Deller's original hilkbd.c port for PA-RISC.
3362306a36Sopenharmony_ci *
3462306a36Sopenharmony_ci *
3562306a36Sopenharmony_ci * Driver theory of operation:
3662306a36Sopenharmony_ci *
3762306a36Sopenharmony_ci * hp_sdc_put does all writing to the SDC.  ISR can run on a different
3862306a36Sopenharmony_ci * CPU than hp_sdc_put, but only one CPU runs hp_sdc_put at a time
3962306a36Sopenharmony_ci * (it cannot really benefit from SMP anyway.)  A tasket fit this perfectly.
4062306a36Sopenharmony_ci *
4162306a36Sopenharmony_ci * All data coming back from the SDC is sent via interrupt and can be read
4262306a36Sopenharmony_ci * fully in the ISR, so there are no latency/throughput problems there.
4362306a36Sopenharmony_ci * The problem is with output, due to the slow clock speed of the SDC
4462306a36Sopenharmony_ci * compared to the CPU.  This should not be too horrible most of the time,
4562306a36Sopenharmony_ci * but if used with HIL devices that support the multibyte transfer command,
4662306a36Sopenharmony_ci * keeping outbound throughput flowing at the 6500KBps that the HIL is
4762306a36Sopenharmony_ci * capable of is more than can be done at HZ=100.
4862306a36Sopenharmony_ci *
4962306a36Sopenharmony_ci * Busy polling for IBF clear wastes CPU cycles and bus cycles.  hp_sdc.ibf
5062306a36Sopenharmony_ci * is set to 0 when the IBF flag in the status register has cleared.  ISR
5162306a36Sopenharmony_ci * may do this, and may also access the parts of queued transactions related
5262306a36Sopenharmony_ci * to reading data back from the SDC, but otherwise will not touch the
5362306a36Sopenharmony_ci * hp_sdc state. Whenever a register is written hp_sdc.ibf is set to 1.
5462306a36Sopenharmony_ci *
5562306a36Sopenharmony_ci * The i8042 write index and the values in the 4-byte input buffer
5662306a36Sopenharmony_ci * starting at 0x70 are kept track of in hp_sdc.wi, and .r7[], respectively,
5762306a36Sopenharmony_ci * to minimize the amount of IO needed to the SDC.  However these values
5862306a36Sopenharmony_ci * do not need to be locked since they are only ever accessed by hp_sdc_put.
5962306a36Sopenharmony_ci *
6062306a36Sopenharmony_ci * A timer task schedules the tasklet once per second just to make
6162306a36Sopenharmony_ci * sure it doesn't freeze up and to allow for bad reads to time out.
6262306a36Sopenharmony_ci */
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci#include <linux/hp_sdc.h>
6562306a36Sopenharmony_ci#include <linux/errno.h>
6662306a36Sopenharmony_ci#include <linux/init.h>
6762306a36Sopenharmony_ci#include <linux/module.h>
6862306a36Sopenharmony_ci#include <linux/ioport.h>
6962306a36Sopenharmony_ci#include <linux/time.h>
7062306a36Sopenharmony_ci#include <linux/semaphore.h>
7162306a36Sopenharmony_ci#include <linux/slab.h>
7262306a36Sopenharmony_ci#include <linux/hil.h>
7362306a36Sopenharmony_ci#include <asm/io.h>
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci/* Machine-specific abstraction */
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci#if defined(__hppa__)
7862306a36Sopenharmony_ci# include <asm/parisc-device.h>
7962306a36Sopenharmony_ci# define sdc_readb(p)		gsc_readb(p)
8062306a36Sopenharmony_ci# define sdc_writeb(v,p)	gsc_writeb((v),(p))
8162306a36Sopenharmony_ci#elif defined(__mc68000__)
8262306a36Sopenharmony_ci#include <linux/uaccess.h>
8362306a36Sopenharmony_ci# define sdc_readb(p)		in_8(p)
8462306a36Sopenharmony_ci# define sdc_writeb(v,p)	out_8((p),(v))
8562306a36Sopenharmony_ci#else
8662306a36Sopenharmony_ci# error "HIL is not supported on this platform"
8762306a36Sopenharmony_ci#endif
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci#define PREFIX "HP SDC: "
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ciMODULE_AUTHOR("Brian S. Julin <bri@calyx.com>");
9262306a36Sopenharmony_ciMODULE_DESCRIPTION("HP i8042-based SDC Driver");
9362306a36Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL");
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_request_timer_irq);
9662306a36Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_request_hil_irq);
9762306a36Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_request_cooked_irq);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_release_timer_irq);
10062306a36Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_release_hil_irq);
10162306a36Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_release_cooked_irq);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ciEXPORT_SYMBOL(__hp_sdc_enqueue_transaction);
10462306a36Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_enqueue_transaction);
10562306a36Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_dequeue_transaction);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic bool hp_sdc_disabled;
10862306a36Sopenharmony_cimodule_param_named(no_hpsdc, hp_sdc_disabled, bool, 0);
10962306a36Sopenharmony_ciMODULE_PARM_DESC(no_hpsdc, "Do not enable HP SDC driver.");
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic hp_i8042_sdc	hp_sdc;	/* All driver state is kept in here. */
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci/*************** primitives for use in any context *********************/
11462306a36Sopenharmony_cistatic inline uint8_t hp_sdc_status_in8(void)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	uint8_t status;
11762306a36Sopenharmony_ci	unsigned long flags;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	write_lock_irqsave(&hp_sdc.ibf_lock, flags);
12062306a36Sopenharmony_ci	status = sdc_readb(hp_sdc.status_io);
12162306a36Sopenharmony_ci	if (!(status & HP_SDC_STATUS_IBF))
12262306a36Sopenharmony_ci		hp_sdc.ibf = 0;
12362306a36Sopenharmony_ci	write_unlock_irqrestore(&hp_sdc.ibf_lock, flags);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	return status;
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistatic inline uint8_t hp_sdc_data_in8(void)
12962306a36Sopenharmony_ci{
13062306a36Sopenharmony_ci	return sdc_readb(hp_sdc.data_io);
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic inline void hp_sdc_status_out8(uint8_t val)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	unsigned long flags;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	write_lock_irqsave(&hp_sdc.ibf_lock, flags);
13862306a36Sopenharmony_ci	hp_sdc.ibf = 1;
13962306a36Sopenharmony_ci	if ((val & 0xf0) == 0xe0)
14062306a36Sopenharmony_ci		hp_sdc.wi = 0xff;
14162306a36Sopenharmony_ci	sdc_writeb(val, hp_sdc.status_io);
14262306a36Sopenharmony_ci	write_unlock_irqrestore(&hp_sdc.ibf_lock, flags);
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic inline void hp_sdc_data_out8(uint8_t val)
14662306a36Sopenharmony_ci{
14762306a36Sopenharmony_ci	unsigned long flags;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	write_lock_irqsave(&hp_sdc.ibf_lock, flags);
15062306a36Sopenharmony_ci	hp_sdc.ibf = 1;
15162306a36Sopenharmony_ci	sdc_writeb(val, hp_sdc.data_io);
15262306a36Sopenharmony_ci	write_unlock_irqrestore(&hp_sdc.ibf_lock, flags);
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci/*	Care must be taken to only invoke hp_sdc_spin_ibf when
15662306a36Sopenharmony_ci *	absolutely needed, or in rarely invoked subroutines.
15762306a36Sopenharmony_ci *	Not only does it waste CPU cycles, it also wastes bus cycles.
15862306a36Sopenharmony_ci */
15962306a36Sopenharmony_cistatic inline void hp_sdc_spin_ibf(void)
16062306a36Sopenharmony_ci{
16162306a36Sopenharmony_ci	unsigned long flags;
16262306a36Sopenharmony_ci	rwlock_t *lock;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	lock = &hp_sdc.ibf_lock;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	read_lock_irqsave(lock, flags);
16762306a36Sopenharmony_ci	if (!hp_sdc.ibf) {
16862306a36Sopenharmony_ci		read_unlock_irqrestore(lock, flags);
16962306a36Sopenharmony_ci		return;
17062306a36Sopenharmony_ci	}
17162306a36Sopenharmony_ci	read_unlock(lock);
17262306a36Sopenharmony_ci	write_lock(lock);
17362306a36Sopenharmony_ci	while (sdc_readb(hp_sdc.status_io) & HP_SDC_STATUS_IBF)
17462306a36Sopenharmony_ci		{ }
17562306a36Sopenharmony_ci	hp_sdc.ibf = 0;
17662306a36Sopenharmony_ci	write_unlock_irqrestore(lock, flags);
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci/************************ Interrupt context functions ************************/
18162306a36Sopenharmony_cistatic void hp_sdc_take(int irq, void *dev_id, uint8_t status, uint8_t data)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	hp_sdc_transaction *curr;
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	read_lock(&hp_sdc.rtq_lock);
18662306a36Sopenharmony_ci	if (hp_sdc.rcurr < 0) {
18762306a36Sopenharmony_ci		read_unlock(&hp_sdc.rtq_lock);
18862306a36Sopenharmony_ci		return;
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci	curr = hp_sdc.tq[hp_sdc.rcurr];
19162306a36Sopenharmony_ci	read_unlock(&hp_sdc.rtq_lock);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	curr->seq[curr->idx++] = status;
19462306a36Sopenharmony_ci	curr->seq[curr->idx++] = data;
19562306a36Sopenharmony_ci	hp_sdc.rqty -= 2;
19662306a36Sopenharmony_ci	hp_sdc.rtime = ktime_get();
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	if (hp_sdc.rqty <= 0) {
19962306a36Sopenharmony_ci		/* All data has been gathered. */
20062306a36Sopenharmony_ci		if (curr->seq[curr->actidx] & HP_SDC_ACT_SEMAPHORE)
20162306a36Sopenharmony_ci			if (curr->act.semaphore)
20262306a36Sopenharmony_ci				up(curr->act.semaphore);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci		if (curr->seq[curr->actidx] & HP_SDC_ACT_CALLBACK)
20562306a36Sopenharmony_ci			if (curr->act.irqhook)
20662306a36Sopenharmony_ci				curr->act.irqhook(irq, dev_id, status, data);
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci		curr->actidx = curr->idx;
20962306a36Sopenharmony_ci		curr->idx++;
21062306a36Sopenharmony_ci		/* Return control of this transaction */
21162306a36Sopenharmony_ci		write_lock(&hp_sdc.rtq_lock);
21262306a36Sopenharmony_ci		hp_sdc.rcurr = -1;
21362306a36Sopenharmony_ci		hp_sdc.rqty = 0;
21462306a36Sopenharmony_ci		write_unlock(&hp_sdc.rtq_lock);
21562306a36Sopenharmony_ci		tasklet_schedule(&hp_sdc.task);
21662306a36Sopenharmony_ci	}
21762306a36Sopenharmony_ci}
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_cistatic irqreturn_t hp_sdc_isr(int irq, void *dev_id)
22062306a36Sopenharmony_ci{
22162306a36Sopenharmony_ci	uint8_t status, data;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	status = hp_sdc_status_in8();
22462306a36Sopenharmony_ci	/* Read data unconditionally to advance i8042. */
22562306a36Sopenharmony_ci	data =   hp_sdc_data_in8();
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	/* For now we are ignoring these until we get the SDC to behave. */
22862306a36Sopenharmony_ci	if (((status & 0xf1) == 0x51) && data == 0x82)
22962306a36Sopenharmony_ci		return IRQ_HANDLED;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	switch (status & HP_SDC_STATUS_IRQMASK) {
23262306a36Sopenharmony_ci	case 0: /* This case is not documented. */
23362306a36Sopenharmony_ci		break;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	case HP_SDC_STATUS_USERTIMER:
23662306a36Sopenharmony_ci	case HP_SDC_STATUS_PERIODIC:
23762306a36Sopenharmony_ci	case HP_SDC_STATUS_TIMER:
23862306a36Sopenharmony_ci		read_lock(&hp_sdc.hook_lock);
23962306a36Sopenharmony_ci		if (hp_sdc.timer != NULL)
24062306a36Sopenharmony_ci			hp_sdc.timer(irq, dev_id, status, data);
24162306a36Sopenharmony_ci		read_unlock(&hp_sdc.hook_lock);
24262306a36Sopenharmony_ci		break;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	case HP_SDC_STATUS_REG:
24562306a36Sopenharmony_ci		hp_sdc_take(irq, dev_id, status, data);
24662306a36Sopenharmony_ci		break;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	case HP_SDC_STATUS_HILCMD:
24962306a36Sopenharmony_ci	case HP_SDC_STATUS_HILDATA:
25062306a36Sopenharmony_ci		read_lock(&hp_sdc.hook_lock);
25162306a36Sopenharmony_ci		if (hp_sdc.hil != NULL)
25262306a36Sopenharmony_ci			hp_sdc.hil(irq, dev_id, status, data);
25362306a36Sopenharmony_ci		read_unlock(&hp_sdc.hook_lock);
25462306a36Sopenharmony_ci		break;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	case HP_SDC_STATUS_PUP:
25762306a36Sopenharmony_ci		read_lock(&hp_sdc.hook_lock);
25862306a36Sopenharmony_ci		if (hp_sdc.pup != NULL)
25962306a36Sopenharmony_ci			hp_sdc.pup(irq, dev_id, status, data);
26062306a36Sopenharmony_ci		else
26162306a36Sopenharmony_ci			printk(KERN_INFO PREFIX "HP SDC reports successful PUP.\n");
26262306a36Sopenharmony_ci		read_unlock(&hp_sdc.hook_lock);
26362306a36Sopenharmony_ci		break;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	default:
26662306a36Sopenharmony_ci		read_lock(&hp_sdc.hook_lock);
26762306a36Sopenharmony_ci		if (hp_sdc.cooked != NULL)
26862306a36Sopenharmony_ci			hp_sdc.cooked(irq, dev_id, status, data);
26962306a36Sopenharmony_ci		read_unlock(&hp_sdc.hook_lock);
27062306a36Sopenharmony_ci		break;
27162306a36Sopenharmony_ci	}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	return IRQ_HANDLED;
27462306a36Sopenharmony_ci}
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_cistatic irqreturn_t hp_sdc_nmisr(int irq, void *dev_id)
27862306a36Sopenharmony_ci{
27962306a36Sopenharmony_ci	int status;
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	status = hp_sdc_status_in8();
28262306a36Sopenharmony_ci	printk(KERN_WARNING PREFIX "NMI !\n");
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci#if 0
28562306a36Sopenharmony_ci	if (status & HP_SDC_NMISTATUS_FHS) {
28662306a36Sopenharmony_ci		read_lock(&hp_sdc.hook_lock);
28762306a36Sopenharmony_ci		if (hp_sdc.timer != NULL)
28862306a36Sopenharmony_ci			hp_sdc.timer(irq, dev_id, status, 0);
28962306a36Sopenharmony_ci		read_unlock(&hp_sdc.hook_lock);
29062306a36Sopenharmony_ci	} else {
29162306a36Sopenharmony_ci		/* TODO: pass this on to the HIL handler, or do SAK here? */
29262306a36Sopenharmony_ci		printk(KERN_WARNING PREFIX "HIL NMI\n");
29362306a36Sopenharmony_ci	}
29462306a36Sopenharmony_ci#endif
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	return IRQ_HANDLED;
29762306a36Sopenharmony_ci}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci/***************** Kernel (tasklet) context functions ****************/
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ciunsigned long hp_sdc_put(void);
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_cistatic void hp_sdc_tasklet(unsigned long foo)
30562306a36Sopenharmony_ci{
30662306a36Sopenharmony_ci	write_lock_irq(&hp_sdc.rtq_lock);
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	if (hp_sdc.rcurr >= 0) {
30962306a36Sopenharmony_ci		ktime_t now = ktime_get();
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci		if (ktime_after(now, ktime_add_us(hp_sdc.rtime,
31262306a36Sopenharmony_ci						  HP_SDC_MAX_REG_DELAY))) {
31362306a36Sopenharmony_ci			hp_sdc_transaction *curr;
31462306a36Sopenharmony_ci			uint8_t tmp;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci			curr = hp_sdc.tq[hp_sdc.rcurr];
31762306a36Sopenharmony_ci			/* If this turns out to be a normal failure mode
31862306a36Sopenharmony_ci			 * we'll need to figure out a way to communicate
31962306a36Sopenharmony_ci			 * it back to the application. and be less verbose.
32062306a36Sopenharmony_ci			 */
32162306a36Sopenharmony_ci			printk(KERN_WARNING PREFIX "read timeout (%lldus)!\n",
32262306a36Sopenharmony_ci			       ktime_us_delta(now, hp_sdc.rtime));
32362306a36Sopenharmony_ci			curr->idx += hp_sdc.rqty;
32462306a36Sopenharmony_ci			hp_sdc.rqty = 0;
32562306a36Sopenharmony_ci			tmp = curr->seq[curr->actidx];
32662306a36Sopenharmony_ci			curr->seq[curr->actidx] |= HP_SDC_ACT_DEAD;
32762306a36Sopenharmony_ci			if (tmp & HP_SDC_ACT_SEMAPHORE)
32862306a36Sopenharmony_ci				if (curr->act.semaphore)
32962306a36Sopenharmony_ci					up(curr->act.semaphore);
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci			if (tmp & HP_SDC_ACT_CALLBACK) {
33262306a36Sopenharmony_ci				/* Note this means that irqhooks may be called
33362306a36Sopenharmony_ci				 * in tasklet/bh context.
33462306a36Sopenharmony_ci				 */
33562306a36Sopenharmony_ci				if (curr->act.irqhook)
33662306a36Sopenharmony_ci					curr->act.irqhook(0, NULL, 0, 0);
33762306a36Sopenharmony_ci			}
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci			curr->actidx = curr->idx;
34062306a36Sopenharmony_ci			curr->idx++;
34162306a36Sopenharmony_ci			hp_sdc.rcurr = -1;
34262306a36Sopenharmony_ci		}
34362306a36Sopenharmony_ci	}
34462306a36Sopenharmony_ci	write_unlock_irq(&hp_sdc.rtq_lock);
34562306a36Sopenharmony_ci	hp_sdc_put();
34662306a36Sopenharmony_ci}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ciunsigned long hp_sdc_put(void)
34962306a36Sopenharmony_ci{
35062306a36Sopenharmony_ci	hp_sdc_transaction *curr;
35162306a36Sopenharmony_ci	uint8_t act;
35262306a36Sopenharmony_ci	int idx, curridx;
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	int limit = 0;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	write_lock(&hp_sdc.lock);
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	/* If i8042 buffers are full, we cannot do anything that
35962306a36Sopenharmony_ci	   requires output, so we skip to the administrativa. */
36062306a36Sopenharmony_ci	if (hp_sdc.ibf) {
36162306a36Sopenharmony_ci		hp_sdc_status_in8();
36262306a36Sopenharmony_ci		if (hp_sdc.ibf)
36362306a36Sopenharmony_ci			goto finish;
36462306a36Sopenharmony_ci	}
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci anew:
36762306a36Sopenharmony_ci	/* See if we are in the middle of a sequence. */
36862306a36Sopenharmony_ci	if (hp_sdc.wcurr < 0)
36962306a36Sopenharmony_ci		hp_sdc.wcurr = 0;
37062306a36Sopenharmony_ci	read_lock_irq(&hp_sdc.rtq_lock);
37162306a36Sopenharmony_ci	if (hp_sdc.rcurr == hp_sdc.wcurr)
37262306a36Sopenharmony_ci		hp_sdc.wcurr++;
37362306a36Sopenharmony_ci	read_unlock_irq(&hp_sdc.rtq_lock);
37462306a36Sopenharmony_ci	if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN)
37562306a36Sopenharmony_ci		hp_sdc.wcurr = 0;
37662306a36Sopenharmony_ci	curridx = hp_sdc.wcurr;
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	if (hp_sdc.tq[curridx] != NULL)
37962306a36Sopenharmony_ci		goto start;
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	while (++curridx != hp_sdc.wcurr) {
38262306a36Sopenharmony_ci		if (curridx >= HP_SDC_QUEUE_LEN) {
38362306a36Sopenharmony_ci			curridx = -1; /* Wrap to top */
38462306a36Sopenharmony_ci			continue;
38562306a36Sopenharmony_ci		}
38662306a36Sopenharmony_ci		read_lock_irq(&hp_sdc.rtq_lock);
38762306a36Sopenharmony_ci		if (hp_sdc.rcurr == curridx) {
38862306a36Sopenharmony_ci			read_unlock_irq(&hp_sdc.rtq_lock);
38962306a36Sopenharmony_ci			continue;
39062306a36Sopenharmony_ci		}
39162306a36Sopenharmony_ci		read_unlock_irq(&hp_sdc.rtq_lock);
39262306a36Sopenharmony_ci		if (hp_sdc.tq[curridx] != NULL)
39362306a36Sopenharmony_ci			break; /* Found one. */
39462306a36Sopenharmony_ci	}
39562306a36Sopenharmony_ci	if (curridx == hp_sdc.wcurr) { /* There's nothing queued to do. */
39662306a36Sopenharmony_ci		curridx = -1;
39762306a36Sopenharmony_ci	}
39862306a36Sopenharmony_ci	hp_sdc.wcurr = curridx;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci start:
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	/* Check to see if the interrupt mask needs to be set. */
40362306a36Sopenharmony_ci	if (hp_sdc.set_im) {
40462306a36Sopenharmony_ci		hp_sdc_status_out8(hp_sdc.im | HP_SDC_CMD_SET_IM);
40562306a36Sopenharmony_ci		hp_sdc.set_im = 0;
40662306a36Sopenharmony_ci		goto finish;
40762306a36Sopenharmony_ci	}
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	if (hp_sdc.wcurr == -1)
41062306a36Sopenharmony_ci		goto done;
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	curr = hp_sdc.tq[curridx];
41362306a36Sopenharmony_ci	idx = curr->actidx;
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	if (curr->actidx >= curr->endidx) {
41662306a36Sopenharmony_ci		hp_sdc.tq[curridx] = NULL;
41762306a36Sopenharmony_ci		/* Interleave outbound data between the transactions. */
41862306a36Sopenharmony_ci		hp_sdc.wcurr++;
41962306a36Sopenharmony_ci		if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN)
42062306a36Sopenharmony_ci			hp_sdc.wcurr = 0;
42162306a36Sopenharmony_ci		goto finish;
42262306a36Sopenharmony_ci	}
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	act = curr->seq[idx];
42562306a36Sopenharmony_ci	idx++;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	if (curr->idx >= curr->endidx) {
42862306a36Sopenharmony_ci		if (act & HP_SDC_ACT_DEALLOC)
42962306a36Sopenharmony_ci			kfree(curr);
43062306a36Sopenharmony_ci		hp_sdc.tq[curridx] = NULL;
43162306a36Sopenharmony_ci		/* Interleave outbound data between the transactions. */
43262306a36Sopenharmony_ci		hp_sdc.wcurr++;
43362306a36Sopenharmony_ci		if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN)
43462306a36Sopenharmony_ci			hp_sdc.wcurr = 0;
43562306a36Sopenharmony_ci		goto finish;
43662306a36Sopenharmony_ci	}
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	while (act & HP_SDC_ACT_PRECMD) {
43962306a36Sopenharmony_ci		if (curr->idx != idx) {
44062306a36Sopenharmony_ci			idx++;
44162306a36Sopenharmony_ci			act &= ~HP_SDC_ACT_PRECMD;
44262306a36Sopenharmony_ci			break;
44362306a36Sopenharmony_ci		}
44462306a36Sopenharmony_ci		hp_sdc_status_out8(curr->seq[idx]);
44562306a36Sopenharmony_ci		curr->idx++;
44662306a36Sopenharmony_ci		/* act finished? */
44762306a36Sopenharmony_ci		if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_PRECMD)
44862306a36Sopenharmony_ci			goto actdone;
44962306a36Sopenharmony_ci		/* skip quantity field if data-out sequence follows. */
45062306a36Sopenharmony_ci		if (act & HP_SDC_ACT_DATAOUT)
45162306a36Sopenharmony_ci			curr->idx++;
45262306a36Sopenharmony_ci		goto finish;
45362306a36Sopenharmony_ci	}
45462306a36Sopenharmony_ci	if (act & HP_SDC_ACT_DATAOUT) {
45562306a36Sopenharmony_ci		int qty;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci		qty = curr->seq[idx];
45862306a36Sopenharmony_ci		idx++;
45962306a36Sopenharmony_ci		if (curr->idx - idx < qty) {
46062306a36Sopenharmony_ci			hp_sdc_data_out8(curr->seq[curr->idx]);
46162306a36Sopenharmony_ci			curr->idx++;
46262306a36Sopenharmony_ci			/* act finished? */
46362306a36Sopenharmony_ci			if (curr->idx - idx >= qty &&
46462306a36Sopenharmony_ci			    (act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAOUT)
46562306a36Sopenharmony_ci				goto actdone;
46662306a36Sopenharmony_ci			goto finish;
46762306a36Sopenharmony_ci		}
46862306a36Sopenharmony_ci		idx += qty;
46962306a36Sopenharmony_ci		act &= ~HP_SDC_ACT_DATAOUT;
47062306a36Sopenharmony_ci	} else
47162306a36Sopenharmony_ci	    while (act & HP_SDC_ACT_DATAREG) {
47262306a36Sopenharmony_ci		int mask;
47362306a36Sopenharmony_ci		uint8_t w7[4];
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci		mask = curr->seq[idx];
47662306a36Sopenharmony_ci		if (idx != curr->idx) {
47762306a36Sopenharmony_ci			idx++;
47862306a36Sopenharmony_ci			idx += !!(mask & 1);
47962306a36Sopenharmony_ci			idx += !!(mask & 2);
48062306a36Sopenharmony_ci			idx += !!(mask & 4);
48162306a36Sopenharmony_ci			idx += !!(mask & 8);
48262306a36Sopenharmony_ci			act &= ~HP_SDC_ACT_DATAREG;
48362306a36Sopenharmony_ci			break;
48462306a36Sopenharmony_ci		}
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci		w7[0] = (mask & 1) ? curr->seq[++idx] : hp_sdc.r7[0];
48762306a36Sopenharmony_ci		w7[1] = (mask & 2) ? curr->seq[++idx] : hp_sdc.r7[1];
48862306a36Sopenharmony_ci		w7[2] = (mask & 4) ? curr->seq[++idx] : hp_sdc.r7[2];
48962306a36Sopenharmony_ci		w7[3] = (mask & 8) ? curr->seq[++idx] : hp_sdc.r7[3];
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci		if (hp_sdc.wi > 0x73 || hp_sdc.wi < 0x70 ||
49262306a36Sopenharmony_ci		    w7[hp_sdc.wi - 0x70] == hp_sdc.r7[hp_sdc.wi - 0x70]) {
49362306a36Sopenharmony_ci			int i = 0;
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci			/* Need to point the write index register */
49662306a36Sopenharmony_ci			while (i < 4 && w7[i] == hp_sdc.r7[i])
49762306a36Sopenharmony_ci				i++;
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci			if (i < 4) {
50062306a36Sopenharmony_ci				hp_sdc_status_out8(HP_SDC_CMD_SET_D0 + i);
50162306a36Sopenharmony_ci				hp_sdc.wi = 0x70 + i;
50262306a36Sopenharmony_ci				goto finish;
50362306a36Sopenharmony_ci			}
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci			idx++;
50662306a36Sopenharmony_ci			if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAREG)
50762306a36Sopenharmony_ci				goto actdone;
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci			curr->idx = idx;
51062306a36Sopenharmony_ci			act &= ~HP_SDC_ACT_DATAREG;
51162306a36Sopenharmony_ci			break;
51262306a36Sopenharmony_ci		}
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci		hp_sdc_data_out8(w7[hp_sdc.wi - 0x70]);
51562306a36Sopenharmony_ci		hp_sdc.r7[hp_sdc.wi - 0x70] = w7[hp_sdc.wi - 0x70];
51662306a36Sopenharmony_ci		hp_sdc.wi++; /* write index register autoincrements */
51762306a36Sopenharmony_ci		{
51862306a36Sopenharmony_ci			int i = 0;
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci			while ((i < 4) && w7[i] == hp_sdc.r7[i])
52162306a36Sopenharmony_ci				i++;
52262306a36Sopenharmony_ci			if (i >= 4) {
52362306a36Sopenharmony_ci				curr->idx = idx + 1;
52462306a36Sopenharmony_ci				if ((act & HP_SDC_ACT_DURING) ==
52562306a36Sopenharmony_ci				    HP_SDC_ACT_DATAREG)
52662306a36Sopenharmony_ci					goto actdone;
52762306a36Sopenharmony_ci			}
52862306a36Sopenharmony_ci		}
52962306a36Sopenharmony_ci		goto finish;
53062306a36Sopenharmony_ci	}
53162306a36Sopenharmony_ci	/* We don't go any further in the command if there is a pending read,
53262306a36Sopenharmony_ci	   because we don't want interleaved results. */
53362306a36Sopenharmony_ci	read_lock_irq(&hp_sdc.rtq_lock);
53462306a36Sopenharmony_ci	if (hp_sdc.rcurr >= 0) {
53562306a36Sopenharmony_ci		read_unlock_irq(&hp_sdc.rtq_lock);
53662306a36Sopenharmony_ci		goto finish;
53762306a36Sopenharmony_ci	}
53862306a36Sopenharmony_ci	read_unlock_irq(&hp_sdc.rtq_lock);
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	if (act & HP_SDC_ACT_POSTCMD) {
54262306a36Sopenharmony_ci		uint8_t postcmd;
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci		/* curr->idx should == idx at this point. */
54562306a36Sopenharmony_ci		postcmd = curr->seq[idx];
54662306a36Sopenharmony_ci		curr->idx++;
54762306a36Sopenharmony_ci		if (act & HP_SDC_ACT_DATAIN) {
54862306a36Sopenharmony_ci
54962306a36Sopenharmony_ci			/* Start a new read */
55062306a36Sopenharmony_ci			hp_sdc.rqty = curr->seq[curr->idx];
55162306a36Sopenharmony_ci			hp_sdc.rtime = ktime_get();
55262306a36Sopenharmony_ci			curr->idx++;
55362306a36Sopenharmony_ci			/* Still need to lock here in case of spurious irq. */
55462306a36Sopenharmony_ci			write_lock_irq(&hp_sdc.rtq_lock);
55562306a36Sopenharmony_ci			hp_sdc.rcurr = curridx;
55662306a36Sopenharmony_ci			write_unlock_irq(&hp_sdc.rtq_lock);
55762306a36Sopenharmony_ci			hp_sdc_status_out8(postcmd);
55862306a36Sopenharmony_ci			goto finish;
55962306a36Sopenharmony_ci		}
56062306a36Sopenharmony_ci		hp_sdc_status_out8(postcmd);
56162306a36Sopenharmony_ci		goto actdone;
56262306a36Sopenharmony_ci	}
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci actdone:
56562306a36Sopenharmony_ci	if (act & HP_SDC_ACT_SEMAPHORE)
56662306a36Sopenharmony_ci		up(curr->act.semaphore);
56762306a36Sopenharmony_ci	else if (act & HP_SDC_ACT_CALLBACK)
56862306a36Sopenharmony_ci		curr->act.irqhook(0,NULL,0,0);
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	if (curr->idx >= curr->endidx) { /* This transaction is over. */
57162306a36Sopenharmony_ci		if (act & HP_SDC_ACT_DEALLOC)
57262306a36Sopenharmony_ci			kfree(curr);
57362306a36Sopenharmony_ci		hp_sdc.tq[curridx] = NULL;
57462306a36Sopenharmony_ci	} else {
57562306a36Sopenharmony_ci		curr->actidx = idx + 1;
57662306a36Sopenharmony_ci		curr->idx = idx + 2;
57762306a36Sopenharmony_ci	}
57862306a36Sopenharmony_ci	/* Interleave outbound data between the transactions. */
57962306a36Sopenharmony_ci	hp_sdc.wcurr++;
58062306a36Sopenharmony_ci	if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN)
58162306a36Sopenharmony_ci		hp_sdc.wcurr = 0;
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci finish:
58462306a36Sopenharmony_ci	/* If by some quirk IBF has cleared and our ISR has run to
58562306a36Sopenharmony_ci	   see that that has happened, do it all again. */
58662306a36Sopenharmony_ci	if (!hp_sdc.ibf && limit++ < 20)
58762306a36Sopenharmony_ci		goto anew;
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci done:
59062306a36Sopenharmony_ci	if (hp_sdc.wcurr >= 0)
59162306a36Sopenharmony_ci		tasklet_schedule(&hp_sdc.task);
59262306a36Sopenharmony_ci	write_unlock(&hp_sdc.lock);
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci	return 0;
59562306a36Sopenharmony_ci}
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci/******* Functions called in either user or kernel context ****/
59862306a36Sopenharmony_ciint __hp_sdc_enqueue_transaction(hp_sdc_transaction *this)
59962306a36Sopenharmony_ci{
60062306a36Sopenharmony_ci	int i;
60162306a36Sopenharmony_ci
60262306a36Sopenharmony_ci	if (this == NULL) {
60362306a36Sopenharmony_ci		BUG();
60462306a36Sopenharmony_ci		return -EINVAL;
60562306a36Sopenharmony_ci	}
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ci	/* Can't have same transaction on queue twice */
60862306a36Sopenharmony_ci	for (i = 0; i < HP_SDC_QUEUE_LEN; i++)
60962306a36Sopenharmony_ci		if (hp_sdc.tq[i] == this)
61062306a36Sopenharmony_ci			goto fail;
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci	this->actidx = 0;
61362306a36Sopenharmony_ci	this->idx = 1;
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_ci	/* Search for empty slot */
61662306a36Sopenharmony_ci	for (i = 0; i < HP_SDC_QUEUE_LEN; i++)
61762306a36Sopenharmony_ci		if (hp_sdc.tq[i] == NULL) {
61862306a36Sopenharmony_ci			hp_sdc.tq[i] = this;
61962306a36Sopenharmony_ci			tasklet_schedule(&hp_sdc.task);
62062306a36Sopenharmony_ci			return 0;
62162306a36Sopenharmony_ci		}
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_ci	printk(KERN_WARNING PREFIX "No free slot to add transaction.\n");
62462306a36Sopenharmony_ci	return -EBUSY;
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_ci fail:
62762306a36Sopenharmony_ci	printk(KERN_WARNING PREFIX "Transaction add failed: transaction already queued?\n");
62862306a36Sopenharmony_ci	return -EINVAL;
62962306a36Sopenharmony_ci}
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ciint hp_sdc_enqueue_transaction(hp_sdc_transaction *this) {
63262306a36Sopenharmony_ci	unsigned long flags;
63362306a36Sopenharmony_ci	int ret;
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_ci	write_lock_irqsave(&hp_sdc.lock, flags);
63662306a36Sopenharmony_ci	ret = __hp_sdc_enqueue_transaction(this);
63762306a36Sopenharmony_ci	write_unlock_irqrestore(&hp_sdc.lock,flags);
63862306a36Sopenharmony_ci
63962306a36Sopenharmony_ci	return ret;
64062306a36Sopenharmony_ci}
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_ciint hp_sdc_dequeue_transaction(hp_sdc_transaction *this)
64362306a36Sopenharmony_ci{
64462306a36Sopenharmony_ci	unsigned long flags;
64562306a36Sopenharmony_ci	int i;
64662306a36Sopenharmony_ci
64762306a36Sopenharmony_ci	write_lock_irqsave(&hp_sdc.lock, flags);
64862306a36Sopenharmony_ci
64962306a36Sopenharmony_ci	/* TODO: don't remove it if it's not done. */
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_ci	for (i = 0; i < HP_SDC_QUEUE_LEN; i++)
65262306a36Sopenharmony_ci		if (hp_sdc.tq[i] == this)
65362306a36Sopenharmony_ci			hp_sdc.tq[i] = NULL;
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	write_unlock_irqrestore(&hp_sdc.lock, flags);
65662306a36Sopenharmony_ci	return 0;
65762306a36Sopenharmony_ci}
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci/********************** User context functions **************************/
66262306a36Sopenharmony_ciint hp_sdc_request_timer_irq(hp_sdc_irqhook *callback)
66362306a36Sopenharmony_ci{
66462306a36Sopenharmony_ci	if (callback == NULL || hp_sdc.dev == NULL)
66562306a36Sopenharmony_ci		return -EINVAL;
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_ci	write_lock_irq(&hp_sdc.hook_lock);
66862306a36Sopenharmony_ci	if (hp_sdc.timer != NULL) {
66962306a36Sopenharmony_ci		write_unlock_irq(&hp_sdc.hook_lock);
67062306a36Sopenharmony_ci		return -EBUSY;
67162306a36Sopenharmony_ci	}
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_ci	hp_sdc.timer = callback;
67462306a36Sopenharmony_ci	/* Enable interrupts from the timers */
67562306a36Sopenharmony_ci	hp_sdc.im &= ~HP_SDC_IM_FH;
67662306a36Sopenharmony_ci        hp_sdc.im &= ~HP_SDC_IM_PT;
67762306a36Sopenharmony_ci	hp_sdc.im &= ~HP_SDC_IM_TIMERS;
67862306a36Sopenharmony_ci	hp_sdc.set_im = 1;
67962306a36Sopenharmony_ci	write_unlock_irq(&hp_sdc.hook_lock);
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
68262306a36Sopenharmony_ci
68362306a36Sopenharmony_ci	return 0;
68462306a36Sopenharmony_ci}
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_ciint hp_sdc_request_hil_irq(hp_sdc_irqhook *callback)
68762306a36Sopenharmony_ci{
68862306a36Sopenharmony_ci	if (callback == NULL || hp_sdc.dev == NULL)
68962306a36Sopenharmony_ci		return -EINVAL;
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ci	write_lock_irq(&hp_sdc.hook_lock);
69262306a36Sopenharmony_ci	if (hp_sdc.hil != NULL) {
69362306a36Sopenharmony_ci		write_unlock_irq(&hp_sdc.hook_lock);
69462306a36Sopenharmony_ci		return -EBUSY;
69562306a36Sopenharmony_ci	}
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci	hp_sdc.hil = callback;
69862306a36Sopenharmony_ci	hp_sdc.im &= ~(HP_SDC_IM_HIL | HP_SDC_IM_RESET);
69962306a36Sopenharmony_ci	hp_sdc.set_im = 1;
70062306a36Sopenharmony_ci	write_unlock_irq(&hp_sdc.hook_lock);
70162306a36Sopenharmony_ci
70262306a36Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
70362306a36Sopenharmony_ci
70462306a36Sopenharmony_ci	return 0;
70562306a36Sopenharmony_ci}
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_ciint hp_sdc_request_cooked_irq(hp_sdc_irqhook *callback)
70862306a36Sopenharmony_ci{
70962306a36Sopenharmony_ci	if (callback == NULL || hp_sdc.dev == NULL)
71062306a36Sopenharmony_ci		return -EINVAL;
71162306a36Sopenharmony_ci
71262306a36Sopenharmony_ci	write_lock_irq(&hp_sdc.hook_lock);
71362306a36Sopenharmony_ci	if (hp_sdc.cooked != NULL) {
71462306a36Sopenharmony_ci		write_unlock_irq(&hp_sdc.hook_lock);
71562306a36Sopenharmony_ci		return -EBUSY;
71662306a36Sopenharmony_ci	}
71762306a36Sopenharmony_ci
71862306a36Sopenharmony_ci	/* Enable interrupts from the HIL MLC */
71962306a36Sopenharmony_ci	hp_sdc.cooked = callback;
72062306a36Sopenharmony_ci	hp_sdc.im &= ~(HP_SDC_IM_HIL | HP_SDC_IM_RESET);
72162306a36Sopenharmony_ci	hp_sdc.set_im = 1;
72262306a36Sopenharmony_ci	write_unlock_irq(&hp_sdc.hook_lock);
72362306a36Sopenharmony_ci
72462306a36Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_ci	return 0;
72762306a36Sopenharmony_ci}
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_ciint hp_sdc_release_timer_irq(hp_sdc_irqhook *callback)
73062306a36Sopenharmony_ci{
73162306a36Sopenharmony_ci	write_lock_irq(&hp_sdc.hook_lock);
73262306a36Sopenharmony_ci	if ((callback != hp_sdc.timer) ||
73362306a36Sopenharmony_ci	    (hp_sdc.timer == NULL)) {
73462306a36Sopenharmony_ci		write_unlock_irq(&hp_sdc.hook_lock);
73562306a36Sopenharmony_ci		return -EINVAL;
73662306a36Sopenharmony_ci	}
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_ci	/* Disable interrupts from the timers */
73962306a36Sopenharmony_ci	hp_sdc.timer = NULL;
74062306a36Sopenharmony_ci	hp_sdc.im |= HP_SDC_IM_TIMERS;
74162306a36Sopenharmony_ci	hp_sdc.im |= HP_SDC_IM_FH;
74262306a36Sopenharmony_ci	hp_sdc.im |= HP_SDC_IM_PT;
74362306a36Sopenharmony_ci	hp_sdc.set_im = 1;
74462306a36Sopenharmony_ci	write_unlock_irq(&hp_sdc.hook_lock);
74562306a36Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
74662306a36Sopenharmony_ci
74762306a36Sopenharmony_ci	return 0;
74862306a36Sopenharmony_ci}
74962306a36Sopenharmony_ci
75062306a36Sopenharmony_ciint hp_sdc_release_hil_irq(hp_sdc_irqhook *callback)
75162306a36Sopenharmony_ci{
75262306a36Sopenharmony_ci	write_lock_irq(&hp_sdc.hook_lock);
75362306a36Sopenharmony_ci	if ((callback != hp_sdc.hil) ||
75462306a36Sopenharmony_ci	    (hp_sdc.hil == NULL)) {
75562306a36Sopenharmony_ci		write_unlock_irq(&hp_sdc.hook_lock);
75662306a36Sopenharmony_ci		return -EINVAL;
75762306a36Sopenharmony_ci	}
75862306a36Sopenharmony_ci
75962306a36Sopenharmony_ci	hp_sdc.hil = NULL;
76062306a36Sopenharmony_ci	/* Disable interrupts from HIL only if there is no cooked driver. */
76162306a36Sopenharmony_ci	if(hp_sdc.cooked == NULL) {
76262306a36Sopenharmony_ci		hp_sdc.im |= (HP_SDC_IM_HIL | HP_SDC_IM_RESET);
76362306a36Sopenharmony_ci		hp_sdc.set_im = 1;
76462306a36Sopenharmony_ci	}
76562306a36Sopenharmony_ci	write_unlock_irq(&hp_sdc.hook_lock);
76662306a36Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
76762306a36Sopenharmony_ci
76862306a36Sopenharmony_ci	return 0;
76962306a36Sopenharmony_ci}
77062306a36Sopenharmony_ci
77162306a36Sopenharmony_ciint hp_sdc_release_cooked_irq(hp_sdc_irqhook *callback)
77262306a36Sopenharmony_ci{
77362306a36Sopenharmony_ci	write_lock_irq(&hp_sdc.hook_lock);
77462306a36Sopenharmony_ci	if ((callback != hp_sdc.cooked) ||
77562306a36Sopenharmony_ci	    (hp_sdc.cooked == NULL)) {
77662306a36Sopenharmony_ci		write_unlock_irq(&hp_sdc.hook_lock);
77762306a36Sopenharmony_ci		return -EINVAL;
77862306a36Sopenharmony_ci	}
77962306a36Sopenharmony_ci
78062306a36Sopenharmony_ci	hp_sdc.cooked = NULL;
78162306a36Sopenharmony_ci	/* Disable interrupts from HIL only if there is no raw HIL driver. */
78262306a36Sopenharmony_ci	if(hp_sdc.hil == NULL) {
78362306a36Sopenharmony_ci		hp_sdc.im |= (HP_SDC_IM_HIL | HP_SDC_IM_RESET);
78462306a36Sopenharmony_ci		hp_sdc.set_im = 1;
78562306a36Sopenharmony_ci	}
78662306a36Sopenharmony_ci	write_unlock_irq(&hp_sdc.hook_lock);
78762306a36Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci	return 0;
79062306a36Sopenharmony_ci}
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci/************************* Keepalive timer task *********************/
79362306a36Sopenharmony_ci
79462306a36Sopenharmony_cistatic void hp_sdc_kicker(struct timer_list *unused)
79562306a36Sopenharmony_ci{
79662306a36Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
79762306a36Sopenharmony_ci	/* Re-insert the periodic task. */
79862306a36Sopenharmony_ci	mod_timer(&hp_sdc.kicker, jiffies + HZ);
79962306a36Sopenharmony_ci}
80062306a36Sopenharmony_ci
80162306a36Sopenharmony_ci/************************** Module Initialization ***************************/
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_ci#if defined(__hppa__)
80462306a36Sopenharmony_ci
80562306a36Sopenharmony_cistatic const struct parisc_device_id hp_sdc_tbl[] __initconst = {
80662306a36Sopenharmony_ci	{
80762306a36Sopenharmony_ci		.hw_type =	HPHW_FIO,
80862306a36Sopenharmony_ci		.hversion_rev =	HVERSION_REV_ANY_ID,
80962306a36Sopenharmony_ci		.hversion =	HVERSION_ANY_ID,
81062306a36Sopenharmony_ci		.sversion =	0x73,
81162306a36Sopenharmony_ci	 },
81262306a36Sopenharmony_ci	{ 0, }
81362306a36Sopenharmony_ci};
81462306a36Sopenharmony_ci
81562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(parisc, hp_sdc_tbl);
81662306a36Sopenharmony_ci
81762306a36Sopenharmony_cistatic int __init hp_sdc_init_hppa(struct parisc_device *d);
81862306a36Sopenharmony_cistatic struct delayed_work moduleloader_work;
81962306a36Sopenharmony_ci
82062306a36Sopenharmony_cistatic struct parisc_driver hp_sdc_driver __refdata = {
82162306a36Sopenharmony_ci	.name =		"hp_sdc",
82262306a36Sopenharmony_ci	.id_table =	hp_sdc_tbl,
82362306a36Sopenharmony_ci	.probe =	hp_sdc_init_hppa,
82462306a36Sopenharmony_ci};
82562306a36Sopenharmony_ci
82662306a36Sopenharmony_ci#endif /* __hppa__ */
82762306a36Sopenharmony_ci
82862306a36Sopenharmony_cistatic int __init hp_sdc_init(void)
82962306a36Sopenharmony_ci{
83062306a36Sopenharmony_ci	char *errstr;
83162306a36Sopenharmony_ci	hp_sdc_transaction t_sync;
83262306a36Sopenharmony_ci	uint8_t ts_sync[6];
83362306a36Sopenharmony_ci	struct semaphore s_sync;
83462306a36Sopenharmony_ci
83562306a36Sopenharmony_ci	rwlock_init(&hp_sdc.lock);
83662306a36Sopenharmony_ci	rwlock_init(&hp_sdc.ibf_lock);
83762306a36Sopenharmony_ci	rwlock_init(&hp_sdc.rtq_lock);
83862306a36Sopenharmony_ci	rwlock_init(&hp_sdc.hook_lock);
83962306a36Sopenharmony_ci
84062306a36Sopenharmony_ci	hp_sdc.timer		= NULL;
84162306a36Sopenharmony_ci	hp_sdc.hil		= NULL;
84262306a36Sopenharmony_ci	hp_sdc.pup		= NULL;
84362306a36Sopenharmony_ci	hp_sdc.cooked		= NULL;
84462306a36Sopenharmony_ci	hp_sdc.im		= HP_SDC_IM_MASK;  /* Mask maskable irqs */
84562306a36Sopenharmony_ci	hp_sdc.set_im		= 1;
84662306a36Sopenharmony_ci	hp_sdc.wi		= 0xff;
84762306a36Sopenharmony_ci	hp_sdc.r7[0]		= 0xff;
84862306a36Sopenharmony_ci	hp_sdc.r7[1]		= 0xff;
84962306a36Sopenharmony_ci	hp_sdc.r7[2]		= 0xff;
85062306a36Sopenharmony_ci	hp_sdc.r7[3]		= 0xff;
85162306a36Sopenharmony_ci	hp_sdc.ibf		= 1;
85262306a36Sopenharmony_ci
85362306a36Sopenharmony_ci	memset(&hp_sdc.tq, 0, sizeof(hp_sdc.tq));
85462306a36Sopenharmony_ci
85562306a36Sopenharmony_ci	hp_sdc.wcurr		= -1;
85662306a36Sopenharmony_ci        hp_sdc.rcurr		= -1;
85762306a36Sopenharmony_ci	hp_sdc.rqty		= 0;
85862306a36Sopenharmony_ci
85962306a36Sopenharmony_ci	hp_sdc.dev_err = -ENODEV;
86062306a36Sopenharmony_ci
86162306a36Sopenharmony_ci	errstr = "IO not found for";
86262306a36Sopenharmony_ci	if (!hp_sdc.base_io)
86362306a36Sopenharmony_ci		goto err0;
86462306a36Sopenharmony_ci
86562306a36Sopenharmony_ci	errstr = "IRQ not found for";
86662306a36Sopenharmony_ci	if (!hp_sdc.irq)
86762306a36Sopenharmony_ci		goto err0;
86862306a36Sopenharmony_ci
86962306a36Sopenharmony_ci	hp_sdc.dev_err = -EBUSY;
87062306a36Sopenharmony_ci
87162306a36Sopenharmony_ci#if defined(__hppa__)
87262306a36Sopenharmony_ci	errstr = "IO not available for";
87362306a36Sopenharmony_ci        if (request_region(hp_sdc.data_io, 2, hp_sdc_driver.name))
87462306a36Sopenharmony_ci		goto err0;
87562306a36Sopenharmony_ci#endif
87662306a36Sopenharmony_ci
87762306a36Sopenharmony_ci	errstr = "IRQ not available for";
87862306a36Sopenharmony_ci	if (request_irq(hp_sdc.irq, &hp_sdc_isr, IRQF_SHARED,
87962306a36Sopenharmony_ci			"HP SDC", &hp_sdc))
88062306a36Sopenharmony_ci		goto err1;
88162306a36Sopenharmony_ci
88262306a36Sopenharmony_ci	errstr = "NMI not available for";
88362306a36Sopenharmony_ci	if (request_irq(hp_sdc.nmi, &hp_sdc_nmisr, IRQF_SHARED,
88462306a36Sopenharmony_ci			"HP SDC NMI", &hp_sdc))
88562306a36Sopenharmony_ci		goto err2;
88662306a36Sopenharmony_ci
88762306a36Sopenharmony_ci	pr_info(PREFIX "HP SDC at 0x%08lx, IRQ %d (NMI IRQ %d)\n",
88862306a36Sopenharmony_ci	       hp_sdc.base_io, hp_sdc.irq, hp_sdc.nmi);
88962306a36Sopenharmony_ci
89062306a36Sopenharmony_ci	hp_sdc_status_in8();
89162306a36Sopenharmony_ci	hp_sdc_data_in8();
89262306a36Sopenharmony_ci
89362306a36Sopenharmony_ci	tasklet_init(&hp_sdc.task, hp_sdc_tasklet, 0);
89462306a36Sopenharmony_ci
89562306a36Sopenharmony_ci	/* Sync the output buffer registers, thus scheduling hp_sdc_tasklet. */
89662306a36Sopenharmony_ci	t_sync.actidx	= 0;
89762306a36Sopenharmony_ci	t_sync.idx	= 1;
89862306a36Sopenharmony_ci	t_sync.endidx	= 6;
89962306a36Sopenharmony_ci	t_sync.seq	= ts_sync;
90062306a36Sopenharmony_ci	ts_sync[0]	= HP_SDC_ACT_DATAREG | HP_SDC_ACT_SEMAPHORE;
90162306a36Sopenharmony_ci	ts_sync[1]	= 0x0f;
90262306a36Sopenharmony_ci	ts_sync[2] = ts_sync[3]	= ts_sync[4] = ts_sync[5] = 0;
90362306a36Sopenharmony_ci	t_sync.act.semaphore = &s_sync;
90462306a36Sopenharmony_ci	sema_init(&s_sync, 0);
90562306a36Sopenharmony_ci	hp_sdc_enqueue_transaction(&t_sync);
90662306a36Sopenharmony_ci	down(&s_sync); /* Wait for t_sync to complete */
90762306a36Sopenharmony_ci
90862306a36Sopenharmony_ci	/* Create the keepalive task */
90962306a36Sopenharmony_ci	timer_setup(&hp_sdc.kicker, hp_sdc_kicker, 0);
91062306a36Sopenharmony_ci	hp_sdc.kicker.expires = jiffies + HZ;
91162306a36Sopenharmony_ci	add_timer(&hp_sdc.kicker);
91262306a36Sopenharmony_ci
91362306a36Sopenharmony_ci	hp_sdc.dev_err = 0;
91462306a36Sopenharmony_ci	return 0;
91562306a36Sopenharmony_ci err2:
91662306a36Sopenharmony_ci	free_irq(hp_sdc.irq, &hp_sdc);
91762306a36Sopenharmony_ci err1:
91862306a36Sopenharmony_ci	release_region(hp_sdc.data_io, 2);
91962306a36Sopenharmony_ci err0:
92062306a36Sopenharmony_ci	printk(KERN_WARNING PREFIX ": %s SDC IO=0x%p IRQ=0x%x NMI=0x%x\n",
92162306a36Sopenharmony_ci		errstr, (void *)hp_sdc.base_io, hp_sdc.irq, hp_sdc.nmi);
92262306a36Sopenharmony_ci	hp_sdc.dev = NULL;
92362306a36Sopenharmony_ci
92462306a36Sopenharmony_ci	return hp_sdc.dev_err;
92562306a36Sopenharmony_ci}
92662306a36Sopenharmony_ci
92762306a36Sopenharmony_ci#if defined(__hppa__)
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_cistatic void request_module_delayed(struct work_struct *work)
93062306a36Sopenharmony_ci{
93162306a36Sopenharmony_ci	request_module("hp_sdc_mlc");
93262306a36Sopenharmony_ci}
93362306a36Sopenharmony_ci
93462306a36Sopenharmony_cistatic int __init hp_sdc_init_hppa(struct parisc_device *d)
93562306a36Sopenharmony_ci{
93662306a36Sopenharmony_ci	int ret;
93762306a36Sopenharmony_ci
93862306a36Sopenharmony_ci	if (!d)
93962306a36Sopenharmony_ci		return 1;
94062306a36Sopenharmony_ci	if (hp_sdc.dev != NULL)
94162306a36Sopenharmony_ci		return 1;	/* We only expect one SDC */
94262306a36Sopenharmony_ci
94362306a36Sopenharmony_ci	hp_sdc.dev		= d;
94462306a36Sopenharmony_ci	hp_sdc.irq		= d->irq;
94562306a36Sopenharmony_ci	hp_sdc.nmi		= d->aux_irq;
94662306a36Sopenharmony_ci	hp_sdc.base_io		= d->hpa.start;
94762306a36Sopenharmony_ci	hp_sdc.data_io		= d->hpa.start + 0x800;
94862306a36Sopenharmony_ci	hp_sdc.status_io	= d->hpa.start + 0x801;
94962306a36Sopenharmony_ci
95062306a36Sopenharmony_ci	INIT_DELAYED_WORK(&moduleloader_work, request_module_delayed);
95162306a36Sopenharmony_ci
95262306a36Sopenharmony_ci	ret = hp_sdc_init();
95362306a36Sopenharmony_ci	/* after successful initialization give SDC some time to settle
95462306a36Sopenharmony_ci	 * and then load the hp_sdc_mlc upper layer driver */
95562306a36Sopenharmony_ci	if (!ret)
95662306a36Sopenharmony_ci		schedule_delayed_work(&moduleloader_work,
95762306a36Sopenharmony_ci			msecs_to_jiffies(2000));
95862306a36Sopenharmony_ci
95962306a36Sopenharmony_ci	return ret;
96062306a36Sopenharmony_ci}
96162306a36Sopenharmony_ci
96262306a36Sopenharmony_ci#endif /* __hppa__ */
96362306a36Sopenharmony_ci
96462306a36Sopenharmony_cistatic void hp_sdc_exit(void)
96562306a36Sopenharmony_ci{
96662306a36Sopenharmony_ci	/* do nothing if we don't have a SDC */
96762306a36Sopenharmony_ci	if (!hp_sdc.dev)
96862306a36Sopenharmony_ci		return;
96962306a36Sopenharmony_ci
97062306a36Sopenharmony_ci	write_lock_irq(&hp_sdc.lock);
97162306a36Sopenharmony_ci
97262306a36Sopenharmony_ci	/* Turn off all maskable "sub-function" irq's. */
97362306a36Sopenharmony_ci	hp_sdc_spin_ibf();
97462306a36Sopenharmony_ci	sdc_writeb(HP_SDC_CMD_SET_IM | HP_SDC_IM_MASK, hp_sdc.status_io);
97562306a36Sopenharmony_ci
97662306a36Sopenharmony_ci	/* Wait until we know this has been processed by the i8042 */
97762306a36Sopenharmony_ci	hp_sdc_spin_ibf();
97862306a36Sopenharmony_ci
97962306a36Sopenharmony_ci	free_irq(hp_sdc.nmi, &hp_sdc);
98062306a36Sopenharmony_ci	free_irq(hp_sdc.irq, &hp_sdc);
98162306a36Sopenharmony_ci	write_unlock_irq(&hp_sdc.lock);
98262306a36Sopenharmony_ci
98362306a36Sopenharmony_ci	del_timer_sync(&hp_sdc.kicker);
98462306a36Sopenharmony_ci
98562306a36Sopenharmony_ci	tasklet_kill(&hp_sdc.task);
98662306a36Sopenharmony_ci
98762306a36Sopenharmony_ci#if defined(__hppa__)
98862306a36Sopenharmony_ci	cancel_delayed_work_sync(&moduleloader_work);
98962306a36Sopenharmony_ci	if (unregister_parisc_driver(&hp_sdc_driver))
99062306a36Sopenharmony_ci		printk(KERN_WARNING PREFIX "Error unregistering HP SDC");
99162306a36Sopenharmony_ci#endif
99262306a36Sopenharmony_ci}
99362306a36Sopenharmony_ci
99462306a36Sopenharmony_cistatic int __init hp_sdc_register(void)
99562306a36Sopenharmony_ci{
99662306a36Sopenharmony_ci	hp_sdc_transaction tq_init;
99762306a36Sopenharmony_ci	uint8_t tq_init_seq[5];
99862306a36Sopenharmony_ci	struct semaphore tq_init_sem;
99962306a36Sopenharmony_ci#if defined(__mc68000__)
100062306a36Sopenharmony_ci	unsigned char i;
100162306a36Sopenharmony_ci#endif
100262306a36Sopenharmony_ci
100362306a36Sopenharmony_ci	if (hp_sdc_disabled) {
100462306a36Sopenharmony_ci		printk(KERN_WARNING PREFIX "HP SDC driver disabled by no_hpsdc=1.\n");
100562306a36Sopenharmony_ci		return -ENODEV;
100662306a36Sopenharmony_ci	}
100762306a36Sopenharmony_ci
100862306a36Sopenharmony_ci	hp_sdc.dev = NULL;
100962306a36Sopenharmony_ci	hp_sdc.dev_err = 0;
101062306a36Sopenharmony_ci#if defined(__hppa__)
101162306a36Sopenharmony_ci	if (register_parisc_driver(&hp_sdc_driver)) {
101262306a36Sopenharmony_ci		printk(KERN_WARNING PREFIX "Error registering SDC with system bus tree.\n");
101362306a36Sopenharmony_ci		return -ENODEV;
101462306a36Sopenharmony_ci	}
101562306a36Sopenharmony_ci#elif defined(__mc68000__)
101662306a36Sopenharmony_ci	if (!MACH_IS_HP300)
101762306a36Sopenharmony_ci	    return -ENODEV;
101862306a36Sopenharmony_ci
101962306a36Sopenharmony_ci	hp_sdc.irq	 = 1;
102062306a36Sopenharmony_ci	hp_sdc.nmi	 = 7;
102162306a36Sopenharmony_ci	hp_sdc.base_io	 = (unsigned long) 0xf0428000;
102262306a36Sopenharmony_ci	hp_sdc.data_io	 = (unsigned long) hp_sdc.base_io + 1;
102362306a36Sopenharmony_ci	hp_sdc.status_io = (unsigned long) hp_sdc.base_io + 3;
102462306a36Sopenharmony_ci	if (!copy_from_kernel_nofault(&i, (unsigned char *)hp_sdc.data_io, 1))
102562306a36Sopenharmony_ci		hp_sdc.dev = (void *)1;
102662306a36Sopenharmony_ci	hp_sdc.dev_err   = hp_sdc_init();
102762306a36Sopenharmony_ci#endif
102862306a36Sopenharmony_ci	if (hp_sdc.dev == NULL) {
102962306a36Sopenharmony_ci		printk(KERN_WARNING PREFIX "No SDC found.\n");
103062306a36Sopenharmony_ci		return hp_sdc.dev_err;
103162306a36Sopenharmony_ci	}
103262306a36Sopenharmony_ci
103362306a36Sopenharmony_ci	sema_init(&tq_init_sem, 0);
103462306a36Sopenharmony_ci
103562306a36Sopenharmony_ci	tq_init.actidx		= 0;
103662306a36Sopenharmony_ci	tq_init.idx		= 1;
103762306a36Sopenharmony_ci	tq_init.endidx		= 5;
103862306a36Sopenharmony_ci	tq_init.seq		= tq_init_seq;
103962306a36Sopenharmony_ci	tq_init.act.semaphore	= &tq_init_sem;
104062306a36Sopenharmony_ci
104162306a36Sopenharmony_ci	tq_init_seq[0] =
104262306a36Sopenharmony_ci		HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN | HP_SDC_ACT_SEMAPHORE;
104362306a36Sopenharmony_ci	tq_init_seq[1] = HP_SDC_CMD_READ_KCC;
104462306a36Sopenharmony_ci	tq_init_seq[2] = 1;
104562306a36Sopenharmony_ci	tq_init_seq[3] = 0;
104662306a36Sopenharmony_ci	tq_init_seq[4] = 0;
104762306a36Sopenharmony_ci
104862306a36Sopenharmony_ci	hp_sdc_enqueue_transaction(&tq_init);
104962306a36Sopenharmony_ci
105062306a36Sopenharmony_ci	down(&tq_init_sem);
105162306a36Sopenharmony_ci	up(&tq_init_sem);
105262306a36Sopenharmony_ci
105362306a36Sopenharmony_ci	if ((tq_init_seq[0] & HP_SDC_ACT_DEAD) == HP_SDC_ACT_DEAD) {
105462306a36Sopenharmony_ci		printk(KERN_WARNING PREFIX "Error reading config byte.\n");
105562306a36Sopenharmony_ci		hp_sdc_exit();
105662306a36Sopenharmony_ci		return -ENODEV;
105762306a36Sopenharmony_ci	}
105862306a36Sopenharmony_ci	hp_sdc.r11 = tq_init_seq[4];
105962306a36Sopenharmony_ci	if (hp_sdc.r11 & HP_SDC_CFG_NEW) {
106062306a36Sopenharmony_ci		const char *str;
106162306a36Sopenharmony_ci		printk(KERN_INFO PREFIX "New style SDC\n");
106262306a36Sopenharmony_ci		tq_init_seq[1] = HP_SDC_CMD_READ_XTD;
106362306a36Sopenharmony_ci		tq_init.actidx		= 0;
106462306a36Sopenharmony_ci		tq_init.idx		= 1;
106562306a36Sopenharmony_ci		down(&tq_init_sem);
106662306a36Sopenharmony_ci		hp_sdc_enqueue_transaction(&tq_init);
106762306a36Sopenharmony_ci		down(&tq_init_sem);
106862306a36Sopenharmony_ci		up(&tq_init_sem);
106962306a36Sopenharmony_ci		if ((tq_init_seq[0] & HP_SDC_ACT_DEAD) == HP_SDC_ACT_DEAD) {
107062306a36Sopenharmony_ci			printk(KERN_WARNING PREFIX "Error reading extended config byte.\n");
107162306a36Sopenharmony_ci			return -ENODEV;
107262306a36Sopenharmony_ci		}
107362306a36Sopenharmony_ci		hp_sdc.r7e = tq_init_seq[4];
107462306a36Sopenharmony_ci		HP_SDC_XTD_REV_STRINGS(hp_sdc.r7e & HP_SDC_XTD_REV, str)
107562306a36Sopenharmony_ci		printk(KERN_INFO PREFIX "Revision: %s\n", str);
107662306a36Sopenharmony_ci		if (hp_sdc.r7e & HP_SDC_XTD_BEEPER)
107762306a36Sopenharmony_ci			printk(KERN_INFO PREFIX "TI SN76494 beeper present\n");
107862306a36Sopenharmony_ci		if (hp_sdc.r7e & HP_SDC_XTD_BBRTC)
107962306a36Sopenharmony_ci			printk(KERN_INFO PREFIX "OKI MSM-58321 BBRTC present\n");
108062306a36Sopenharmony_ci		printk(KERN_INFO PREFIX "Spunking the self test register to force PUP "
108162306a36Sopenharmony_ci		       "on next firmware reset.\n");
108262306a36Sopenharmony_ci		tq_init_seq[0] = HP_SDC_ACT_PRECMD |
108362306a36Sopenharmony_ci			HP_SDC_ACT_DATAOUT | HP_SDC_ACT_SEMAPHORE;
108462306a36Sopenharmony_ci		tq_init_seq[1] = HP_SDC_CMD_SET_STR;
108562306a36Sopenharmony_ci		tq_init_seq[2] = 1;
108662306a36Sopenharmony_ci		tq_init_seq[3] = 0;
108762306a36Sopenharmony_ci		tq_init.actidx		= 0;
108862306a36Sopenharmony_ci		tq_init.idx		= 1;
108962306a36Sopenharmony_ci		tq_init.endidx		= 4;
109062306a36Sopenharmony_ci		down(&tq_init_sem);
109162306a36Sopenharmony_ci		hp_sdc_enqueue_transaction(&tq_init);
109262306a36Sopenharmony_ci		down(&tq_init_sem);
109362306a36Sopenharmony_ci		up(&tq_init_sem);
109462306a36Sopenharmony_ci	} else
109562306a36Sopenharmony_ci		printk(KERN_INFO PREFIX "Old style SDC (1820-%s).\n",
109662306a36Sopenharmony_ci		       (hp_sdc.r11 & HP_SDC_CFG_REV) ? "3300" : "2564/3087");
109762306a36Sopenharmony_ci
109862306a36Sopenharmony_ci        return 0;
109962306a36Sopenharmony_ci}
110062306a36Sopenharmony_ci
110162306a36Sopenharmony_cimodule_init(hp_sdc_register);
110262306a36Sopenharmony_cimodule_exit(hp_sdc_exit);
110362306a36Sopenharmony_ci
110462306a36Sopenharmony_ci/* Timing notes:  These measurements taken on my 64MHz 7100-LC (715/64)
110562306a36Sopenharmony_ci *                                              cycles cycles-adj    time
110662306a36Sopenharmony_ci * between two consecutive mfctl(16)'s:              4        n/a    63ns
110762306a36Sopenharmony_ci * hp_sdc_spin_ibf when idle:                      119        115   1.7us
110862306a36Sopenharmony_ci * gsc_writeb status register:                      83         79   1.2us
110962306a36Sopenharmony_ci * IBF to clear after sending SET_IM:             6204       6006    93us
111062306a36Sopenharmony_ci * IBF to clear after sending LOAD_RT:            4467       4352    68us
111162306a36Sopenharmony_ci * IBF to clear after sending two LOAD_RTs:      18974      18859   295us
111262306a36Sopenharmony_ci * READ_T1, read status/data, IRQ, call handler: 35564        n/a   556us
111362306a36Sopenharmony_ci * cmd to ~IBF READ_T1 2nd time right after:   5158403        n/a    81ms
111462306a36Sopenharmony_ci * between IRQ received and ~IBF for above:    2578877        n/a    40ms
111562306a36Sopenharmony_ci *
111662306a36Sopenharmony_ci * Performance stats after a run of this module configuring HIL and
111762306a36Sopenharmony_ci * receiving a few mouse events:
111862306a36Sopenharmony_ci *
111962306a36Sopenharmony_ci * status in8  282508 cycles 7128 calls
112062306a36Sopenharmony_ci * status out8   8404 cycles  341 calls
112162306a36Sopenharmony_ci * data out8     1734 cycles   78 calls
112262306a36Sopenharmony_ci * isr         174324 cycles  617 calls (includes take)
112362306a36Sopenharmony_ci * take          1241 cycles    2 calls
112462306a36Sopenharmony_ci * put        1411504 cycles 6937 calls
112562306a36Sopenharmony_ci * task       1655209 cycles 6937 calls (includes put)
112662306a36Sopenharmony_ci *
112762306a36Sopenharmony_ci */
1128