⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 hp_sdc.c

📁 QQ2440板子
💻 C
📖 第 1 页 / 共 2 页
字号:
/* * HP i8042-based System Device Controller driver. * * Copyright (c) 2001 Brian S. Julin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright *    notice, this list of conditions, and the following disclaimer, *    without modification. * 2. The name of the author may not be used to endorse or promote products *    derived from this software without specific prior written permission. * * Alternatively, this software may be distributed under the terms of the * GNU General Public License ("GPL"). * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * * References: * System Device Controller Microprocessor Firmware Theory of Operation *      for Part Number 1820-4784 Revision B.  Dwg No. A-1820-4784-2 * Helge Deller's original hilkbd.c port for PA-RISC. * * * Driver theory of operation: * * hp_sdc_put does all writing to the SDC.  ISR can run on a different  * CPU than hp_sdc_put, but only one CPU runs hp_sdc_put at a time  * (it cannot really benefit from SMP anyway.)  A tasket fit this perfectly. * * All data coming back from the SDC is sent via interrupt and can be read  * fully in the ISR, so there are no latency/throughput problems there.   * The problem is with output, due to the slow clock speed of the SDC  * compared to the CPU.  This should not be too horrible most of the time,  * but if used with HIL devices that support the multibyte transfer command,  * keeping outbound throughput flowing at the 6500KBps that the HIL is  * capable of is more than can be done at HZ=100. * * Busy polling for IBF clear wastes CPU cycles and bus cycles.  hp_sdc.ibf  * is set to 0 when the IBF flag in the status register has cleared.  ISR  * may do this, and may also access the parts of queued transactions related  * to reading data back from the SDC, but otherwise will not touch the  * hp_sdc state. Whenever a register is written hp_sdc.ibf is set to 1. * * The i8042 write index and the values in the 4-byte input buffer * starting at 0x70 are kept track of in hp_sdc.wi, and .r7[], respectively, * to minimize the amount of IO needed to the SDC.  However these values  * do not need to be locked since they are only ever accessed by hp_sdc_put. * * A timer task schedules the tasklet once per second just to make * sure it doesn't freeze up and to allow for bad reads to time out. */#include <linux/hp_sdc.h>#include <linux/sched.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/module.h>#include <linux/ioport.h>#include <linux/time.h>#include <linux/slab.h>#include <linux/hil.h>#include <asm/io.h>#include <asm/system.h>/* Machine-specific abstraction */#if defined(__hppa__)# include <asm/parisc-device.h># define sdc_readb(p)		gsc_readb(p)# define sdc_writeb(v,p)	gsc_writeb((v),(p))#elif defined(__mc68000__)# include <asm/uaccess.h># define sdc_readb(p)		in_8(p)# define sdc_writeb(v,p)	out_8((p),(v))#else# error "HIL is not supported on this platform"#endif#define PREFIX "HP SDC: "MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>");MODULE_DESCRIPTION("HP i8042-based SDC Driver");MODULE_LICENSE("Dual BSD/GPL");EXPORT_SYMBOL(hp_sdc_request_timer_irq);EXPORT_SYMBOL(hp_sdc_request_hil_irq);EXPORT_SYMBOL(hp_sdc_request_cooked_irq);EXPORT_SYMBOL(hp_sdc_release_timer_irq);EXPORT_SYMBOL(hp_sdc_release_hil_irq);EXPORT_SYMBOL(hp_sdc_release_cooked_irq);EXPORT_SYMBOL(hp_sdc_enqueue_transaction);EXPORT_SYMBOL(hp_sdc_dequeue_transaction);static hp_i8042_sdc	hp_sdc;	/* All driver state is kept in here. *//*************** primitives for use in any context *********************/static inline uint8_t hp_sdc_status_in8 (void) {	uint8_t status;	unsigned long flags;	write_lock_irqsave(&hp_sdc.ibf_lock, flags);	status = sdc_readb(hp_sdc.status_io);	if (!(status & HP_SDC_STATUS_IBF)) hp_sdc.ibf = 0;	write_unlock_irqrestore(&hp_sdc.ibf_lock, flags);	return status;}static inline uint8_t hp_sdc_data_in8 (void) {	return sdc_readb(hp_sdc.data_io); }static inline void hp_sdc_status_out8 (uint8_t val) {	unsigned long flags;	write_lock_irqsave(&hp_sdc.ibf_lock, flags);	hp_sdc.ibf = 1;	if ((val & 0xf0) == 0xe0) hp_sdc.wi = 0xff;	sdc_writeb(val, hp_sdc.status_io);	write_unlock_irqrestore(&hp_sdc.ibf_lock, flags);}static inline void hp_sdc_data_out8 (uint8_t val) {	unsigned long flags;	write_lock_irqsave(&hp_sdc.ibf_lock, flags);	hp_sdc.ibf = 1;	sdc_writeb(val, hp_sdc.data_io);	write_unlock_irqrestore(&hp_sdc.ibf_lock, flags);}/*	Care must be taken to only invoke hp_sdc_spin_ibf when  *	absolutely needed, or in rarely invoked subroutines.   *	Not only does it waste CPU cycles, it also wastes bus cycles.  */static inline void hp_sdc_spin_ibf(void) {	unsigned long flags;	rwlock_t *lock;	lock = &hp_sdc.ibf_lock;	read_lock_irqsave(lock, flags);	if (!hp_sdc.ibf) {		read_unlock_irqrestore(lock, flags);		return;	}	read_unlock(lock);	write_lock(lock);	while (sdc_readb(hp_sdc.status_io) & HP_SDC_STATUS_IBF) {};	hp_sdc.ibf = 0;	write_unlock_irqrestore(lock, flags);}/************************ Interrupt context functions ************************/static void hp_sdc_take (int irq, void *dev_id, uint8_t status, uint8_t data) {	hp_sdc_transaction *curr;	read_lock(&hp_sdc.rtq_lock);	if (hp_sdc.rcurr < 0) {	  	read_unlock(&hp_sdc.rtq_lock);		return;	}	curr = hp_sdc.tq[hp_sdc.rcurr];	read_unlock(&hp_sdc.rtq_lock);	curr->seq[curr->idx++] = status;	curr->seq[curr->idx++] = data;	hp_sdc.rqty -= 2;	do_gettimeofday(&hp_sdc.rtv);	if (hp_sdc.rqty <= 0) {		/* All data has been gathered. */		if(curr->seq[curr->actidx] & HP_SDC_ACT_SEMAPHORE) {			if (curr->act.semaphore) up(curr->act.semaphore);		}		if(curr->seq[curr->actidx] & HP_SDC_ACT_CALLBACK) {			if (curr->act.irqhook)				curr->act.irqhook(irq, dev_id, status, data);		}		curr->actidx = curr->idx;		curr->idx++;		/* Return control of this transaction */		write_lock(&hp_sdc.rtq_lock);		hp_sdc.rcurr = -1; 		hp_sdc.rqty = 0;		write_unlock(&hp_sdc.rtq_lock);		tasklet_schedule(&hp_sdc.task);	}}static irqreturn_t hp_sdc_isr(int irq, void *dev_id, struct pt_regs * regs) {	uint8_t status, data;	status = hp_sdc_status_in8();	/* Read data unconditionally to advance i8042. */	data =   hp_sdc_data_in8();	/* For now we are ignoring these until we get the SDC to behave. */	if (((status & 0xf1) == 0x51) && data == 0x82) {	  return IRQ_HANDLED;	}	switch(status & HP_SDC_STATUS_IRQMASK) {	      case 0: /* This case is not documented. */		break;	      case HP_SDC_STATUS_USERTIMER:	      case HP_SDC_STATUS_PERIODIC:	      case HP_SDC_STATUS_TIMER:		read_lock(&hp_sdc.hook_lock);	      	if (hp_sdc.timer != NULL)			hp_sdc.timer(irq, dev_id, status, data);		read_unlock(&hp_sdc.hook_lock);		break;	      case HP_SDC_STATUS_REG:		hp_sdc_take(irq, dev_id, status, data);		break;	      case HP_SDC_STATUS_HILCMD:	      case HP_SDC_STATUS_HILDATA:		read_lock(&hp_sdc.hook_lock);		if (hp_sdc.hil != NULL)			hp_sdc.hil(irq, dev_id, status, data);		read_unlock(&hp_sdc.hook_lock);		break;	      case HP_SDC_STATUS_PUP:		read_lock(&hp_sdc.hook_lock);		if (hp_sdc.pup != NULL)			hp_sdc.pup(irq, dev_id, status, data);		else printk(KERN_INFO PREFIX "HP SDC reports successful PUP.\n");		read_unlock(&hp_sdc.hook_lock);		break;	      default:		read_lock(&hp_sdc.hook_lock);		if (hp_sdc.cooked != NULL)			hp_sdc.cooked(irq, dev_id, status, data);		read_unlock(&hp_sdc.hook_lock);		break;	}	return IRQ_HANDLED;}static irqreturn_t hp_sdc_nmisr(int irq, void *dev_id, struct pt_regs * regs) {	int status;		status = hp_sdc_status_in8();	printk(KERN_WARNING PREFIX "NMI !\n");#if 0		if (status & HP_SDC_NMISTATUS_FHS) {		read_lock(&hp_sdc.hook_lock);	      	if (hp_sdc.timer != NULL)			hp_sdc.timer(irq, dev_id, status, 0);		read_unlock(&hp_sdc.hook_lock);	}	else {		/* TODO: pass this on to the HIL handler, or do SAK here? */		printk(KERN_WARNING PREFIX "HIL NMI\n");	}#endif	return IRQ_HANDLED;}/***************** Kernel (tasklet) context functions ****************/unsigned long hp_sdc_put(void);static void hp_sdc_tasklet(unsigned long foo) {	write_lock_irq(&hp_sdc.rtq_lock);	if (hp_sdc.rcurr >= 0) {		struct timeval tv;		do_gettimeofday(&tv);		if (tv.tv_sec > hp_sdc.rtv.tv_sec) tv.tv_usec += 1000000;		if (tv.tv_usec - hp_sdc.rtv.tv_usec > HP_SDC_MAX_REG_DELAY) {			hp_sdc_transaction *curr;			uint8_t tmp;			curr = hp_sdc.tq[hp_sdc.rcurr];			/* If this turns out to be a normal failure mode			 * we'll need to figure out a way to communicate			 * it back to the application. and be less verbose.			 */			printk(KERN_WARNING PREFIX "read timeout (%ius)!\n",			       tv.tv_usec - hp_sdc.rtv.tv_usec);			curr->idx += hp_sdc.rqty;			hp_sdc.rqty = 0;			tmp = curr->seq[curr->actidx];			curr->seq[curr->actidx] |= HP_SDC_ACT_DEAD;			if(tmp & HP_SDC_ACT_SEMAPHORE) {				if (curr->act.semaphore) 					up(curr->act.semaphore);			}			if(tmp & HP_SDC_ACT_CALLBACK) {				/* Note this means that irqhooks may be called				 * in tasklet/bh context.				 */				if (curr->act.irqhook) 					curr->act.irqhook(0, 0, 0, 0);			}			curr->actidx = curr->idx;			curr->idx++;			hp_sdc.rcurr = -1; 		}	}	write_unlock_irq(&hp_sdc.rtq_lock);	hp_sdc_put();}unsigned long hp_sdc_put(void) {	hp_sdc_transaction *curr;	uint8_t act;	int idx, curridx;	int limit = 0;	write_lock(&hp_sdc.lock);	/* If i8042 buffers are full, we cannot do anything that	   requires output, so we skip to the administrativa. */	if (hp_sdc.ibf) {		hp_sdc_status_in8();		if (hp_sdc.ibf) goto finish;	} anew:	/* See if we are in the middle of a sequence. */	if (hp_sdc.wcurr < 0) hp_sdc.wcurr = 0;	read_lock_irq(&hp_sdc.rtq_lock);	if (hp_sdc.rcurr == hp_sdc.wcurr) hp_sdc.wcurr++;	read_unlock_irq(&hp_sdc.rtq_lock);	if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0;	curridx = hp_sdc.wcurr;	if (hp_sdc.tq[curridx] != NULL) goto start;	while (++curridx != hp_sdc.wcurr) {		if (curridx >= HP_SDC_QUEUE_LEN) {			curridx = -1; /* Wrap to top */			continue;		}		read_lock_irq(&hp_sdc.rtq_lock);		if (hp_sdc.rcurr == curridx) {			read_unlock_irq(&hp_sdc.rtq_lock);			continue;		}		read_unlock_irq(&hp_sdc.rtq_lock);		if (hp_sdc.tq[curridx] != NULL) break; /* Found one. */	}	if (curridx == hp_sdc.wcurr) { /* There's nothing queued to do. */		curridx = -1;	}	hp_sdc.wcurr = curridx; start:	/* Check to see if the interrupt mask needs to be set. */	if (hp_sdc.set_im) {		hp_sdc_status_out8(hp_sdc.im | HP_SDC_CMD_SET_IM);		hp_sdc.set_im = 0;		goto finish;	}	if (hp_sdc.wcurr == -1) goto done;	curr = hp_sdc.tq[curridx];	idx = curr->actidx;	if (curr->actidx >= curr->endidx) {		hp_sdc.tq[curridx] = NULL;		/* Interleave outbound data between the transactions. */		hp_sdc.wcurr++;		if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0;		goto finish;		}	act = curr->seq[idx];	idx++;	if (curr->idx >= curr->endidx) {		if (act & HP_SDC_ACT_DEALLOC) kfree(curr);		hp_sdc.tq[curridx] = NULL;		/* Interleave outbound data between the transactions. */		hp_sdc.wcurr++;		if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0;		goto finish;		}	while (act & HP_SDC_ACT_PRECMD) {		if (curr->idx != idx) {			idx++;			act &= ~HP_SDC_ACT_PRECMD;			break;		}		hp_sdc_status_out8(curr->seq[idx]);		curr->idx++;		/* act finished? */		if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_PRECMD)		  goto actdone;		/* skip quantity field if data-out sequence follows. */		if (act & HP_SDC_ACT_DATAOUT) curr->idx++;		goto finish;	}	if (act & HP_SDC_ACT_DATAOUT) {		int qty;		qty = curr->seq[idx];		idx++;		if (curr->idx - idx < qty) {			hp_sdc_data_out8(curr->seq[curr->idx]);			curr->idx++;			/* act finished? */			if ((curr->idx - idx >= qty) && 			    ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAOUT))				goto actdone;			goto finish;		}		idx += qty;		act &= ~HP_SDC_ACT_DATAOUT;	}	else while (act & HP_SDC_ACT_DATAREG) {		int mask;		uint8_t w7[4];		mask = curr->seq[idx];		if (idx != curr->idx) {			idx++;			idx += !!(mask & 1);			idx += !!(mask & 2);			idx += !!(mask & 4);			idx += !!(mask & 8);			act &= ~HP_SDC_ACT_DATAREG;			break;		}				w7[0] = (mask & 1) ? curr->seq[++idx] : hp_sdc.r7[0];		w7[1] = (mask & 2) ? curr->seq[++idx] : hp_sdc.r7[1];		w7[2] = (mask & 4) ? curr->seq[++idx] : hp_sdc.r7[2];		w7[3] = (mask & 8) ? curr->seq[++idx] : hp_sdc.r7[3];				if (hp_sdc.wi > 0x73 || hp_sdc.wi < 0x70 ||		        w7[hp_sdc.wi-0x70] == hp_sdc.r7[hp_sdc.wi-0x70]) {			int i = 0;			/* Need to point the write index register */				while ((i < 4) && w7[i] == hp_sdc.r7[i]) i++;			if (i < 4) {				hp_sdc_status_out8(HP_SDC_CMD_SET_D0 + i);				hp_sdc.wi = 0x70 + i;				goto finish;			}			idx++;			if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAREG)				goto actdone;			curr->idx = idx;			act &= ~HP_SDC_ACT_DATAREG;			break;		}		hp_sdc_data_out8(w7[hp_sdc.wi - 0x70]);		hp_sdc.r7[hp_sdc.wi - 0x70] = w7[hp_sdc.wi - 0x70];		hp_sdc.wi++; /* write index register autoincrements */		{			int i = 0;			while ((i < 4) && w7[i] == hp_sdc.r7[i]) i++;			if (i >= 4) {				curr->idx = idx + 1;				if ((act & HP_SDC_ACT_DURING) == 				    HP_SDC_ACT_DATAREG)				        goto actdone;			}		}		goto finish;	}	/* We don't go any further in the command if there is a pending read,	   because we don't want interleaved results. */	read_lock_irq(&hp_sdc.rtq_lock);	if (hp_sdc.rcurr >= 0) {		read_unlock_irq(&hp_sdc.rtq_lock);		goto finish;	}	read_unlock_irq(&hp_sdc.rtq_lock);	if (act & HP_SDC_ACT_POSTCMD) {	  	uint8_t postcmd;		/* curr->idx should == idx at this point. */		postcmd = curr->seq[idx];		curr->idx++;		if (act & HP_SDC_ACT_DATAIN) {			/* Start a new read */	  		hp_sdc.rqty = curr->seq[curr->idx];			do_gettimeofday(&hp_sdc.rtv);			curr->idx++;			/* Still need to lock here in case of spurious irq. */			write_lock_irq(&hp_sdc.rtq_lock);			hp_sdc.rcurr = curridx; 			write_unlock_irq(&hp_sdc.rtq_lock);			hp_sdc_status_out8(postcmd);			goto finish;		}		hp_sdc_status_out8(postcmd);		goto actdone;	}actdone:	if (act & HP_SDC_ACT_SEMAPHORE) {		up(curr->act.semaphore);	}	else if (act & HP_SDC_ACT_CALLBACK) {		curr->act.irqhook(0,0,0,0);

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -