Psion SSI driver (touchpad & voltage) patch for linux/drivers/ssi/ files

This patch contains updates to the following files:
./drivers/ssi/Config.in
./drivers/ssi/Makefile
./drivers/ssi/adc7843.c
./drivers/ssi/ps5mx_ssi.c
./drivers/ssi/ssi_bus.h
./drivers/ssi/ssi_dev.h


diff -urN -X /home/arm/dontdiff_tml_arm /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/Config.in linux-2.4.19-rmk2/drivers/ssi/Config.in
diff -urN -X /home/arm/dontdiff_tml_arm /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/Config.in linux-2.4.19-rmk2/drivers/ssi/Config.in
--- /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/Config.in	2002-10-02 22:05:26.000000000 -0700
+++ linux-2.4.19-rmk2/drivers/ssi/Config.in	2002-10-02 22:07:30.000000000 -0700
@@ -5,7 +5,9 @@
 
 comment 'SSI Bus Drivers'
 dep_tristate '  CLPS711X SSI support' CONFIG_SSI_CLPS711X $CONFIG_SSI $CONFIG_ARCH_CLPS711X
+dep_tristate '  Psion 5mx SSI support' CONFIG_SSI_PS5MX $CONFIG_SSI $CONFIG_ARCH_PSIONW
 
 comment 'SSI Device Drivers'
 dep_tristate '  JUNO keyboard support' CONFIG_SSI_JUNO $CONFIG_SSI
+dep_tristate '  Touchpanel support'    CONFIG_SSI_ADC7843 $CONFIG_SSI
 endmenu
diff -urN -X /home/arm/dontdiff_tml_arm /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/Makefile linux-2.4.19-rmk2/drivers/ssi/Makefile
diff -urN -X /home/arm/dontdiff_tml_arm /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/Makefile linux-2.4.19-rmk2/drivers/ssi/Makefile
--- /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/Makefile	2002-10-02 22:05:26.000000000 -0700
+++ linux-2.4.19-rmk2/drivers/ssi/Makefile	2002-10-02 22:07:30.000000000 -0700
@@ -21,7 +21,9 @@
 
 obj-$(CONFIG_SSI)		+= ssi_core.o
 obj-$(CONFIG_SSI_CLPS711X)	+= clps711x_ssi1.o
-obj-y				+= juno.o
+obj-$(CONFIG_SSI_PS5MX)         += ps5mx_ssi.o
+obj-$(CONFIG_SSI_ADC7843)       += adc7843.o
+obj-$(CONFIG_SSI_JUNO)		+= juno.o
 
 # Extract lists of the multi-part drivers.
 # The 'int-*' lists are intermediate files used to build the multi's.
diff -urN -X /home/arm/dontdiff_tml_arm /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/adc7843.c linux-2.4.19-rmk2/drivers/ssi/adc7843.c
diff -urN -X /home/arm/dontdiff_tml_arm /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/adc7843.c linux-2.4.19-rmk2/drivers/ssi/adc7843.c
--- /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/adc7843.c	1969-12-31 16:00:00.000000000 -0800
+++ linux-2.4.19-rmk2/drivers/ssi/adc7843.c	2002-10-02 22:07:30.000000000 -0700
@@ -0,0 +1,297 @@
+/* drivers/ssi/adc7843.c
+ *
+ * Driver for the 7843 ADC found on the Psion 5mx 
+ * Copyright 2001 Shane R. Nay <shane@minirl.com>
+ *
+ * Polling support by Thomas A. de Ruiter <thomas@de-ruiter.cx>
+ *
+ * Some code is borrowed from other parts of the kernel,
+ * the largest body of such code was grabbed from:
+ * drivers/char/qpmouse.c
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/poll.h>
+
+#include <asm/mach-types.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+#include "ssi_dev.h"
+#include "ssi_bus.h"
+
+
+#define AD_QUEUE_SIZE	32
+#define PEN_DOWN        1<<31
+
+struct ad_queue {
+	u_int head;
+	u_int tail;
+	wait_queue_head_t proc_list;
+	struct fasync_struct *fasync;
+	u_int buf[AD_QUEUE_SIZE];
+};
+
+static struct ad_queue * queue;
+static struct timer_list adtimer;
+static int ad_active;
+void ad_bottomhalf(unsigned long);
+DECLARE_TASKLET(adtasklet, ad_bottomhalf, NULL);
+
+/* Each buf u_int composes three pieces of information.
+ * The first is located at bit 31, or 1<<31.  That is whether
+ * or not the pen is down.  The x coord is 12bit and shifted up
+ * in the upper 2 bytes, while the y coord is in the lower 2 bytes
+ * and is also 12bit.
+ *
+ * Every read from this driver should produce *3* shorts, first is
+ * pen status, either 1 or 0.  The other in order are x coord, and
+ * then y coord.  If less than 3 shorts are requested, then it will
+ * give the requested amount of information starting at the top, and
+ * will abandon the rest, and move it's pointer up to the next set
+ * of coordinates.
+ *
+ * It will only store one pen up info set, after that it will just
+ * dump the info into never-never land.
+ */
+
+
+/* Always happens in ISR, no need to deal with concurrency */
+static void add_adqueue(u_int dat) {
+	int head=queue->head;
+	queue->buf[head]=dat;
+	if(head != ((queue->tail-1)&(AD_QUEUE_SIZE-1))) {
+		head++;
+		head &= (AD_QUEUE_SIZE-1);
+	}
+	queue->head=head;
+}
+
+static u_int pop_adqueue(void) {
+	u_int flags;
+	u_int ret;
+	save_flags(flags);
+	cli();
+	ret=queue->buf[queue->tail];
+	queue->tail=(queue->tail+1) & (AD_QUEUE_SIZE-1);
+	restore_flags(flags);
+	return ret;
+}
+
+#define IS_EMPTY(queue) (queue->head==queue->tail)
+
+
+static struct ssi_dev ads7843_dev = {
+	name:		"ADS7843",
+	id:		1,
+	proto:		SSI_MICROWIRE,
+	cfglen:		8,
+	framelen:	12,
+	clkpol:		0,
+	clkfreq:	2500000,
+};
+
+/* Send a request for an x,y pair */
+static void ad_sendreq(unsigned long ptr) {
+	struct ssi_dev* dev=(struct ssi_dev*)ptr;
+	ssi_transmit_data(dev,0xd000);
+	ssi_transmit_data(dev,0x9000);
+}
+
+/* Do the time consuming ISR stuff */
+void ad_bottomhalf(unsigned long private) {
+	/* Wake up userland process, allow that process to
+	 *   restart timer */
+	del_timer(&adtimer);
+
+	if(!(IS_EMPTY(queue)) && ad_active) {
+		kill_fasync(&queue->fasync, SIGIO, POLL_IN);
+		wake_up_interruptible(&queue->proc_list);
+	}
+	if(ad_active) {
+		init_timer(&adtimer);
+		adtimer.function=ad_sendreq;
+		adtimer.data=private;
+		adtimer.expires=jiffies + 10;
+		add_timer(&adtimer);
+	}
+}
+
+/* Recieve each byte off the serial return, called by the SSI bus */
+void ad_rcv(struct ssi_dev* dev, u_int dat) {
+	static u_int x=0x80001000;
+	/* Bit 12 of static x acts as a selector switch between
+	 * whether we're recieving x, or y.  Statics are bad, so
+	 * I cooked up this goofy scheme so as not to need another
+	 * one.  Also bit 31 acts inverse of normal, if it's set,
+	 * then the prior was a pen up event.  We start off the
+	 * action with it being up, and recieving x.
+	 */
+	if(!(x&0x1000)) {
+		if(x==PEN_DOWN) {
+			/* Pen was up on previous, so we
+			 * don't really care.  Most frequent
+			 * case so it lies at the top.
+			 */
+		} else if(x==0) { /* Pen was just released */
+			x|=PEN_DOWN;
+			add_adqueue(0);
+		} else { /* We've got data. */
+			add_adqueue((x<<16)|((~(dat))&0xfff)|(PEN_DOWN));
+			/* You are probably wondering why I inverted y.  Well...,
+			 * don't blame me, Psion inverted in it hardware,
+			 * and I don't want to deal with it in every application
+			 * I write, so I'm doing it at driver level.
+			 */
+			x=0;
+		}
+		x|=0x1000;
+		tasklet_schedule(&adtasklet);
+	} else {
+		x|=dat & 0xfff;
+		x &= ~(0x1000);
+	}
+}
+
+/* Read routine.  Documented in more detail above. */
+static ssize_t read_ad(struct file * file, char * buffer,
+                       size_t count, loff_t *ppos)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	ssize_t i = count;
+	unsigned char c[6];
+	if (IS_EMPTY(queue)) {
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+		add_wait_queue(&queue->proc_list, &wait);
+repeat:
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (IS_EMPTY(queue) && !signal_pending(current)) {
+			schedule();
+			goto repeat;
+		}
+		current->state = TASK_RUNNING;
+		remove_wait_queue(&queue->proc_list, &wait);
+	}
+	while (i > 0 && !(IS_EMPTY(queue))) {
+		u_int coord=pop_adqueue();
+		c[0]=0;
+		if(coord & PEN_DOWN)
+			c[1]=1;
+		else
+			c[1]=0;
+		c[2]=(coord>>24)&0xf;
+		c[3]=(coord>>16)&0xff;
+		c[4]=(coord>>8)&0xff;
+		c[5]=coord&0xff;
+		for(coord=0;coord<6 && i>0; coord++) {
+			put_user(c[coord],buffer++);
+			i--;
+		}
+	}
+	if (count-i) {
+		file->f_dentry->d_inode->i_atime = CURRENT_TIME;
+		return count-i;
+	}
+	if (signal_pending(current))
+		return -ERESTARTSYS;
+	return 0;
+}
+
+/* Open routine, kick starts the timer, and sets active
+ * to true so timer reset routine will flip the timer on
+ * again for the next go round.
+ */
+static int open_ad (struct inode* inode, struct file* file) {
+	if(ad_active)
+		return -EBUSY;
+	ad_active++;
+	init_timer(&adtimer);
+	adtimer.function=ad_sendreq;
+	adtimer.data=(unsigned long)&ads7843_dev;
+	adtimer.expires=jiffies + 10;
+	add_timer(&adtimer);
+	return 0;
+}
+
+/* Asynchronous anyone? :) */
+static int fasync_ad(int fd, struct file *filp, int on)
+{
+	int retval;
+
+	retval = fasync_helper(fd, filp, on, &queue->fasync);
+	if (retval < 0)
+		return retval;
+	return 0;
+}
+
+/* Polling :-) */
+static unsigned int poll_ad(struct file * filp, poll_table * wait)
+{
+//    poll_wait(filp, &queue, wait);
+    if (IS_EMPTY(queue))
+        return 0;
+    else
+        return POLLIN | POLLRDNORM;
+}
+
+
+
+/* Close routine. By setting ad_active to false, basically when
+ * the timer pings around the next time, it will not activate a
+ * new timer next go round.  This allows for a nice "soft landing",
+ * and for all the x/y variables to be saved for next reader, and in
+ * good order.
+ */
+static int release_ad(struct inode * inode, struct file* file) {
+	if(ad_active) {
+		ad_active=0;
+		fasync_ad(-1,file,0);
+	}
+	return 0;
+}
+
+static struct file_operations adc7843_fops = {
+        owner:    THIS_MODULE,
+        read:     read_ad,
+        open:     open_ad,
+        poll:     poll_ad,
+        release:  release_ad,
+        fasync:   fasync_ad,
+        };
+
+static struct miscdevice adc7843_tpanel = {
+	        PS5MX_TPANEL_MINOR, "adc7843", &adc7843_fops
+        };
+
+/* Initialization routine called by bus when registered */
+int ad_init(struct ssi_dev* dev) {
+	if((queue = kmalloc(sizeof(*queue), GFP_KERNEL))==NULL) {
+		printk(KERN_ERR "adc7843: no queue memory.\n");
+		return -ENOMEM;
+	}
+	memset(queue,0,sizeof(*queue));
+	init_waitqueue_head(&queue->proc_list);
+	ssi_select_device(dev->bus,dev);
+	adtasklet.data=(unsigned long)dev;
+	misc_register(&adc7843_tpanel);
+	return 0;
+}
+
+/* Initialization routine called from initialization routine
+ * in device specific bus.
+ */
+void init_adc7843(struct ssi_bus* bus) {
+	ads7843_dev.init=ad_init;
+	ads7843_dev.rcv=ad_rcv;
+	ssi_register_device(bus,&ads7843_dev);
+}
diff -urN -X /home/arm/dontdiff_tml_arm /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/ps5mx_ssi.c linux-2.4.19-rmk2/drivers/ssi/ps5mx_ssi.c
diff -urN -X /home/arm/dontdiff_tml_arm /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/ps5mx_ssi.c linux-2.4.19-rmk2/drivers/ssi/ps5mx_ssi.c
--- /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/ps5mx_ssi.c	1969-12-31 16:00:00.000000000 -0800
+++ linux-2.4.19-rmk2/drivers/ssi/ps5mx_ssi.c	2002-10-02 22:07:30.000000000 -0700
@@ -0,0 +1,213 @@
+/*
+ *  linux/drivers/ssi/ps5mx_ssi.c
+ *
+ * SSI bus driver for the Psion 5MX SSI bus.
+ * 
+ * Copyright (c) 2001 Shane R. Nay (shane@minirl.com)
+ * Based on the clps driver.  Opted to
+ * do interupt based writers/readers.
+ *
+ */
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+
+#include <asm/mach-types.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+
+#include <asm/hardware/psionw.h>
+
+#include "ssi_bus.h"
+#include "ssi_dev.h"
+
+#define TX_QUEUE_SIZE 16 /* Must be some 2^n */
+
+struct ssi5mx_txdat {
+	u_int head;
+	u_int tail;
+	u_int buf[TX_QUEUE_SIZE];
+};
+
+static struct ssi5mx_txdat* queue;
+
+static void add_txqueue(u_int dat) {
+	unsigned long flags;
+	int head=queue->head;
+	save_flags(flags);  /* No save_and_cli..?, what gives linux-arm? */
+	cli();
+	queue->buf[head]=dat;
+	if(head != (queue->tail-1)&(TX_QUEUE_SIZE-1)) {
+		head++;
+		head &= (TX_QUEUE_SIZE-1);
+	}
+	queue->head=head;
+	restore_flags(flags);
+}
+
+/* Always happens in ISR, no need to deal with concurrency */
+static u_int pop_txqueue(void) {
+	u_int ret;
+	ret=queue->buf[queue->tail];
+	queue->tail=(queue->tail+1) & (TX_QUEUE_SIZE-1);
+	return ret;
+}
+
+#define IS_EMPTY(queue) queue->head==queue->tail
+
+
+/*
+ * NE on PS5mx.
+ */
+static void ssi5mx_select_id(int id)
+{
+
+}
+
+/*
+ * Select the specified device.
+ * NE on 5mx yet. SRN
+ *
+ * App notes when done:
+ * Need to implement switch for SPI proto type, and do a switch()
+ * based on that to write different things to the SSCR0 control
+ * register.  Right now, only thing on is the National Microwire
+ * ADC, so it is initialized accordingly.
+ */
+static int ssi5mx_select(struct ssi_bus *bus, struct ssi_dev *dev)
+{
+	return 0;
+
+}
+
+static void ssi5mx_int(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct ssi_bus *bus = (struct ssi_bus *)dev_id;
+	u_int sssr;
+	sssr=psionw_readl(SSSR);
+	/* Do recieve first */
+	while(sssr & 1<<2) {
+		ssi_core_rcv(bus, psionw_readl(SSDR));
+		sssr=psionw_readl(SSSR);
+	}
+	/* Do transmit of queued data */
+	while((!(IS_EMPTY(queue))) && (sssr & 1<<1)) {
+		psionw_writel(pop_txqueue(),SSDR);
+		sssr=psionw_readl(SSSR);
+	}
+	/* Finally, yank tx empty interrupt if our TX queue is empty */
+	if(IS_EMPTY(queue)) {
+		sssr=psionw_readl(SSCR1);
+		sssr &= ~(0x2); /* Remove fifo'ing, and Transmit queue empty */
+		psionw_writel(sssr,SSCR1);
+	}
+}
+
+/*
+ * Every transmission follows with a reciept.  So we
+ * have to enable the bus interrupt for transmission if
+ * necessary, and queue up the bottom half transmissions
+ * and the later bottom have reciepts.
+ */
+static int ssi5mx_trans(struct ssi_bus *bus, u_int data)
+{
+	if(IS_EMPTY(queue)) {
+		/* Startup transmit irq after adding to queue */
+		u_int sssr;
+		add_txqueue(data);
+		sssr=psionw_readl(SSCR1);
+		sssr |= 0x2;
+		psionw_writel(sssr,SSCR1);
+		return 0;
+	}
+	add_txqueue(data);
+
+	return 0;
+}
+
+#ifdef CONFIG_SSI_ADC7843
+void init_adc7843(struct ssi_bus*);
+#endif
+
+/*
+ * Initialise the SSI bus.
+ */
+static int ssi5mx_bus_init(struct ssi_bus *bus)
+{
+	int retval;
+	u_int sssr;
+
+	retval = request_irq(IRQ_SSEOTI, ssi5mx_int, 0, "ssi5mx", bus);
+	if (retval)
+		return retval;
+	if((queue = kmalloc(sizeof(*queue), GFP_KERNEL))==NULL)
+	{
+		printk(KERN_ERR "ssi5mx: no queue memory.\n");
+		return -ENOMEM;
+	}
+	memset(queue,0,sizeof(*queue));
+	printk("5mx SSI bus initialized\n");
+	ssi5mx_select(bus, NULL);
+	psionw_writel(0x0,SSSR);
+	psionw_writel(0xff2b,SSCR0);
+	psionw_writel(0x1,SSCR1);
+	sssr=psionw_readl(SSCR0);
+	sssr|=1<<7;
+	psionw_writel(sssr,SSCR0);
+	/* Welcome to the old days of module/filesystem
+	   intialization.  Okay, I don't see a better
+	   way to do this at this exact juncture,
+	   and I really want to write a driver not a
+	   bus..., so... SRN
+	*/
+#ifdef CONFIG_SSI_ADC7843
+	init_adc7843(bus);
+#endif
+	return 0;
+}
+
+static void ssi5mx_bus_exit(struct ssi_bus *bus)
+{
+	u_int sssr;
+	ssi5mx_select(bus, NULL);
+	sssr=psionw_readl(SSCR0);
+	sssr &= ~(1<<7);
+	psionw_writel(sssr,SSCR0);
+
+	free_irq(IRQ_SSEOTI, bus);
+}
+
+void ssi5mx_powerdown(int lock)
+{
+	psionw_writel(psionw_readl(SSCR0) & ~(1<<7), SSCR0);
+}
+
+void ssi5mx_powerup(int lock)
+{
+	psionw_writel(psionw_readl(SSCR0) | (1<<7), SSCR0);
+}
+
+static struct ssi_bus psionw_ssi5mx_bus = {
+        name:	"psionw_ssi5mx",
+        init:	ssi5mx_bus_init,
+        exit:	ssi5mx_bus_exit,
+        select:	ssi5mx_select,
+        trans:	ssi5mx_trans,
+        };
+
+static int __init psionw_ssi5mx_init(void)
+{
+	return ssi_register_bus(&psionw_ssi5mx_bus);
+}
+
+static void __exit psionw_ssi5mx_exit(void)
+{
+	ssi_unregister_bus(&psionw_ssi5mx_bus);
+}
+
+module_init(psionw_ssi5mx_init);
+module_exit(psionw_ssi5mx_exit);
diff -urN -X /home/arm/dontdiff_tml_arm /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/ssi_bus.h linux-2.4.19-rmk2/drivers/ssi/ssi_bus.h
diff -urN -X /home/arm/dontdiff_tml_arm /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/ssi_bus.h linux-2.4.19-rmk2/drivers/ssi/ssi_bus.h
--- /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/ssi_bus.h	2002-10-02 22:05:26.000000000 -0700
+++ linux-2.4.19-rmk2/drivers/ssi/ssi_bus.h	2002-10-02 22:07:30.000000000 -0700
@@ -19,3 +19,4 @@
 extern int ssi_core_rcv(struct ssi_bus *bus, u_int data);
 extern int ssi_register_bus(struct ssi_bus *bus);
 extern int ssi_unregister_bus(struct ssi_bus *bus);
+extern int ssi_transmit_data(struct ssi_dev *dev, u_int data);
diff -urN -X /home/arm/dontdiff_tml_arm /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/ssi_dev.h linux-2.4.19-rmk2/drivers/ssi/ssi_dev.h
diff -urN -X /home/arm/dontdiff_tml_arm /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/ssi_dev.h linux-2.4.19-rmk2/drivers/ssi/ssi_dev.h
--- /home/download/kernels/linux-2.4.19-rmk2-vanilla/drivers/ssi/ssi_dev.h	2002-10-02 22:05:26.000000000 -0700
+++ linux-2.4.19-rmk2/drivers/ssi/ssi_dev.h	2002-10-02 22:07:30.000000000 -0700
@@ -19,3 +19,5 @@
 };
 
 
+int ssi_select_device(struct ssi_bus *bus, struct ssi_dev *dev);
+int ssi_register_device(struct ssi_bus *bus, struct ssi_dev *dev);
