18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * HP i8042-based System Device Controller driver.
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (c) 2001 Brian S. Julin
58c2ecf20Sopenharmony_ci * All rights reserved.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Redistribution and use in source and binary forms, with or without
88c2ecf20Sopenharmony_ci * modification, are permitted provided that the following conditions
98c2ecf20Sopenharmony_ci * are met:
108c2ecf20Sopenharmony_ci * 1. Redistributions of source code must retain the above copyright
118c2ecf20Sopenharmony_ci *    notice, this list of conditions, and the following disclaimer,
128c2ecf20Sopenharmony_ci *    without modification.
138c2ecf20Sopenharmony_ci * 2. The name of the author may not be used to endorse or promote products
148c2ecf20Sopenharmony_ci *    derived from this software without specific prior written permission.
158c2ecf20Sopenharmony_ci *
168c2ecf20Sopenharmony_ci * Alternatively, this software may be distributed under the terms of the
178c2ecf20Sopenharmony_ci * GNU General Public License ("GPL").
188c2ecf20Sopenharmony_ci *
198c2ecf20Sopenharmony_ci * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
208c2ecf20Sopenharmony_ci * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
218c2ecf20Sopenharmony_ci * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
228c2ecf20Sopenharmony_ci * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
238c2ecf20Sopenharmony_ci * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
248c2ecf20Sopenharmony_ci * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
258c2ecf20Sopenharmony_ci * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
268c2ecf20Sopenharmony_ci * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
278c2ecf20Sopenharmony_ci * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
288c2ecf20Sopenharmony_ci *
298c2ecf20Sopenharmony_ci * References:
308c2ecf20Sopenharmony_ci * System Device Controller Microprocessor Firmware Theory of Operation
318c2ecf20Sopenharmony_ci *      for Part Number 1820-4784 Revision B.  Dwg No. A-1820-4784-2
328c2ecf20Sopenharmony_ci * Helge Deller's original hilkbd.c port for PA-RISC.
338c2ecf20Sopenharmony_ci *
348c2ecf20Sopenharmony_ci *
358c2ecf20Sopenharmony_ci * Driver theory of operation:
368c2ecf20Sopenharmony_ci *
378c2ecf20Sopenharmony_ci * hp_sdc_put does all writing to the SDC.  ISR can run on a different
388c2ecf20Sopenharmony_ci * CPU than hp_sdc_put, but only one CPU runs hp_sdc_put at a time
398c2ecf20Sopenharmony_ci * (it cannot really benefit from SMP anyway.)  A tasket fit this perfectly.
408c2ecf20Sopenharmony_ci *
418c2ecf20Sopenharmony_ci * All data coming back from the SDC is sent via interrupt and can be read
428c2ecf20Sopenharmony_ci * fully in the ISR, so there are no latency/throughput problems there.
438c2ecf20Sopenharmony_ci * The problem is with output, due to the slow clock speed of the SDC
448c2ecf20Sopenharmony_ci * compared to the CPU.  This should not be too horrible most of the time,
458c2ecf20Sopenharmony_ci * but if used with HIL devices that support the multibyte transfer command,
468c2ecf20Sopenharmony_ci * keeping outbound throughput flowing at the 6500KBps that the HIL is
478c2ecf20Sopenharmony_ci * capable of is more than can be done at HZ=100.
488c2ecf20Sopenharmony_ci *
498c2ecf20Sopenharmony_ci * Busy polling for IBF clear wastes CPU cycles and bus cycles.  hp_sdc.ibf
508c2ecf20Sopenharmony_ci * is set to 0 when the IBF flag in the status register has cleared.  ISR
518c2ecf20Sopenharmony_ci * may do this, and may also access the parts of queued transactions related
528c2ecf20Sopenharmony_ci * to reading data back from the SDC, but otherwise will not touch the
538c2ecf20Sopenharmony_ci * hp_sdc state. Whenever a register is written hp_sdc.ibf is set to 1.
548c2ecf20Sopenharmony_ci *
558c2ecf20Sopenharmony_ci * The i8042 write index and the values in the 4-byte input buffer
568c2ecf20Sopenharmony_ci * starting at 0x70 are kept track of in hp_sdc.wi, and .r7[], respectively,
578c2ecf20Sopenharmony_ci * to minimize the amount of IO needed to the SDC.  However these values
588c2ecf20Sopenharmony_ci * do not need to be locked since they are only ever accessed by hp_sdc_put.
598c2ecf20Sopenharmony_ci *
608c2ecf20Sopenharmony_ci * A timer task schedules the tasklet once per second just to make
618c2ecf20Sopenharmony_ci * sure it doesn't freeze up and to allow for bad reads to time out.
628c2ecf20Sopenharmony_ci */
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci#include <linux/hp_sdc.h>
658c2ecf20Sopenharmony_ci#include <linux/errno.h>
668c2ecf20Sopenharmony_ci#include <linux/init.h>
678c2ecf20Sopenharmony_ci#include <linux/module.h>
688c2ecf20Sopenharmony_ci#include <linux/ioport.h>
698c2ecf20Sopenharmony_ci#include <linux/time.h>
708c2ecf20Sopenharmony_ci#include <linux/semaphore.h>
718c2ecf20Sopenharmony_ci#include <linux/slab.h>
728c2ecf20Sopenharmony_ci#include <linux/hil.h>
738c2ecf20Sopenharmony_ci#include <asm/io.h>
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci/* Machine-specific abstraction */
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci#if defined(__hppa__)
788c2ecf20Sopenharmony_ci# include <asm/parisc-device.h>
798c2ecf20Sopenharmony_ci# define sdc_readb(p)		gsc_readb(p)
808c2ecf20Sopenharmony_ci# define sdc_writeb(v,p)	gsc_writeb((v),(p))
818c2ecf20Sopenharmony_ci#elif defined(__mc68000__)
828c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
838c2ecf20Sopenharmony_ci# define sdc_readb(p)		in_8(p)
848c2ecf20Sopenharmony_ci# define sdc_writeb(v,p)	out_8((p),(v))
858c2ecf20Sopenharmony_ci#else
868c2ecf20Sopenharmony_ci# error "HIL is not supported on this platform"
878c2ecf20Sopenharmony_ci#endif
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci#define PREFIX "HP SDC: "
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ciMODULE_AUTHOR("Brian S. Julin <bri@calyx.com>");
928c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("HP i8042-based SDC Driver");
938c2ecf20Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL");
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_request_timer_irq);
968c2ecf20Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_request_hil_irq);
978c2ecf20Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_request_cooked_irq);
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_release_timer_irq);
1008c2ecf20Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_release_hil_irq);
1018c2ecf20Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_release_cooked_irq);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ciEXPORT_SYMBOL(__hp_sdc_enqueue_transaction);
1048c2ecf20Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_enqueue_transaction);
1058c2ecf20Sopenharmony_ciEXPORT_SYMBOL(hp_sdc_dequeue_transaction);
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_cistatic bool hp_sdc_disabled;
1088c2ecf20Sopenharmony_cimodule_param_named(no_hpsdc, hp_sdc_disabled, bool, 0);
1098c2ecf20Sopenharmony_ciMODULE_PARM_DESC(no_hpsdc, "Do not enable HP SDC driver.");
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_cistatic hp_i8042_sdc	hp_sdc;	/* All driver state is kept in here. */
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci/*************** primitives for use in any context *********************/
1148c2ecf20Sopenharmony_cistatic inline uint8_t hp_sdc_status_in8(void)
1158c2ecf20Sopenharmony_ci{
1168c2ecf20Sopenharmony_ci	uint8_t status;
1178c2ecf20Sopenharmony_ci	unsigned long flags;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	write_lock_irqsave(&hp_sdc.ibf_lock, flags);
1208c2ecf20Sopenharmony_ci	status = sdc_readb(hp_sdc.status_io);
1218c2ecf20Sopenharmony_ci	if (!(status & HP_SDC_STATUS_IBF))
1228c2ecf20Sopenharmony_ci		hp_sdc.ibf = 0;
1238c2ecf20Sopenharmony_ci	write_unlock_irqrestore(&hp_sdc.ibf_lock, flags);
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	return status;
1268c2ecf20Sopenharmony_ci}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_cistatic inline uint8_t hp_sdc_data_in8(void)
1298c2ecf20Sopenharmony_ci{
1308c2ecf20Sopenharmony_ci	return sdc_readb(hp_sdc.data_io);
1318c2ecf20Sopenharmony_ci}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_cistatic inline void hp_sdc_status_out8(uint8_t val)
1348c2ecf20Sopenharmony_ci{
1358c2ecf20Sopenharmony_ci	unsigned long flags;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	write_lock_irqsave(&hp_sdc.ibf_lock, flags);
1388c2ecf20Sopenharmony_ci	hp_sdc.ibf = 1;
1398c2ecf20Sopenharmony_ci	if ((val & 0xf0) == 0xe0)
1408c2ecf20Sopenharmony_ci		hp_sdc.wi = 0xff;
1418c2ecf20Sopenharmony_ci	sdc_writeb(val, hp_sdc.status_io);
1428c2ecf20Sopenharmony_ci	write_unlock_irqrestore(&hp_sdc.ibf_lock, flags);
1438c2ecf20Sopenharmony_ci}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic inline void hp_sdc_data_out8(uint8_t val)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	unsigned long flags;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	write_lock_irqsave(&hp_sdc.ibf_lock, flags);
1508c2ecf20Sopenharmony_ci	hp_sdc.ibf = 1;
1518c2ecf20Sopenharmony_ci	sdc_writeb(val, hp_sdc.data_io);
1528c2ecf20Sopenharmony_ci	write_unlock_irqrestore(&hp_sdc.ibf_lock, flags);
1538c2ecf20Sopenharmony_ci}
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci/*	Care must be taken to only invoke hp_sdc_spin_ibf when
1568c2ecf20Sopenharmony_ci *	absolutely needed, or in rarely invoked subroutines.
1578c2ecf20Sopenharmony_ci *	Not only does it waste CPU cycles, it also wastes bus cycles.
1588c2ecf20Sopenharmony_ci */
1598c2ecf20Sopenharmony_cistatic inline void hp_sdc_spin_ibf(void)
1608c2ecf20Sopenharmony_ci{
1618c2ecf20Sopenharmony_ci	unsigned long flags;
1628c2ecf20Sopenharmony_ci	rwlock_t *lock;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	lock = &hp_sdc.ibf_lock;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	read_lock_irqsave(lock, flags);
1678c2ecf20Sopenharmony_ci	if (!hp_sdc.ibf) {
1688c2ecf20Sopenharmony_ci		read_unlock_irqrestore(lock, flags);
1698c2ecf20Sopenharmony_ci		return;
1708c2ecf20Sopenharmony_ci	}
1718c2ecf20Sopenharmony_ci	read_unlock(lock);
1728c2ecf20Sopenharmony_ci	write_lock(lock);
1738c2ecf20Sopenharmony_ci	while (sdc_readb(hp_sdc.status_io) & HP_SDC_STATUS_IBF)
1748c2ecf20Sopenharmony_ci		{ }
1758c2ecf20Sopenharmony_ci	hp_sdc.ibf = 0;
1768c2ecf20Sopenharmony_ci	write_unlock_irqrestore(lock, flags);
1778c2ecf20Sopenharmony_ci}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci/************************ Interrupt context functions ************************/
1818c2ecf20Sopenharmony_cistatic void hp_sdc_take(int irq, void *dev_id, uint8_t status, uint8_t data)
1828c2ecf20Sopenharmony_ci{
1838c2ecf20Sopenharmony_ci	hp_sdc_transaction *curr;
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	read_lock(&hp_sdc.rtq_lock);
1868c2ecf20Sopenharmony_ci	if (hp_sdc.rcurr < 0) {
1878c2ecf20Sopenharmony_ci		read_unlock(&hp_sdc.rtq_lock);
1888c2ecf20Sopenharmony_ci		return;
1898c2ecf20Sopenharmony_ci	}
1908c2ecf20Sopenharmony_ci	curr = hp_sdc.tq[hp_sdc.rcurr];
1918c2ecf20Sopenharmony_ci	read_unlock(&hp_sdc.rtq_lock);
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	curr->seq[curr->idx++] = status;
1948c2ecf20Sopenharmony_ci	curr->seq[curr->idx++] = data;
1958c2ecf20Sopenharmony_ci	hp_sdc.rqty -= 2;
1968c2ecf20Sopenharmony_ci	hp_sdc.rtime = ktime_get();
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	if (hp_sdc.rqty <= 0) {
1998c2ecf20Sopenharmony_ci		/* All data has been gathered. */
2008c2ecf20Sopenharmony_ci		if (curr->seq[curr->actidx] & HP_SDC_ACT_SEMAPHORE)
2018c2ecf20Sopenharmony_ci			if (curr->act.semaphore)
2028c2ecf20Sopenharmony_ci				up(curr->act.semaphore);
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci		if (curr->seq[curr->actidx] & HP_SDC_ACT_CALLBACK)
2058c2ecf20Sopenharmony_ci			if (curr->act.irqhook)
2068c2ecf20Sopenharmony_ci				curr->act.irqhook(irq, dev_id, status, data);
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci		curr->actidx = curr->idx;
2098c2ecf20Sopenharmony_ci		curr->idx++;
2108c2ecf20Sopenharmony_ci		/* Return control of this transaction */
2118c2ecf20Sopenharmony_ci		write_lock(&hp_sdc.rtq_lock);
2128c2ecf20Sopenharmony_ci		hp_sdc.rcurr = -1;
2138c2ecf20Sopenharmony_ci		hp_sdc.rqty = 0;
2148c2ecf20Sopenharmony_ci		write_unlock(&hp_sdc.rtq_lock);
2158c2ecf20Sopenharmony_ci		tasklet_schedule(&hp_sdc.task);
2168c2ecf20Sopenharmony_ci	}
2178c2ecf20Sopenharmony_ci}
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_cistatic irqreturn_t hp_sdc_isr(int irq, void *dev_id)
2208c2ecf20Sopenharmony_ci{
2218c2ecf20Sopenharmony_ci	uint8_t status, data;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	status = hp_sdc_status_in8();
2248c2ecf20Sopenharmony_ci	/* Read data unconditionally to advance i8042. */
2258c2ecf20Sopenharmony_ci	data =   hp_sdc_data_in8();
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	/* For now we are ignoring these until we get the SDC to behave. */
2288c2ecf20Sopenharmony_ci	if (((status & 0xf1) == 0x51) && data == 0x82)
2298c2ecf20Sopenharmony_ci		return IRQ_HANDLED;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	switch (status & HP_SDC_STATUS_IRQMASK) {
2328c2ecf20Sopenharmony_ci	case 0: /* This case is not documented. */
2338c2ecf20Sopenharmony_ci		break;
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	case HP_SDC_STATUS_USERTIMER:
2368c2ecf20Sopenharmony_ci	case HP_SDC_STATUS_PERIODIC:
2378c2ecf20Sopenharmony_ci	case HP_SDC_STATUS_TIMER:
2388c2ecf20Sopenharmony_ci		read_lock(&hp_sdc.hook_lock);
2398c2ecf20Sopenharmony_ci		if (hp_sdc.timer != NULL)
2408c2ecf20Sopenharmony_ci			hp_sdc.timer(irq, dev_id, status, data);
2418c2ecf20Sopenharmony_ci		read_unlock(&hp_sdc.hook_lock);
2428c2ecf20Sopenharmony_ci		break;
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	case HP_SDC_STATUS_REG:
2458c2ecf20Sopenharmony_ci		hp_sdc_take(irq, dev_id, status, data);
2468c2ecf20Sopenharmony_ci		break;
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	case HP_SDC_STATUS_HILCMD:
2498c2ecf20Sopenharmony_ci	case HP_SDC_STATUS_HILDATA:
2508c2ecf20Sopenharmony_ci		read_lock(&hp_sdc.hook_lock);
2518c2ecf20Sopenharmony_ci		if (hp_sdc.hil != NULL)
2528c2ecf20Sopenharmony_ci			hp_sdc.hil(irq, dev_id, status, data);
2538c2ecf20Sopenharmony_ci		read_unlock(&hp_sdc.hook_lock);
2548c2ecf20Sopenharmony_ci		break;
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	case HP_SDC_STATUS_PUP:
2578c2ecf20Sopenharmony_ci		read_lock(&hp_sdc.hook_lock);
2588c2ecf20Sopenharmony_ci		if (hp_sdc.pup != NULL)
2598c2ecf20Sopenharmony_ci			hp_sdc.pup(irq, dev_id, status, data);
2608c2ecf20Sopenharmony_ci		else
2618c2ecf20Sopenharmony_ci			printk(KERN_INFO PREFIX "HP SDC reports successful PUP.\n");
2628c2ecf20Sopenharmony_ci		read_unlock(&hp_sdc.hook_lock);
2638c2ecf20Sopenharmony_ci		break;
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci	default:
2668c2ecf20Sopenharmony_ci		read_lock(&hp_sdc.hook_lock);
2678c2ecf20Sopenharmony_ci		if (hp_sdc.cooked != NULL)
2688c2ecf20Sopenharmony_ci			hp_sdc.cooked(irq, dev_id, status, data);
2698c2ecf20Sopenharmony_ci		read_unlock(&hp_sdc.hook_lock);
2708c2ecf20Sopenharmony_ci		break;
2718c2ecf20Sopenharmony_ci	}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
2748c2ecf20Sopenharmony_ci}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_cistatic irqreturn_t hp_sdc_nmisr(int irq, void *dev_id)
2788c2ecf20Sopenharmony_ci{
2798c2ecf20Sopenharmony_ci	int status;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	status = hp_sdc_status_in8();
2828c2ecf20Sopenharmony_ci	printk(KERN_WARNING PREFIX "NMI !\n");
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci#if 0
2858c2ecf20Sopenharmony_ci	if (status & HP_SDC_NMISTATUS_FHS) {
2868c2ecf20Sopenharmony_ci		read_lock(&hp_sdc.hook_lock);
2878c2ecf20Sopenharmony_ci		if (hp_sdc.timer != NULL)
2888c2ecf20Sopenharmony_ci			hp_sdc.timer(irq, dev_id, status, 0);
2898c2ecf20Sopenharmony_ci		read_unlock(&hp_sdc.hook_lock);
2908c2ecf20Sopenharmony_ci	} else {
2918c2ecf20Sopenharmony_ci		/* TODO: pass this on to the HIL handler, or do SAK here? */
2928c2ecf20Sopenharmony_ci		printk(KERN_WARNING PREFIX "HIL NMI\n");
2938c2ecf20Sopenharmony_ci	}
2948c2ecf20Sopenharmony_ci#endif
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
2978c2ecf20Sopenharmony_ci}
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci/***************** Kernel (tasklet) context functions ****************/
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ciunsigned long hp_sdc_put(void);
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_cistatic void hp_sdc_tasklet(unsigned long foo)
3058c2ecf20Sopenharmony_ci{
3068c2ecf20Sopenharmony_ci	write_lock_irq(&hp_sdc.rtq_lock);
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_ci	if (hp_sdc.rcurr >= 0) {
3098c2ecf20Sopenharmony_ci		ktime_t now = ktime_get();
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ci		if (ktime_after(now, ktime_add_us(hp_sdc.rtime,
3128c2ecf20Sopenharmony_ci						  HP_SDC_MAX_REG_DELAY))) {
3138c2ecf20Sopenharmony_ci			hp_sdc_transaction *curr;
3148c2ecf20Sopenharmony_ci			uint8_t tmp;
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci			curr = hp_sdc.tq[hp_sdc.rcurr];
3178c2ecf20Sopenharmony_ci			/* If this turns out to be a normal failure mode
3188c2ecf20Sopenharmony_ci			 * we'll need to figure out a way to communicate
3198c2ecf20Sopenharmony_ci			 * it back to the application. and be less verbose.
3208c2ecf20Sopenharmony_ci			 */
3218c2ecf20Sopenharmony_ci			printk(KERN_WARNING PREFIX "read timeout (%lldus)!\n",
3228c2ecf20Sopenharmony_ci			       ktime_us_delta(now, hp_sdc.rtime));
3238c2ecf20Sopenharmony_ci			curr->idx += hp_sdc.rqty;
3248c2ecf20Sopenharmony_ci			hp_sdc.rqty = 0;
3258c2ecf20Sopenharmony_ci			tmp = curr->seq[curr->actidx];
3268c2ecf20Sopenharmony_ci			curr->seq[curr->actidx] |= HP_SDC_ACT_DEAD;
3278c2ecf20Sopenharmony_ci			if (tmp & HP_SDC_ACT_SEMAPHORE)
3288c2ecf20Sopenharmony_ci				if (curr->act.semaphore)
3298c2ecf20Sopenharmony_ci					up(curr->act.semaphore);
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci			if (tmp & HP_SDC_ACT_CALLBACK) {
3328c2ecf20Sopenharmony_ci				/* Note this means that irqhooks may be called
3338c2ecf20Sopenharmony_ci				 * in tasklet/bh context.
3348c2ecf20Sopenharmony_ci				 */
3358c2ecf20Sopenharmony_ci				if (curr->act.irqhook)
3368c2ecf20Sopenharmony_ci					curr->act.irqhook(0, NULL, 0, 0);
3378c2ecf20Sopenharmony_ci			}
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci			curr->actidx = curr->idx;
3408c2ecf20Sopenharmony_ci			curr->idx++;
3418c2ecf20Sopenharmony_ci			hp_sdc.rcurr = -1;
3428c2ecf20Sopenharmony_ci		}
3438c2ecf20Sopenharmony_ci	}
3448c2ecf20Sopenharmony_ci	write_unlock_irq(&hp_sdc.rtq_lock);
3458c2ecf20Sopenharmony_ci	hp_sdc_put();
3468c2ecf20Sopenharmony_ci}
3478c2ecf20Sopenharmony_ci
3488c2ecf20Sopenharmony_ciunsigned long hp_sdc_put(void)
3498c2ecf20Sopenharmony_ci{
3508c2ecf20Sopenharmony_ci	hp_sdc_transaction *curr;
3518c2ecf20Sopenharmony_ci	uint8_t act;
3528c2ecf20Sopenharmony_ci	int idx, curridx;
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	int limit = 0;
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci	write_lock(&hp_sdc.lock);
3578c2ecf20Sopenharmony_ci
3588c2ecf20Sopenharmony_ci	/* If i8042 buffers are full, we cannot do anything that
3598c2ecf20Sopenharmony_ci	   requires output, so we skip to the administrativa. */
3608c2ecf20Sopenharmony_ci	if (hp_sdc.ibf) {
3618c2ecf20Sopenharmony_ci		hp_sdc_status_in8();
3628c2ecf20Sopenharmony_ci		if (hp_sdc.ibf)
3638c2ecf20Sopenharmony_ci			goto finish;
3648c2ecf20Sopenharmony_ci	}
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci anew:
3678c2ecf20Sopenharmony_ci	/* See if we are in the middle of a sequence. */
3688c2ecf20Sopenharmony_ci	if (hp_sdc.wcurr < 0)
3698c2ecf20Sopenharmony_ci		hp_sdc.wcurr = 0;
3708c2ecf20Sopenharmony_ci	read_lock_irq(&hp_sdc.rtq_lock);
3718c2ecf20Sopenharmony_ci	if (hp_sdc.rcurr == hp_sdc.wcurr)
3728c2ecf20Sopenharmony_ci		hp_sdc.wcurr++;
3738c2ecf20Sopenharmony_ci	read_unlock_irq(&hp_sdc.rtq_lock);
3748c2ecf20Sopenharmony_ci	if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN)
3758c2ecf20Sopenharmony_ci		hp_sdc.wcurr = 0;
3768c2ecf20Sopenharmony_ci	curridx = hp_sdc.wcurr;
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	if (hp_sdc.tq[curridx] != NULL)
3798c2ecf20Sopenharmony_ci		goto start;
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	while (++curridx != hp_sdc.wcurr) {
3828c2ecf20Sopenharmony_ci		if (curridx >= HP_SDC_QUEUE_LEN) {
3838c2ecf20Sopenharmony_ci			curridx = -1; /* Wrap to top */
3848c2ecf20Sopenharmony_ci			continue;
3858c2ecf20Sopenharmony_ci		}
3868c2ecf20Sopenharmony_ci		read_lock_irq(&hp_sdc.rtq_lock);
3878c2ecf20Sopenharmony_ci		if (hp_sdc.rcurr == curridx) {
3888c2ecf20Sopenharmony_ci			read_unlock_irq(&hp_sdc.rtq_lock);
3898c2ecf20Sopenharmony_ci			continue;
3908c2ecf20Sopenharmony_ci		}
3918c2ecf20Sopenharmony_ci		read_unlock_irq(&hp_sdc.rtq_lock);
3928c2ecf20Sopenharmony_ci		if (hp_sdc.tq[curridx] != NULL)
3938c2ecf20Sopenharmony_ci			break; /* Found one. */
3948c2ecf20Sopenharmony_ci	}
3958c2ecf20Sopenharmony_ci	if (curridx == hp_sdc.wcurr) { /* There's nothing queued to do. */
3968c2ecf20Sopenharmony_ci		curridx = -1;
3978c2ecf20Sopenharmony_ci	}
3988c2ecf20Sopenharmony_ci	hp_sdc.wcurr = curridx;
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_ci start:
4018c2ecf20Sopenharmony_ci
4028c2ecf20Sopenharmony_ci	/* Check to see if the interrupt mask needs to be set. */
4038c2ecf20Sopenharmony_ci	if (hp_sdc.set_im) {
4048c2ecf20Sopenharmony_ci		hp_sdc_status_out8(hp_sdc.im | HP_SDC_CMD_SET_IM);
4058c2ecf20Sopenharmony_ci		hp_sdc.set_im = 0;
4068c2ecf20Sopenharmony_ci		goto finish;
4078c2ecf20Sopenharmony_ci	}
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ci	if (hp_sdc.wcurr == -1)
4108c2ecf20Sopenharmony_ci		goto done;
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci	curr = hp_sdc.tq[curridx];
4138c2ecf20Sopenharmony_ci	idx = curr->actidx;
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_ci	if (curr->actidx >= curr->endidx) {
4168c2ecf20Sopenharmony_ci		hp_sdc.tq[curridx] = NULL;
4178c2ecf20Sopenharmony_ci		/* Interleave outbound data between the transactions. */
4188c2ecf20Sopenharmony_ci		hp_sdc.wcurr++;
4198c2ecf20Sopenharmony_ci		if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN)
4208c2ecf20Sopenharmony_ci			hp_sdc.wcurr = 0;
4218c2ecf20Sopenharmony_ci		goto finish;
4228c2ecf20Sopenharmony_ci	}
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci	act = curr->seq[idx];
4258c2ecf20Sopenharmony_ci	idx++;
4268c2ecf20Sopenharmony_ci
4278c2ecf20Sopenharmony_ci	if (curr->idx >= curr->endidx) {
4288c2ecf20Sopenharmony_ci		if (act & HP_SDC_ACT_DEALLOC)
4298c2ecf20Sopenharmony_ci			kfree(curr);
4308c2ecf20Sopenharmony_ci		hp_sdc.tq[curridx] = NULL;
4318c2ecf20Sopenharmony_ci		/* Interleave outbound data between the transactions. */
4328c2ecf20Sopenharmony_ci		hp_sdc.wcurr++;
4338c2ecf20Sopenharmony_ci		if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN)
4348c2ecf20Sopenharmony_ci			hp_sdc.wcurr = 0;
4358c2ecf20Sopenharmony_ci		goto finish;
4368c2ecf20Sopenharmony_ci	}
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci	while (act & HP_SDC_ACT_PRECMD) {
4398c2ecf20Sopenharmony_ci		if (curr->idx != idx) {
4408c2ecf20Sopenharmony_ci			idx++;
4418c2ecf20Sopenharmony_ci			act &= ~HP_SDC_ACT_PRECMD;
4428c2ecf20Sopenharmony_ci			break;
4438c2ecf20Sopenharmony_ci		}
4448c2ecf20Sopenharmony_ci		hp_sdc_status_out8(curr->seq[idx]);
4458c2ecf20Sopenharmony_ci		curr->idx++;
4468c2ecf20Sopenharmony_ci		/* act finished? */
4478c2ecf20Sopenharmony_ci		if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_PRECMD)
4488c2ecf20Sopenharmony_ci			goto actdone;
4498c2ecf20Sopenharmony_ci		/* skip quantity field if data-out sequence follows. */
4508c2ecf20Sopenharmony_ci		if (act & HP_SDC_ACT_DATAOUT)
4518c2ecf20Sopenharmony_ci			curr->idx++;
4528c2ecf20Sopenharmony_ci		goto finish;
4538c2ecf20Sopenharmony_ci	}
4548c2ecf20Sopenharmony_ci	if (act & HP_SDC_ACT_DATAOUT) {
4558c2ecf20Sopenharmony_ci		int qty;
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_ci		qty = curr->seq[idx];
4588c2ecf20Sopenharmony_ci		idx++;
4598c2ecf20Sopenharmony_ci		if (curr->idx - idx < qty) {
4608c2ecf20Sopenharmony_ci			hp_sdc_data_out8(curr->seq[curr->idx]);
4618c2ecf20Sopenharmony_ci			curr->idx++;
4628c2ecf20Sopenharmony_ci			/* act finished? */
4638c2ecf20Sopenharmony_ci			if (curr->idx - idx >= qty &&
4648c2ecf20Sopenharmony_ci			    (act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAOUT)
4658c2ecf20Sopenharmony_ci				goto actdone;
4668c2ecf20Sopenharmony_ci			goto finish;
4678c2ecf20Sopenharmony_ci		}
4688c2ecf20Sopenharmony_ci		idx += qty;
4698c2ecf20Sopenharmony_ci		act &= ~HP_SDC_ACT_DATAOUT;
4708c2ecf20Sopenharmony_ci	} else
4718c2ecf20Sopenharmony_ci	    while (act & HP_SDC_ACT_DATAREG) {
4728c2ecf20Sopenharmony_ci		int mask;
4738c2ecf20Sopenharmony_ci		uint8_t w7[4];
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci		mask = curr->seq[idx];
4768c2ecf20Sopenharmony_ci		if (idx != curr->idx) {
4778c2ecf20Sopenharmony_ci			idx++;
4788c2ecf20Sopenharmony_ci			idx += !!(mask & 1);
4798c2ecf20Sopenharmony_ci			idx += !!(mask & 2);
4808c2ecf20Sopenharmony_ci			idx += !!(mask & 4);
4818c2ecf20Sopenharmony_ci			idx += !!(mask & 8);
4828c2ecf20Sopenharmony_ci			act &= ~HP_SDC_ACT_DATAREG;
4838c2ecf20Sopenharmony_ci			break;
4848c2ecf20Sopenharmony_ci		}
4858c2ecf20Sopenharmony_ci
4868c2ecf20Sopenharmony_ci		w7[0] = (mask & 1) ? curr->seq[++idx] : hp_sdc.r7[0];
4878c2ecf20Sopenharmony_ci		w7[1] = (mask & 2) ? curr->seq[++idx] : hp_sdc.r7[1];
4888c2ecf20Sopenharmony_ci		w7[2] = (mask & 4) ? curr->seq[++idx] : hp_sdc.r7[2];
4898c2ecf20Sopenharmony_ci		w7[3] = (mask & 8) ? curr->seq[++idx] : hp_sdc.r7[3];
4908c2ecf20Sopenharmony_ci
4918c2ecf20Sopenharmony_ci		if (hp_sdc.wi > 0x73 || hp_sdc.wi < 0x70 ||
4928c2ecf20Sopenharmony_ci		    w7[hp_sdc.wi - 0x70] == hp_sdc.r7[hp_sdc.wi - 0x70]) {
4938c2ecf20Sopenharmony_ci			int i = 0;
4948c2ecf20Sopenharmony_ci
4958c2ecf20Sopenharmony_ci			/* Need to point the write index register */
4968c2ecf20Sopenharmony_ci			while (i < 4 && w7[i] == hp_sdc.r7[i])
4978c2ecf20Sopenharmony_ci				i++;
4988c2ecf20Sopenharmony_ci
4998c2ecf20Sopenharmony_ci			if (i < 4) {
5008c2ecf20Sopenharmony_ci				hp_sdc_status_out8(HP_SDC_CMD_SET_D0 + i);
5018c2ecf20Sopenharmony_ci				hp_sdc.wi = 0x70 + i;
5028c2ecf20Sopenharmony_ci				goto finish;
5038c2ecf20Sopenharmony_ci			}
5048c2ecf20Sopenharmony_ci
5058c2ecf20Sopenharmony_ci			idx++;
5068c2ecf20Sopenharmony_ci			if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAREG)
5078c2ecf20Sopenharmony_ci				goto actdone;
5088c2ecf20Sopenharmony_ci
5098c2ecf20Sopenharmony_ci			curr->idx = idx;
5108c2ecf20Sopenharmony_ci			act &= ~HP_SDC_ACT_DATAREG;
5118c2ecf20Sopenharmony_ci			break;
5128c2ecf20Sopenharmony_ci		}
5138c2ecf20Sopenharmony_ci
5148c2ecf20Sopenharmony_ci		hp_sdc_data_out8(w7[hp_sdc.wi - 0x70]);
5158c2ecf20Sopenharmony_ci		hp_sdc.r7[hp_sdc.wi - 0x70] = w7[hp_sdc.wi - 0x70];
5168c2ecf20Sopenharmony_ci		hp_sdc.wi++; /* write index register autoincrements */
5178c2ecf20Sopenharmony_ci		{
5188c2ecf20Sopenharmony_ci			int i = 0;
5198c2ecf20Sopenharmony_ci
5208c2ecf20Sopenharmony_ci			while ((i < 4) && w7[i] == hp_sdc.r7[i])
5218c2ecf20Sopenharmony_ci				i++;
5228c2ecf20Sopenharmony_ci			if (i >= 4) {
5238c2ecf20Sopenharmony_ci				curr->idx = idx + 1;
5248c2ecf20Sopenharmony_ci				if ((act & HP_SDC_ACT_DURING) ==
5258c2ecf20Sopenharmony_ci				    HP_SDC_ACT_DATAREG)
5268c2ecf20Sopenharmony_ci					goto actdone;
5278c2ecf20Sopenharmony_ci			}
5288c2ecf20Sopenharmony_ci		}
5298c2ecf20Sopenharmony_ci		goto finish;
5308c2ecf20Sopenharmony_ci	}
5318c2ecf20Sopenharmony_ci	/* We don't go any further in the command if there is a pending read,
5328c2ecf20Sopenharmony_ci	   because we don't want interleaved results. */
5338c2ecf20Sopenharmony_ci	read_lock_irq(&hp_sdc.rtq_lock);
5348c2ecf20Sopenharmony_ci	if (hp_sdc.rcurr >= 0) {
5358c2ecf20Sopenharmony_ci		read_unlock_irq(&hp_sdc.rtq_lock);
5368c2ecf20Sopenharmony_ci		goto finish;
5378c2ecf20Sopenharmony_ci	}
5388c2ecf20Sopenharmony_ci	read_unlock_irq(&hp_sdc.rtq_lock);
5398c2ecf20Sopenharmony_ci
5408c2ecf20Sopenharmony_ci
5418c2ecf20Sopenharmony_ci	if (act & HP_SDC_ACT_POSTCMD) {
5428c2ecf20Sopenharmony_ci		uint8_t postcmd;
5438c2ecf20Sopenharmony_ci
5448c2ecf20Sopenharmony_ci		/* curr->idx should == idx at this point. */
5458c2ecf20Sopenharmony_ci		postcmd = curr->seq[idx];
5468c2ecf20Sopenharmony_ci		curr->idx++;
5478c2ecf20Sopenharmony_ci		if (act & HP_SDC_ACT_DATAIN) {
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci			/* Start a new read */
5508c2ecf20Sopenharmony_ci			hp_sdc.rqty = curr->seq[curr->idx];
5518c2ecf20Sopenharmony_ci			hp_sdc.rtime = ktime_get();
5528c2ecf20Sopenharmony_ci			curr->idx++;
5538c2ecf20Sopenharmony_ci			/* Still need to lock here in case of spurious irq. */
5548c2ecf20Sopenharmony_ci			write_lock_irq(&hp_sdc.rtq_lock);
5558c2ecf20Sopenharmony_ci			hp_sdc.rcurr = curridx;
5568c2ecf20Sopenharmony_ci			write_unlock_irq(&hp_sdc.rtq_lock);
5578c2ecf20Sopenharmony_ci			hp_sdc_status_out8(postcmd);
5588c2ecf20Sopenharmony_ci			goto finish;
5598c2ecf20Sopenharmony_ci		}
5608c2ecf20Sopenharmony_ci		hp_sdc_status_out8(postcmd);
5618c2ecf20Sopenharmony_ci		goto actdone;
5628c2ecf20Sopenharmony_ci	}
5638c2ecf20Sopenharmony_ci
5648c2ecf20Sopenharmony_ci actdone:
5658c2ecf20Sopenharmony_ci	if (act & HP_SDC_ACT_SEMAPHORE)
5668c2ecf20Sopenharmony_ci		up(curr->act.semaphore);
5678c2ecf20Sopenharmony_ci	else if (act & HP_SDC_ACT_CALLBACK)
5688c2ecf20Sopenharmony_ci		curr->act.irqhook(0,NULL,0,0);
5698c2ecf20Sopenharmony_ci
5708c2ecf20Sopenharmony_ci	if (curr->idx >= curr->endidx) { /* This transaction is over. */
5718c2ecf20Sopenharmony_ci		if (act & HP_SDC_ACT_DEALLOC)
5728c2ecf20Sopenharmony_ci			kfree(curr);
5738c2ecf20Sopenharmony_ci		hp_sdc.tq[curridx] = NULL;
5748c2ecf20Sopenharmony_ci	} else {
5758c2ecf20Sopenharmony_ci		curr->actidx = idx + 1;
5768c2ecf20Sopenharmony_ci		curr->idx = idx + 2;
5778c2ecf20Sopenharmony_ci	}
5788c2ecf20Sopenharmony_ci	/* Interleave outbound data between the transactions. */
5798c2ecf20Sopenharmony_ci	hp_sdc.wcurr++;
5808c2ecf20Sopenharmony_ci	if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN)
5818c2ecf20Sopenharmony_ci		hp_sdc.wcurr = 0;
5828c2ecf20Sopenharmony_ci
5838c2ecf20Sopenharmony_ci finish:
5848c2ecf20Sopenharmony_ci	/* If by some quirk IBF has cleared and our ISR has run to
5858c2ecf20Sopenharmony_ci	   see that that has happened, do it all again. */
5868c2ecf20Sopenharmony_ci	if (!hp_sdc.ibf && limit++ < 20)
5878c2ecf20Sopenharmony_ci		goto anew;
5888c2ecf20Sopenharmony_ci
5898c2ecf20Sopenharmony_ci done:
5908c2ecf20Sopenharmony_ci	if (hp_sdc.wcurr >= 0)
5918c2ecf20Sopenharmony_ci		tasklet_schedule(&hp_sdc.task);
5928c2ecf20Sopenharmony_ci	write_unlock(&hp_sdc.lock);
5938c2ecf20Sopenharmony_ci
5948c2ecf20Sopenharmony_ci	return 0;
5958c2ecf20Sopenharmony_ci}
5968c2ecf20Sopenharmony_ci
5978c2ecf20Sopenharmony_ci/******* Functions called in either user or kernel context ****/
5988c2ecf20Sopenharmony_ciint __hp_sdc_enqueue_transaction(hp_sdc_transaction *this)
5998c2ecf20Sopenharmony_ci{
6008c2ecf20Sopenharmony_ci	int i;
6018c2ecf20Sopenharmony_ci
6028c2ecf20Sopenharmony_ci	if (this == NULL) {
6038c2ecf20Sopenharmony_ci		BUG();
6048c2ecf20Sopenharmony_ci		return -EINVAL;
6058c2ecf20Sopenharmony_ci	}
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ci	/* Can't have same transaction on queue twice */
6088c2ecf20Sopenharmony_ci	for (i = 0; i < HP_SDC_QUEUE_LEN; i++)
6098c2ecf20Sopenharmony_ci		if (hp_sdc.tq[i] == this)
6108c2ecf20Sopenharmony_ci			goto fail;
6118c2ecf20Sopenharmony_ci
6128c2ecf20Sopenharmony_ci	this->actidx = 0;
6138c2ecf20Sopenharmony_ci	this->idx = 1;
6148c2ecf20Sopenharmony_ci
6158c2ecf20Sopenharmony_ci	/* Search for empty slot */
6168c2ecf20Sopenharmony_ci	for (i = 0; i < HP_SDC_QUEUE_LEN; i++)
6178c2ecf20Sopenharmony_ci		if (hp_sdc.tq[i] == NULL) {
6188c2ecf20Sopenharmony_ci			hp_sdc.tq[i] = this;
6198c2ecf20Sopenharmony_ci			tasklet_schedule(&hp_sdc.task);
6208c2ecf20Sopenharmony_ci			return 0;
6218c2ecf20Sopenharmony_ci		}
6228c2ecf20Sopenharmony_ci
6238c2ecf20Sopenharmony_ci	printk(KERN_WARNING PREFIX "No free slot to add transaction.\n");
6248c2ecf20Sopenharmony_ci	return -EBUSY;
6258c2ecf20Sopenharmony_ci
6268c2ecf20Sopenharmony_ci fail:
6278c2ecf20Sopenharmony_ci	printk(KERN_WARNING PREFIX "Transaction add failed: transaction already queued?\n");
6288c2ecf20Sopenharmony_ci	return -EINVAL;
6298c2ecf20Sopenharmony_ci}
6308c2ecf20Sopenharmony_ci
6318c2ecf20Sopenharmony_ciint hp_sdc_enqueue_transaction(hp_sdc_transaction *this) {
6328c2ecf20Sopenharmony_ci	unsigned long flags;
6338c2ecf20Sopenharmony_ci	int ret;
6348c2ecf20Sopenharmony_ci
6358c2ecf20Sopenharmony_ci	write_lock_irqsave(&hp_sdc.lock, flags);
6368c2ecf20Sopenharmony_ci	ret = __hp_sdc_enqueue_transaction(this);
6378c2ecf20Sopenharmony_ci	write_unlock_irqrestore(&hp_sdc.lock,flags);
6388c2ecf20Sopenharmony_ci
6398c2ecf20Sopenharmony_ci	return ret;
6408c2ecf20Sopenharmony_ci}
6418c2ecf20Sopenharmony_ci
6428c2ecf20Sopenharmony_ciint hp_sdc_dequeue_transaction(hp_sdc_transaction *this)
6438c2ecf20Sopenharmony_ci{
6448c2ecf20Sopenharmony_ci	unsigned long flags;
6458c2ecf20Sopenharmony_ci	int i;
6468c2ecf20Sopenharmony_ci
6478c2ecf20Sopenharmony_ci	write_lock_irqsave(&hp_sdc.lock, flags);
6488c2ecf20Sopenharmony_ci
6498c2ecf20Sopenharmony_ci	/* TODO: don't remove it if it's not done. */
6508c2ecf20Sopenharmony_ci
6518c2ecf20Sopenharmony_ci	for (i = 0; i < HP_SDC_QUEUE_LEN; i++)
6528c2ecf20Sopenharmony_ci		if (hp_sdc.tq[i] == this)
6538c2ecf20Sopenharmony_ci			hp_sdc.tq[i] = NULL;
6548c2ecf20Sopenharmony_ci
6558c2ecf20Sopenharmony_ci	write_unlock_irqrestore(&hp_sdc.lock, flags);
6568c2ecf20Sopenharmony_ci	return 0;
6578c2ecf20Sopenharmony_ci}
6588c2ecf20Sopenharmony_ci
6598c2ecf20Sopenharmony_ci
6608c2ecf20Sopenharmony_ci
6618c2ecf20Sopenharmony_ci/********************** User context functions **************************/
6628c2ecf20Sopenharmony_ciint hp_sdc_request_timer_irq(hp_sdc_irqhook *callback)
6638c2ecf20Sopenharmony_ci{
6648c2ecf20Sopenharmony_ci	if (callback == NULL || hp_sdc.dev == NULL)
6658c2ecf20Sopenharmony_ci		return -EINVAL;
6668c2ecf20Sopenharmony_ci
6678c2ecf20Sopenharmony_ci	write_lock_irq(&hp_sdc.hook_lock);
6688c2ecf20Sopenharmony_ci	if (hp_sdc.timer != NULL) {
6698c2ecf20Sopenharmony_ci		write_unlock_irq(&hp_sdc.hook_lock);
6708c2ecf20Sopenharmony_ci		return -EBUSY;
6718c2ecf20Sopenharmony_ci	}
6728c2ecf20Sopenharmony_ci
6738c2ecf20Sopenharmony_ci	hp_sdc.timer = callback;
6748c2ecf20Sopenharmony_ci	/* Enable interrupts from the timers */
6758c2ecf20Sopenharmony_ci	hp_sdc.im &= ~HP_SDC_IM_FH;
6768c2ecf20Sopenharmony_ci        hp_sdc.im &= ~HP_SDC_IM_PT;
6778c2ecf20Sopenharmony_ci	hp_sdc.im &= ~HP_SDC_IM_TIMERS;
6788c2ecf20Sopenharmony_ci	hp_sdc.set_im = 1;
6798c2ecf20Sopenharmony_ci	write_unlock_irq(&hp_sdc.hook_lock);
6808c2ecf20Sopenharmony_ci
6818c2ecf20Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
6828c2ecf20Sopenharmony_ci
6838c2ecf20Sopenharmony_ci	return 0;
6848c2ecf20Sopenharmony_ci}
6858c2ecf20Sopenharmony_ci
6868c2ecf20Sopenharmony_ciint hp_sdc_request_hil_irq(hp_sdc_irqhook *callback)
6878c2ecf20Sopenharmony_ci{
6888c2ecf20Sopenharmony_ci	if (callback == NULL || hp_sdc.dev == NULL)
6898c2ecf20Sopenharmony_ci		return -EINVAL;
6908c2ecf20Sopenharmony_ci
6918c2ecf20Sopenharmony_ci	write_lock_irq(&hp_sdc.hook_lock);
6928c2ecf20Sopenharmony_ci	if (hp_sdc.hil != NULL) {
6938c2ecf20Sopenharmony_ci		write_unlock_irq(&hp_sdc.hook_lock);
6948c2ecf20Sopenharmony_ci		return -EBUSY;
6958c2ecf20Sopenharmony_ci	}
6968c2ecf20Sopenharmony_ci
6978c2ecf20Sopenharmony_ci	hp_sdc.hil = callback;
6988c2ecf20Sopenharmony_ci	hp_sdc.im &= ~(HP_SDC_IM_HIL | HP_SDC_IM_RESET);
6998c2ecf20Sopenharmony_ci	hp_sdc.set_im = 1;
7008c2ecf20Sopenharmony_ci	write_unlock_irq(&hp_sdc.hook_lock);
7018c2ecf20Sopenharmony_ci
7028c2ecf20Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
7038c2ecf20Sopenharmony_ci
7048c2ecf20Sopenharmony_ci	return 0;
7058c2ecf20Sopenharmony_ci}
7068c2ecf20Sopenharmony_ci
7078c2ecf20Sopenharmony_ciint hp_sdc_request_cooked_irq(hp_sdc_irqhook *callback)
7088c2ecf20Sopenharmony_ci{
7098c2ecf20Sopenharmony_ci	if (callback == NULL || hp_sdc.dev == NULL)
7108c2ecf20Sopenharmony_ci		return -EINVAL;
7118c2ecf20Sopenharmony_ci
7128c2ecf20Sopenharmony_ci	write_lock_irq(&hp_sdc.hook_lock);
7138c2ecf20Sopenharmony_ci	if (hp_sdc.cooked != NULL) {
7148c2ecf20Sopenharmony_ci		write_unlock_irq(&hp_sdc.hook_lock);
7158c2ecf20Sopenharmony_ci		return -EBUSY;
7168c2ecf20Sopenharmony_ci	}
7178c2ecf20Sopenharmony_ci
7188c2ecf20Sopenharmony_ci	/* Enable interrupts from the HIL MLC */
7198c2ecf20Sopenharmony_ci	hp_sdc.cooked = callback;
7208c2ecf20Sopenharmony_ci	hp_sdc.im &= ~(HP_SDC_IM_HIL | HP_SDC_IM_RESET);
7218c2ecf20Sopenharmony_ci	hp_sdc.set_im = 1;
7228c2ecf20Sopenharmony_ci	write_unlock_irq(&hp_sdc.hook_lock);
7238c2ecf20Sopenharmony_ci
7248c2ecf20Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
7258c2ecf20Sopenharmony_ci
7268c2ecf20Sopenharmony_ci	return 0;
7278c2ecf20Sopenharmony_ci}
7288c2ecf20Sopenharmony_ci
7298c2ecf20Sopenharmony_ciint hp_sdc_release_timer_irq(hp_sdc_irqhook *callback)
7308c2ecf20Sopenharmony_ci{
7318c2ecf20Sopenharmony_ci	write_lock_irq(&hp_sdc.hook_lock);
7328c2ecf20Sopenharmony_ci	if ((callback != hp_sdc.timer) ||
7338c2ecf20Sopenharmony_ci	    (hp_sdc.timer == NULL)) {
7348c2ecf20Sopenharmony_ci		write_unlock_irq(&hp_sdc.hook_lock);
7358c2ecf20Sopenharmony_ci		return -EINVAL;
7368c2ecf20Sopenharmony_ci	}
7378c2ecf20Sopenharmony_ci
7388c2ecf20Sopenharmony_ci	/* Disable interrupts from the timers */
7398c2ecf20Sopenharmony_ci	hp_sdc.timer = NULL;
7408c2ecf20Sopenharmony_ci	hp_sdc.im |= HP_SDC_IM_TIMERS;
7418c2ecf20Sopenharmony_ci	hp_sdc.im |= HP_SDC_IM_FH;
7428c2ecf20Sopenharmony_ci	hp_sdc.im |= HP_SDC_IM_PT;
7438c2ecf20Sopenharmony_ci	hp_sdc.set_im = 1;
7448c2ecf20Sopenharmony_ci	write_unlock_irq(&hp_sdc.hook_lock);
7458c2ecf20Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
7468c2ecf20Sopenharmony_ci
7478c2ecf20Sopenharmony_ci	return 0;
7488c2ecf20Sopenharmony_ci}
7498c2ecf20Sopenharmony_ci
7508c2ecf20Sopenharmony_ciint hp_sdc_release_hil_irq(hp_sdc_irqhook *callback)
7518c2ecf20Sopenharmony_ci{
7528c2ecf20Sopenharmony_ci	write_lock_irq(&hp_sdc.hook_lock);
7538c2ecf20Sopenharmony_ci	if ((callback != hp_sdc.hil) ||
7548c2ecf20Sopenharmony_ci	    (hp_sdc.hil == NULL)) {
7558c2ecf20Sopenharmony_ci		write_unlock_irq(&hp_sdc.hook_lock);
7568c2ecf20Sopenharmony_ci		return -EINVAL;
7578c2ecf20Sopenharmony_ci	}
7588c2ecf20Sopenharmony_ci
7598c2ecf20Sopenharmony_ci	hp_sdc.hil = NULL;
7608c2ecf20Sopenharmony_ci	/* Disable interrupts from HIL only if there is no cooked driver. */
7618c2ecf20Sopenharmony_ci	if(hp_sdc.cooked == NULL) {
7628c2ecf20Sopenharmony_ci		hp_sdc.im |= (HP_SDC_IM_HIL | HP_SDC_IM_RESET);
7638c2ecf20Sopenharmony_ci		hp_sdc.set_im = 1;
7648c2ecf20Sopenharmony_ci	}
7658c2ecf20Sopenharmony_ci	write_unlock_irq(&hp_sdc.hook_lock);
7668c2ecf20Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
7678c2ecf20Sopenharmony_ci
7688c2ecf20Sopenharmony_ci	return 0;
7698c2ecf20Sopenharmony_ci}
7708c2ecf20Sopenharmony_ci
7718c2ecf20Sopenharmony_ciint hp_sdc_release_cooked_irq(hp_sdc_irqhook *callback)
7728c2ecf20Sopenharmony_ci{
7738c2ecf20Sopenharmony_ci	write_lock_irq(&hp_sdc.hook_lock);
7748c2ecf20Sopenharmony_ci	if ((callback != hp_sdc.cooked) ||
7758c2ecf20Sopenharmony_ci	    (hp_sdc.cooked == NULL)) {
7768c2ecf20Sopenharmony_ci		write_unlock_irq(&hp_sdc.hook_lock);
7778c2ecf20Sopenharmony_ci		return -EINVAL;
7788c2ecf20Sopenharmony_ci	}
7798c2ecf20Sopenharmony_ci
7808c2ecf20Sopenharmony_ci	hp_sdc.cooked = NULL;
7818c2ecf20Sopenharmony_ci	/* Disable interrupts from HIL only if there is no raw HIL driver. */
7828c2ecf20Sopenharmony_ci	if(hp_sdc.hil == NULL) {
7838c2ecf20Sopenharmony_ci		hp_sdc.im |= (HP_SDC_IM_HIL | HP_SDC_IM_RESET);
7848c2ecf20Sopenharmony_ci		hp_sdc.set_im = 1;
7858c2ecf20Sopenharmony_ci	}
7868c2ecf20Sopenharmony_ci	write_unlock_irq(&hp_sdc.hook_lock);
7878c2ecf20Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
7888c2ecf20Sopenharmony_ci
7898c2ecf20Sopenharmony_ci	return 0;
7908c2ecf20Sopenharmony_ci}
7918c2ecf20Sopenharmony_ci
7928c2ecf20Sopenharmony_ci/************************* Keepalive timer task *********************/
7938c2ecf20Sopenharmony_ci
7948c2ecf20Sopenharmony_cistatic void hp_sdc_kicker(struct timer_list *unused)
7958c2ecf20Sopenharmony_ci{
7968c2ecf20Sopenharmony_ci	tasklet_schedule(&hp_sdc.task);
7978c2ecf20Sopenharmony_ci	/* Re-insert the periodic task. */
7988c2ecf20Sopenharmony_ci	mod_timer(&hp_sdc.kicker, jiffies + HZ);
7998c2ecf20Sopenharmony_ci}
8008c2ecf20Sopenharmony_ci
8018c2ecf20Sopenharmony_ci/************************** Module Initialization ***************************/
8028c2ecf20Sopenharmony_ci
8038c2ecf20Sopenharmony_ci#if defined(__hppa__)
8048c2ecf20Sopenharmony_ci
8058c2ecf20Sopenharmony_cistatic const struct parisc_device_id hp_sdc_tbl[] __initconst = {
8068c2ecf20Sopenharmony_ci	{
8078c2ecf20Sopenharmony_ci		.hw_type =	HPHW_FIO,
8088c2ecf20Sopenharmony_ci		.hversion_rev =	HVERSION_REV_ANY_ID,
8098c2ecf20Sopenharmony_ci		.hversion =	HVERSION_ANY_ID,
8108c2ecf20Sopenharmony_ci		.sversion =	0x73,
8118c2ecf20Sopenharmony_ci	 },
8128c2ecf20Sopenharmony_ci	{ 0, }
8138c2ecf20Sopenharmony_ci};
8148c2ecf20Sopenharmony_ci
8158c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(parisc, hp_sdc_tbl);
8168c2ecf20Sopenharmony_ci
8178c2ecf20Sopenharmony_cistatic int __init hp_sdc_init_hppa(struct parisc_device *d);
8188c2ecf20Sopenharmony_cistatic struct delayed_work moduleloader_work;
8198c2ecf20Sopenharmony_ci
8208c2ecf20Sopenharmony_cistatic struct parisc_driver hp_sdc_driver __refdata = {
8218c2ecf20Sopenharmony_ci	.name =		"hp_sdc",
8228c2ecf20Sopenharmony_ci	.id_table =	hp_sdc_tbl,
8238c2ecf20Sopenharmony_ci	.probe =	hp_sdc_init_hppa,
8248c2ecf20Sopenharmony_ci};
8258c2ecf20Sopenharmony_ci
8268c2ecf20Sopenharmony_ci#endif /* __hppa__ */
8278c2ecf20Sopenharmony_ci
8288c2ecf20Sopenharmony_cistatic int __init hp_sdc_init(void)
8298c2ecf20Sopenharmony_ci{
8308c2ecf20Sopenharmony_ci	char *errstr;
8318c2ecf20Sopenharmony_ci	hp_sdc_transaction t_sync;
8328c2ecf20Sopenharmony_ci	uint8_t ts_sync[6];
8338c2ecf20Sopenharmony_ci	struct semaphore s_sync;
8348c2ecf20Sopenharmony_ci
8358c2ecf20Sopenharmony_ci	rwlock_init(&hp_sdc.lock);
8368c2ecf20Sopenharmony_ci	rwlock_init(&hp_sdc.ibf_lock);
8378c2ecf20Sopenharmony_ci	rwlock_init(&hp_sdc.rtq_lock);
8388c2ecf20Sopenharmony_ci	rwlock_init(&hp_sdc.hook_lock);
8398c2ecf20Sopenharmony_ci
8408c2ecf20Sopenharmony_ci	hp_sdc.timer		= NULL;
8418c2ecf20Sopenharmony_ci	hp_sdc.hil		= NULL;
8428c2ecf20Sopenharmony_ci	hp_sdc.pup		= NULL;
8438c2ecf20Sopenharmony_ci	hp_sdc.cooked		= NULL;
8448c2ecf20Sopenharmony_ci	hp_sdc.im		= HP_SDC_IM_MASK;  /* Mask maskable irqs */
8458c2ecf20Sopenharmony_ci	hp_sdc.set_im		= 1;
8468c2ecf20Sopenharmony_ci	hp_sdc.wi		= 0xff;
8478c2ecf20Sopenharmony_ci	hp_sdc.r7[0]		= 0xff;
8488c2ecf20Sopenharmony_ci	hp_sdc.r7[1]		= 0xff;
8498c2ecf20Sopenharmony_ci	hp_sdc.r7[2]		= 0xff;
8508c2ecf20Sopenharmony_ci	hp_sdc.r7[3]		= 0xff;
8518c2ecf20Sopenharmony_ci	hp_sdc.ibf		= 1;
8528c2ecf20Sopenharmony_ci
8538c2ecf20Sopenharmony_ci	memset(&hp_sdc.tq, 0, sizeof(hp_sdc.tq));
8548c2ecf20Sopenharmony_ci
8558c2ecf20Sopenharmony_ci	hp_sdc.wcurr		= -1;
8568c2ecf20Sopenharmony_ci        hp_sdc.rcurr		= -1;
8578c2ecf20Sopenharmony_ci	hp_sdc.rqty		= 0;
8588c2ecf20Sopenharmony_ci
8598c2ecf20Sopenharmony_ci	hp_sdc.dev_err = -ENODEV;
8608c2ecf20Sopenharmony_ci
8618c2ecf20Sopenharmony_ci	errstr = "IO not found for";
8628c2ecf20Sopenharmony_ci	if (!hp_sdc.base_io)
8638c2ecf20Sopenharmony_ci		goto err0;
8648c2ecf20Sopenharmony_ci
8658c2ecf20Sopenharmony_ci	errstr = "IRQ not found for";
8668c2ecf20Sopenharmony_ci	if (!hp_sdc.irq)
8678c2ecf20Sopenharmony_ci		goto err0;
8688c2ecf20Sopenharmony_ci
8698c2ecf20Sopenharmony_ci	hp_sdc.dev_err = -EBUSY;
8708c2ecf20Sopenharmony_ci
8718c2ecf20Sopenharmony_ci#if defined(__hppa__)
8728c2ecf20Sopenharmony_ci	errstr = "IO not available for";
8738c2ecf20Sopenharmony_ci        if (request_region(hp_sdc.data_io, 2, hp_sdc_driver.name))
8748c2ecf20Sopenharmony_ci		goto err0;
8758c2ecf20Sopenharmony_ci#endif
8768c2ecf20Sopenharmony_ci
8778c2ecf20Sopenharmony_ci	errstr = "IRQ not available for";
8788c2ecf20Sopenharmony_ci	if (request_irq(hp_sdc.irq, &hp_sdc_isr, IRQF_SHARED,
8798c2ecf20Sopenharmony_ci			"HP SDC", &hp_sdc))
8808c2ecf20Sopenharmony_ci		goto err1;
8818c2ecf20Sopenharmony_ci
8828c2ecf20Sopenharmony_ci	errstr = "NMI not available for";
8838c2ecf20Sopenharmony_ci	if (request_irq(hp_sdc.nmi, &hp_sdc_nmisr, IRQF_SHARED,
8848c2ecf20Sopenharmony_ci			"HP SDC NMI", &hp_sdc))
8858c2ecf20Sopenharmony_ci		goto err2;
8868c2ecf20Sopenharmony_ci
8878c2ecf20Sopenharmony_ci	pr_info(PREFIX "HP SDC at 0x%08lx, IRQ %d (NMI IRQ %d)\n",
8888c2ecf20Sopenharmony_ci	       hp_sdc.base_io, hp_sdc.irq, hp_sdc.nmi);
8898c2ecf20Sopenharmony_ci
8908c2ecf20Sopenharmony_ci	hp_sdc_status_in8();
8918c2ecf20Sopenharmony_ci	hp_sdc_data_in8();
8928c2ecf20Sopenharmony_ci
8938c2ecf20Sopenharmony_ci	tasklet_init(&hp_sdc.task, hp_sdc_tasklet, 0);
8948c2ecf20Sopenharmony_ci
8958c2ecf20Sopenharmony_ci	/* Sync the output buffer registers, thus scheduling hp_sdc_tasklet. */
8968c2ecf20Sopenharmony_ci	t_sync.actidx	= 0;
8978c2ecf20Sopenharmony_ci	t_sync.idx	= 1;
8988c2ecf20Sopenharmony_ci	t_sync.endidx	= 6;
8998c2ecf20Sopenharmony_ci	t_sync.seq	= ts_sync;
9008c2ecf20Sopenharmony_ci	ts_sync[0]	= HP_SDC_ACT_DATAREG | HP_SDC_ACT_SEMAPHORE;
9018c2ecf20Sopenharmony_ci	ts_sync[1]	= 0x0f;
9028c2ecf20Sopenharmony_ci	ts_sync[2] = ts_sync[3]	= ts_sync[4] = ts_sync[5] = 0;
9038c2ecf20Sopenharmony_ci	t_sync.act.semaphore = &s_sync;
9048c2ecf20Sopenharmony_ci	sema_init(&s_sync, 0);
9058c2ecf20Sopenharmony_ci	hp_sdc_enqueue_transaction(&t_sync);
9068c2ecf20Sopenharmony_ci	down(&s_sync); /* Wait for t_sync to complete */
9078c2ecf20Sopenharmony_ci
9088c2ecf20Sopenharmony_ci	/* Create the keepalive task */
9098c2ecf20Sopenharmony_ci	timer_setup(&hp_sdc.kicker, hp_sdc_kicker, 0);
9108c2ecf20Sopenharmony_ci	hp_sdc.kicker.expires = jiffies + HZ;
9118c2ecf20Sopenharmony_ci	add_timer(&hp_sdc.kicker);
9128c2ecf20Sopenharmony_ci
9138c2ecf20Sopenharmony_ci	hp_sdc.dev_err = 0;
9148c2ecf20Sopenharmony_ci	return 0;
9158c2ecf20Sopenharmony_ci err2:
9168c2ecf20Sopenharmony_ci	free_irq(hp_sdc.irq, &hp_sdc);
9178c2ecf20Sopenharmony_ci err1:
9188c2ecf20Sopenharmony_ci	release_region(hp_sdc.data_io, 2);
9198c2ecf20Sopenharmony_ci err0:
9208c2ecf20Sopenharmony_ci	printk(KERN_WARNING PREFIX ": %s SDC IO=0x%p IRQ=0x%x NMI=0x%x\n",
9218c2ecf20Sopenharmony_ci		errstr, (void *)hp_sdc.base_io, hp_sdc.irq, hp_sdc.nmi);
9228c2ecf20Sopenharmony_ci	hp_sdc.dev = NULL;
9238c2ecf20Sopenharmony_ci
9248c2ecf20Sopenharmony_ci	return hp_sdc.dev_err;
9258c2ecf20Sopenharmony_ci}
9268c2ecf20Sopenharmony_ci
9278c2ecf20Sopenharmony_ci#if defined(__hppa__)
9288c2ecf20Sopenharmony_ci
9298c2ecf20Sopenharmony_cistatic void request_module_delayed(struct work_struct *work)
9308c2ecf20Sopenharmony_ci{
9318c2ecf20Sopenharmony_ci	request_module("hp_sdc_mlc");
9328c2ecf20Sopenharmony_ci}
9338c2ecf20Sopenharmony_ci
9348c2ecf20Sopenharmony_cistatic int __init hp_sdc_init_hppa(struct parisc_device *d)
9358c2ecf20Sopenharmony_ci{
9368c2ecf20Sopenharmony_ci	int ret;
9378c2ecf20Sopenharmony_ci
9388c2ecf20Sopenharmony_ci	if (!d)
9398c2ecf20Sopenharmony_ci		return 1;
9408c2ecf20Sopenharmony_ci	if (hp_sdc.dev != NULL)
9418c2ecf20Sopenharmony_ci		return 1;	/* We only expect one SDC */
9428c2ecf20Sopenharmony_ci
9438c2ecf20Sopenharmony_ci	hp_sdc.dev		= d;
9448c2ecf20Sopenharmony_ci	hp_sdc.irq		= d->irq;
9458c2ecf20Sopenharmony_ci	hp_sdc.nmi		= d->aux_irq;
9468c2ecf20Sopenharmony_ci	hp_sdc.base_io		= d->hpa.start;
9478c2ecf20Sopenharmony_ci	hp_sdc.data_io		= d->hpa.start + 0x800;
9488c2ecf20Sopenharmony_ci	hp_sdc.status_io	= d->hpa.start + 0x801;
9498c2ecf20Sopenharmony_ci
9508c2ecf20Sopenharmony_ci	INIT_DELAYED_WORK(&moduleloader_work, request_module_delayed);
9518c2ecf20Sopenharmony_ci
9528c2ecf20Sopenharmony_ci	ret = hp_sdc_init();
9538c2ecf20Sopenharmony_ci	/* after successful initialization give SDC some time to settle
9548c2ecf20Sopenharmony_ci	 * and then load the hp_sdc_mlc upper layer driver */
9558c2ecf20Sopenharmony_ci	if (!ret)
9568c2ecf20Sopenharmony_ci		schedule_delayed_work(&moduleloader_work,
9578c2ecf20Sopenharmony_ci			msecs_to_jiffies(2000));
9588c2ecf20Sopenharmony_ci
9598c2ecf20Sopenharmony_ci	return ret;
9608c2ecf20Sopenharmony_ci}
9618c2ecf20Sopenharmony_ci
9628c2ecf20Sopenharmony_ci#endif /* __hppa__ */
9638c2ecf20Sopenharmony_ci
9648c2ecf20Sopenharmony_cistatic void hp_sdc_exit(void)
9658c2ecf20Sopenharmony_ci{
9668c2ecf20Sopenharmony_ci	/* do nothing if we don't have a SDC */
9678c2ecf20Sopenharmony_ci	if (!hp_sdc.dev)
9688c2ecf20Sopenharmony_ci		return;
9698c2ecf20Sopenharmony_ci
9708c2ecf20Sopenharmony_ci	write_lock_irq(&hp_sdc.lock);
9718c2ecf20Sopenharmony_ci
9728c2ecf20Sopenharmony_ci	/* Turn off all maskable "sub-function" irq's. */
9738c2ecf20Sopenharmony_ci	hp_sdc_spin_ibf();
9748c2ecf20Sopenharmony_ci	sdc_writeb(HP_SDC_CMD_SET_IM | HP_SDC_IM_MASK, hp_sdc.status_io);
9758c2ecf20Sopenharmony_ci
9768c2ecf20Sopenharmony_ci	/* Wait until we know this has been processed by the i8042 */
9778c2ecf20Sopenharmony_ci	hp_sdc_spin_ibf();
9788c2ecf20Sopenharmony_ci
9798c2ecf20Sopenharmony_ci	free_irq(hp_sdc.nmi, &hp_sdc);
9808c2ecf20Sopenharmony_ci	free_irq(hp_sdc.irq, &hp_sdc);
9818c2ecf20Sopenharmony_ci	write_unlock_irq(&hp_sdc.lock);
9828c2ecf20Sopenharmony_ci
9838c2ecf20Sopenharmony_ci	del_timer_sync(&hp_sdc.kicker);
9848c2ecf20Sopenharmony_ci
9858c2ecf20Sopenharmony_ci	tasklet_kill(&hp_sdc.task);
9868c2ecf20Sopenharmony_ci
9878c2ecf20Sopenharmony_ci#if defined(__hppa__)
9888c2ecf20Sopenharmony_ci	cancel_delayed_work_sync(&moduleloader_work);
9898c2ecf20Sopenharmony_ci	if (unregister_parisc_driver(&hp_sdc_driver))
9908c2ecf20Sopenharmony_ci		printk(KERN_WARNING PREFIX "Error unregistering HP SDC");
9918c2ecf20Sopenharmony_ci#endif
9928c2ecf20Sopenharmony_ci}
9938c2ecf20Sopenharmony_ci
9948c2ecf20Sopenharmony_cistatic int __init hp_sdc_register(void)
9958c2ecf20Sopenharmony_ci{
9968c2ecf20Sopenharmony_ci	hp_sdc_transaction tq_init;
9978c2ecf20Sopenharmony_ci	uint8_t tq_init_seq[5];
9988c2ecf20Sopenharmony_ci	struct semaphore tq_init_sem;
9998c2ecf20Sopenharmony_ci#if defined(__mc68000__)
10008c2ecf20Sopenharmony_ci	unsigned char i;
10018c2ecf20Sopenharmony_ci#endif
10028c2ecf20Sopenharmony_ci
10038c2ecf20Sopenharmony_ci	if (hp_sdc_disabled) {
10048c2ecf20Sopenharmony_ci		printk(KERN_WARNING PREFIX "HP SDC driver disabled by no_hpsdc=1.\n");
10058c2ecf20Sopenharmony_ci		return -ENODEV;
10068c2ecf20Sopenharmony_ci	}
10078c2ecf20Sopenharmony_ci
10088c2ecf20Sopenharmony_ci	hp_sdc.dev = NULL;
10098c2ecf20Sopenharmony_ci	hp_sdc.dev_err = 0;
10108c2ecf20Sopenharmony_ci#if defined(__hppa__)
10118c2ecf20Sopenharmony_ci	if (register_parisc_driver(&hp_sdc_driver)) {
10128c2ecf20Sopenharmony_ci		printk(KERN_WARNING PREFIX "Error registering SDC with system bus tree.\n");
10138c2ecf20Sopenharmony_ci		return -ENODEV;
10148c2ecf20Sopenharmony_ci	}
10158c2ecf20Sopenharmony_ci#elif defined(__mc68000__)
10168c2ecf20Sopenharmony_ci	if (!MACH_IS_HP300)
10178c2ecf20Sopenharmony_ci	    return -ENODEV;
10188c2ecf20Sopenharmony_ci
10198c2ecf20Sopenharmony_ci	hp_sdc.irq	 = 1;
10208c2ecf20Sopenharmony_ci	hp_sdc.nmi	 = 7;
10218c2ecf20Sopenharmony_ci	hp_sdc.base_io	 = (unsigned long) 0xf0428000;
10228c2ecf20Sopenharmony_ci	hp_sdc.data_io	 = (unsigned long) hp_sdc.base_io + 1;
10238c2ecf20Sopenharmony_ci	hp_sdc.status_io = (unsigned long) hp_sdc.base_io + 3;
10248c2ecf20Sopenharmony_ci	if (!copy_from_kernel_nofault(&i, (unsigned char *)hp_sdc.data_io, 1))
10258c2ecf20Sopenharmony_ci		hp_sdc.dev = (void *)1;
10268c2ecf20Sopenharmony_ci	hp_sdc.dev_err   = hp_sdc_init();
10278c2ecf20Sopenharmony_ci#endif
10288c2ecf20Sopenharmony_ci	if (hp_sdc.dev == NULL) {
10298c2ecf20Sopenharmony_ci		printk(KERN_WARNING PREFIX "No SDC found.\n");
10308c2ecf20Sopenharmony_ci		return hp_sdc.dev_err;
10318c2ecf20Sopenharmony_ci	}
10328c2ecf20Sopenharmony_ci
10338c2ecf20Sopenharmony_ci	sema_init(&tq_init_sem, 0);
10348c2ecf20Sopenharmony_ci
10358c2ecf20Sopenharmony_ci	tq_init.actidx		= 0;
10368c2ecf20Sopenharmony_ci	tq_init.idx		= 1;
10378c2ecf20Sopenharmony_ci	tq_init.endidx		= 5;
10388c2ecf20Sopenharmony_ci	tq_init.seq		= tq_init_seq;
10398c2ecf20Sopenharmony_ci	tq_init.act.semaphore	= &tq_init_sem;
10408c2ecf20Sopenharmony_ci
10418c2ecf20Sopenharmony_ci	tq_init_seq[0] =
10428c2ecf20Sopenharmony_ci		HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN | HP_SDC_ACT_SEMAPHORE;
10438c2ecf20Sopenharmony_ci	tq_init_seq[1] = HP_SDC_CMD_READ_KCC;
10448c2ecf20Sopenharmony_ci	tq_init_seq[2] = 1;
10458c2ecf20Sopenharmony_ci	tq_init_seq[3] = 0;
10468c2ecf20Sopenharmony_ci	tq_init_seq[4] = 0;
10478c2ecf20Sopenharmony_ci
10488c2ecf20Sopenharmony_ci	hp_sdc_enqueue_transaction(&tq_init);
10498c2ecf20Sopenharmony_ci
10508c2ecf20Sopenharmony_ci	down(&tq_init_sem);
10518c2ecf20Sopenharmony_ci	up(&tq_init_sem);
10528c2ecf20Sopenharmony_ci
10538c2ecf20Sopenharmony_ci	if ((tq_init_seq[0] & HP_SDC_ACT_DEAD) == HP_SDC_ACT_DEAD) {
10548c2ecf20Sopenharmony_ci		printk(KERN_WARNING PREFIX "Error reading config byte.\n");
10558c2ecf20Sopenharmony_ci		hp_sdc_exit();
10568c2ecf20Sopenharmony_ci		return -ENODEV;
10578c2ecf20Sopenharmony_ci	}
10588c2ecf20Sopenharmony_ci	hp_sdc.r11 = tq_init_seq[4];
10598c2ecf20Sopenharmony_ci	if (hp_sdc.r11 & HP_SDC_CFG_NEW) {
10608c2ecf20Sopenharmony_ci		const char *str;
10618c2ecf20Sopenharmony_ci		printk(KERN_INFO PREFIX "New style SDC\n");
10628c2ecf20Sopenharmony_ci		tq_init_seq[1] = HP_SDC_CMD_READ_XTD;
10638c2ecf20Sopenharmony_ci		tq_init.actidx		= 0;
10648c2ecf20Sopenharmony_ci		tq_init.idx		= 1;
10658c2ecf20Sopenharmony_ci		down(&tq_init_sem);
10668c2ecf20Sopenharmony_ci		hp_sdc_enqueue_transaction(&tq_init);
10678c2ecf20Sopenharmony_ci		down(&tq_init_sem);
10688c2ecf20Sopenharmony_ci		up(&tq_init_sem);
10698c2ecf20Sopenharmony_ci		if ((tq_init_seq[0] & HP_SDC_ACT_DEAD) == HP_SDC_ACT_DEAD) {
10708c2ecf20Sopenharmony_ci			printk(KERN_WARNING PREFIX "Error reading extended config byte.\n");
10718c2ecf20Sopenharmony_ci			return -ENODEV;
10728c2ecf20Sopenharmony_ci		}
10738c2ecf20Sopenharmony_ci		hp_sdc.r7e = tq_init_seq[4];
10748c2ecf20Sopenharmony_ci		HP_SDC_XTD_REV_STRINGS(hp_sdc.r7e & HP_SDC_XTD_REV, str)
10758c2ecf20Sopenharmony_ci		printk(KERN_INFO PREFIX "Revision: %s\n", str);
10768c2ecf20Sopenharmony_ci		if (hp_sdc.r7e & HP_SDC_XTD_BEEPER)
10778c2ecf20Sopenharmony_ci			printk(KERN_INFO PREFIX "TI SN76494 beeper present\n");
10788c2ecf20Sopenharmony_ci		if (hp_sdc.r7e & HP_SDC_XTD_BBRTC)
10798c2ecf20Sopenharmony_ci			printk(KERN_INFO PREFIX "OKI MSM-58321 BBRTC present\n");
10808c2ecf20Sopenharmony_ci		printk(KERN_INFO PREFIX "Spunking the self test register to force PUP "
10818c2ecf20Sopenharmony_ci		       "on next firmware reset.\n");
10828c2ecf20Sopenharmony_ci		tq_init_seq[0] = HP_SDC_ACT_PRECMD |
10838c2ecf20Sopenharmony_ci			HP_SDC_ACT_DATAOUT | HP_SDC_ACT_SEMAPHORE;
10848c2ecf20Sopenharmony_ci		tq_init_seq[1] = HP_SDC_CMD_SET_STR;
10858c2ecf20Sopenharmony_ci		tq_init_seq[2] = 1;
10868c2ecf20Sopenharmony_ci		tq_init_seq[3] = 0;
10878c2ecf20Sopenharmony_ci		tq_init.actidx		= 0;
10888c2ecf20Sopenharmony_ci		tq_init.idx		= 1;
10898c2ecf20Sopenharmony_ci		tq_init.endidx		= 4;
10908c2ecf20Sopenharmony_ci		down(&tq_init_sem);
10918c2ecf20Sopenharmony_ci		hp_sdc_enqueue_transaction(&tq_init);
10928c2ecf20Sopenharmony_ci		down(&tq_init_sem);
10938c2ecf20Sopenharmony_ci		up(&tq_init_sem);
10948c2ecf20Sopenharmony_ci	} else
10958c2ecf20Sopenharmony_ci		printk(KERN_INFO PREFIX "Old style SDC (1820-%s).\n",
10968c2ecf20Sopenharmony_ci		       (hp_sdc.r11 & HP_SDC_CFG_REV) ? "3300" : "2564/3087");
10978c2ecf20Sopenharmony_ci
10988c2ecf20Sopenharmony_ci        return 0;
10998c2ecf20Sopenharmony_ci}
11008c2ecf20Sopenharmony_ci
11018c2ecf20Sopenharmony_cimodule_init(hp_sdc_register);
11028c2ecf20Sopenharmony_cimodule_exit(hp_sdc_exit);
11038c2ecf20Sopenharmony_ci
11048c2ecf20Sopenharmony_ci/* Timing notes:  These measurements taken on my 64MHz 7100-LC (715/64)
11058c2ecf20Sopenharmony_ci *                                              cycles cycles-adj    time
11068c2ecf20Sopenharmony_ci * between two consecutive mfctl(16)'s:              4        n/a    63ns
11078c2ecf20Sopenharmony_ci * hp_sdc_spin_ibf when idle:                      119        115   1.7us
11088c2ecf20Sopenharmony_ci * gsc_writeb status register:                      83         79   1.2us
11098c2ecf20Sopenharmony_ci * IBF to clear after sending SET_IM:             6204       6006    93us
11108c2ecf20Sopenharmony_ci * IBF to clear after sending LOAD_RT:            4467       4352    68us
11118c2ecf20Sopenharmony_ci * IBF to clear after sending two LOAD_RTs:      18974      18859   295us
11128c2ecf20Sopenharmony_ci * READ_T1, read status/data, IRQ, call handler: 35564        n/a   556us
11138c2ecf20Sopenharmony_ci * cmd to ~IBF READ_T1 2nd time right after:   5158403        n/a    81ms
11148c2ecf20Sopenharmony_ci * between IRQ received and ~IBF for above:    2578877        n/a    40ms
11158c2ecf20Sopenharmony_ci *
11168c2ecf20Sopenharmony_ci * Performance stats after a run of this module configuring HIL and
11178c2ecf20Sopenharmony_ci * receiving a few mouse events:
11188c2ecf20Sopenharmony_ci *
11198c2ecf20Sopenharmony_ci * status in8  282508 cycles 7128 calls
11208c2ecf20Sopenharmony_ci * status out8   8404 cycles  341 calls
11218c2ecf20Sopenharmony_ci * data out8     1734 cycles   78 calls
11228c2ecf20Sopenharmony_ci * isr         174324 cycles  617 calls (includes take)
11238c2ecf20Sopenharmony_ci * take          1241 cycles    2 calls
11248c2ecf20Sopenharmony_ci * put        1411504 cycles 6937 calls
11258c2ecf20Sopenharmony_ci * task       1655209 cycles 6937 calls (includes put)
11268c2ecf20Sopenharmony_ci *
11278c2ecf20Sopenharmony_ci */
1128