lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <20190319120835.3840-1-saumah@gmail.com>
Date:   Tue, 19 Mar 2019 20:08:35 +0800
From:   Morris Ku <saumah@...il.com>
To:     lee.jones@...aro.org
Cc:     linux-kernel@...r.kernel.org, morris_ku@...ix.com,
        Morris Ku <saumah@...il.com>
Subject: [PATCH 4/4] Add support for SUNIX Multi-I/O board

Driver for SUNIX Multi-I/O card.Based on parport_pc.c, ppdev.c
and lp.c by Linus Torvalds, Theodore Ts'o.

Signed-off-by: Morris Ku <saumah@...il.com>
---
 mfd/sunix/snx_ieee1284.c     | 144 +++++++
 mfd/sunix/snx_ieee1284_ops.c | 258 +++++++++++++
 mfd/sunix/snx_lp.c           | 717 +++++++++++++++++++++++++++++++++++
 mfd/sunix/snx_lp.h           | 119 ++++++
 mfd/sunix/snx_parallel.c     | 397 +++++++++++++++++++
 mfd/sunix/snx_ppdev.c        | 454 ++++++++++++++++++++++
 mfd/sunix/snx_ppdev.h        |  15 +
 mfd/sunix/snx_share.c        | 629 ++++++++++++++++++++++++++++++
 8 files changed, 2733 insertions(+)
 create mode 100644 mfd/sunix/snx_ieee1284.c
 create mode 100644 mfd/sunix/snx_ieee1284_ops.c
 create mode 100644 mfd/sunix/snx_lp.c
 create mode 100644 mfd/sunix/snx_lp.h
 create mode 100644 mfd/sunix/snx_parallel.c
 create mode 100644 mfd/sunix/snx_ppdev.c
 create mode 100644 mfd/sunix/snx_ppdev.h
 create mode 100644 mfd/sunix/snx_share.c

diff --git a/mfd/sunix/snx_ieee1284.c b/mfd/sunix/snx_ieee1284.c
new file mode 100644
index 00000000..41b66b02
--- /dev/null
+++ b/mfd/sunix/snx_ieee1284.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "snx_common.h"
+
+static void sunix_parport_ieee1284_wakeup(struct snx_parport *port)
+{
+	up(&port->physport->ieee1284.irq);
+}
+
+static void sunix_timeout_waiting_on_port(struct timer_list *t)
+{
+	struct snx_parport *port = from_timer(port, t, timer);
+
+	sunix_parport_ieee1284_wakeup(port);
+}
+
+int sunix_parport_wait_event(struct snx_parport *port, signed long timeout)
+{
+	int ret;
+
+	if (!port->physport->cad->timeout)
+		return 1;
+
+	timer_setup(&port->timer, sunix_timeout_waiting_on_port, 0);
+	mod_timer(&port->timer, jiffies + timeout);
+
+	ret = down_interruptible(&port->physport->ieee1284.irq);
+
+	if (!del_timer(&port->timer) && !ret)
+		ret = 1;
+
+	return      ret;
+}
+
+
+int sunix_parport_poll_peripheral(struct snx_parport *port, unsigned char mask,
+unsigned char result, int usec)
+{
+	int count = usec / 5 + 2;
+	int i;
+	unsigned char status;
+
+	for (i = 0; i < count; i++) {
+		status = sunix_parport_read_status(port);
+
+		if ((status & mask) == result)
+			return 0;
+
+		if (signal_pending(current))
+			return -EINTR;
+
+		if (need_resched())
+			break;
+
+		if (i >= 2)
+			udelay(5);
+
+	}
+
+	return 1;
+}
+
+
+int sunix_parport_wait_peripheral(struct snx_parport *port,
+unsigned char mask, unsigned char result)
+{
+	int ret;
+	int usec;
+	unsigned long deadline;
+	unsigned char status;
+
+	usec = port->physport->spintime;// usecs of fast polling
+
+	if (!port->physport->cad->timeout)
+		usec = 35000;
+
+	ret = sunix_parport_poll_peripheral(port, mask, result, usec);
+
+	if (ret != 1)
+		return ret;
+
+	if (!port->physport->cad->timeout)
+		return 1;
+
+	deadline = jiffies + (HZ + 24) / 25;
+
+	while (time_before(jiffies, deadline)) {
+		int ret;
+
+		if (signal_pending(current))
+			return -EINTR;
+
+		ret = sunix_parport_wait_event(port, (HZ + 99) / 100);
+		if (ret < 0)
+			return ret;
+
+		status = sunix_parport_read_status(port);
+		if ((status & mask) == result)
+			return 0;
+
+		if (!ret)
+			schedule_timeout_interruptible(msecs_to_jiffies(10));
+	}
+
+	return 1;
+}
+
+
+int sunix_parport_negotiate(struct snx_parport *port, int mode)
+{
+	if (mode == IEEE1284_MODE_COMPAT)
+		return 0;
+
+	return -1;
+}
+
+
+ssize_t sunix_parport_write(struct snx_parport *port,
+const void *buffer, size_t len)
+{
+	ssize_t ret;
+
+	ret = port->ops->compat_write_data(port, buffer, len, 0);
+
+	return ret;
+}
+
+
+ssize_t sunix_parport_read(struct snx_parport *port, void *buffer, size_t len)
+{
+	return -ENODEV;
+}
+
+
+long sunix_parport_set_timeout(struct snx_pardevice *dev, long inactivity)
+{
+	long int old = dev->timeout;
+
+	dev->timeout = inactivity;
+
+	if (dev->port->physport->cad == dev)
+		sunix_parport_ieee1284_wakeup(dev->port);
+
+	return old;
+}
diff --git a/mfd/sunix/snx_ieee1284_ops.c b/mfd/sunix/snx_ieee1284_ops.c
new file mode 100644
index 00000000..2dac03fd
--- /dev/null
+++ b/mfd/sunix/snx_ieee1284_ops.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "snx_common.h"
+
+
+size_t sunix_parport_ieee1284_write_compat(struct snx_parport *port,
+const void *buffer, size_t len, int flags)
+{
+	int no_irq = 1;
+	ssize_t count = 0;
+	const unsigned char *addr = buffer;
+	unsigned char byte;
+	struct snx_pardevice *dev = port->physport->cad;
+	unsigned char ctl = (PARPORT_CONTROL_SELECT | PARPORT_CONTROL_INIT);
+
+	if (port->irq != PARPORT_IRQ_NONE) {
+		sunix_parport_enable_irq(port);
+		no_irq = 0;
+	}
+
+	port->physport->ieee1284.phase = IEEE1284_PH_FWD_DATA;
+	sunix_parport_write_control(port, ctl);
+	sunix_parport_data_forward(port);
+
+	while (count < len) {
+		unsigned long expire = jiffies + dev->timeout;
+		long wait = (HZ + 99) / 100;
+		unsigned char mask = (PARPORT_STATUS_ERROR |
+		PARPORT_STATUS_BUSY);
+		unsigned char val = (PARPORT_STATUS_ERROR |
+		PARPORT_STATUS_BUSY);
+
+		do {
+			if (!sunix_parport_wait_peripheral(port, mask, val))
+				goto ready;
+
+			if ((sunix_parport_read_status(port) &
+			(PARPORT_STATUS_PAPEROUT | PARPORT_STATUS_SELECT |
+			PARPORT_STATUS_ERROR)) != (PARPORT_STATUS_SELECT |
+			PARPORT_STATUS_ERROR))
+				goto stop;
+
+			if (!time_before(jiffies, expire))
+				break;
+
+			if (count && no_irq) {
+				sunix_parport_release(dev);
+
+				schedule_timeout_interruptible(wait);
+
+				sunix_parport_claim_or_block(dev);
+			} else {
+				sunix_parport_wait_event(port, wait);
+			}
+
+			if (signal_pending(current))
+				break;
+
+			wait *= 2;
+		} while (time_before(jiffies, expire));
+
+		if (signal_pending(current))
+			break;
+
+		break;
+
+ready:
+		byte = *addr++;
+		sunix_parport_write_data(port, byte);
+		udelay(1);
+
+		sunix_parport_write_control(port, ctl | PARPORT_CONTROL_STROBE);
+		udelay(1);
+
+		sunix_parport_write_control(port, ctl);
+		udelay(1);
+
+		count++;
+
+		if (time_before(jiffies, expire)) {
+			if (!sunix_parport_yield_blocking(dev) &&
+			need_resched())
+				schedule();
+		}
+	}
+stop:
+	port->physport->ieee1284.phase = IEEE1284_PH_FWD_IDLE;
+
+	return count;
+}
+
+size_t sunix_parport_ieee1284_read_nibble(struct snx_parport *port,
+void *buffer, size_t len, int flags)
+{
+	return 0;
+}
+
+size_t sunix_parport_ieee1284_read_byte(struct snx_parport *port,
+void *buffer, size_t len, int flags)
+{
+	return 0;
+}
+
+size_t sunix_parport_ieee1284_ecp_write_data(struct snx_parport *port,
+const void *buffer, size_t len, int flags)
+{
+	return 0;
+}
+
+size_t sunix_parport_ieee1284_ecp_read_data(struct snx_parport *port,
+void *buffer, size_t len, int flags)
+{
+	return 0;
+}
+
+size_t sunix_parport_ieee1284_ecp_write_addr(struct snx_parport *port,
+const void *buffer, size_t len, int flags)
+{
+	return 0;
+}
+
+size_t sunix_parport_ieee1284_epp_write_data(struct snx_parport *port,
+const void *buffer, size_t len, int flags)
+{
+	unsigned char *bp = (unsigned char *) buffer;
+	size_t ret = 0;
+
+	sunix_parport_frob_control(port, PARPORT_CONTROL_STROBE |
+	PARPORT_CONTROL_AUTOFD | PARPORT_CONTROL_SELECT |
+	PARPORT_CONTROL_INIT, PARPORT_CONTROL_STROBE | PARPORT_CONTROL_INIT);
+
+	port->ops->data_forward(port);
+
+	for (; len > 0; len--, bp++) {
+		sunix_parport_write_data(port, *bp);
+		sunix_parport_frob_control(port, PARPORT_CONTROL_AUTOFD,
+		PARPORT_CONTROL_AUTOFD);
+
+		if (sunix_parport_poll_peripheral(port, PARPORT_STATUS_BUSY,
+		0, 10))
+			break;
+
+		sunix_parport_frob_control(port, PARPORT_CONTROL_AUTOFD, 0);
+
+		if (sunix_parport_poll_peripheral(port, PARPORT_STATUS_BUSY,
+		PARPORT_STATUS_BUSY, 5))
+			break;
+
+		ret++;
+	}
+
+	sunix_parport_frob_control(port, PARPORT_CONTROL_STROBE, 0);
+	return ret;
+}
+
+
+size_t sunix_parport_ieee1284_epp_read_data(struct snx_parport *port,
+void *buffer, size_t len, int flags)
+{
+	unsigned char *bp = (unsigned char *) buffer;
+	unsigned int ret = 0;
+
+	sunix_parport_frob_control(port, PARPORT_CONTROL_STROBE |
+	PARPORT_CONTROL_AUTOFD | PARPORT_CONTROL_SELECT |
+	PARPORT_CONTROL_INIT, PARPORT_CONTROL_INIT);
+
+	port->ops->data_reverse(port);
+
+	for (; len > 0; len--, bp++) {
+		sunix_parport_frob_control(port, PARPORT_CONTROL_AUTOFD,
+		PARPORT_CONTROL_AUTOFD);
+
+		if (sunix_parport_wait_peripheral(port, PARPORT_STATUS_BUSY, 0))
+			break;
+
+		*bp = sunix_parport_read_data(port);
+
+		sunix_parport_frob_control(port, PARPORT_CONTROL_AUTOFD, 0);
+
+		if (sunix_parport_poll_peripheral(port, PARPORT_STATUS_BUSY,
+		PARPORT_STATUS_BUSY, 5))
+			break;
+
+		ret++;
+	}
+	port->ops->data_forward(port);
+	return ret;
+}
+
+
+size_t sunix_parport_ieee1284_epp_write_addr(struct snx_parport *port,
+const void *buffer, size_t len, int flags)
+{
+	unsigned char *bp = (unsigned char *)buffer;
+	size_t ret = 0;
+
+	sunix_parport_frob_control(port, PARPORT_CONTROL_STROBE |
+	PARPORT_CONTROL_AUTOFD | PARPORT_CONTROL_SELECT |
+	PARPORT_CONTROL_INIT, PARPORT_CONTROL_STROBE | PARPORT_CONTROL_INIT);
+
+	port->ops->data_forward(port);
+
+	for (; len > 0; len--, bp++) {
+		sunix_parport_write_data(port, *bp);
+		sunix_parport_frob_control(port, PARPORT_CONTROL_SELECT,
+		PARPORT_CONTROL_SELECT);
+
+		if (sunix_parport_poll_peripheral(port, PARPORT_STATUS_BUSY,
+		0, 10))
+			break;
+
+		sunix_parport_frob_control(port, PARPORT_CONTROL_SELECT, 0);
+
+		if (sunix_parport_poll_peripheral(port, PARPORT_STATUS_BUSY,
+		PARPORT_STATUS_BUSY, 5))
+			break;
+
+		ret++;
+	}
+
+	sunix_parport_frob_control(port, PARPORT_CONTROL_STROBE, 0);
+	return ret;
+}
+
+size_t sunix_parport_ieee1284_epp_read_addr(struct snx_parport *port,
+void *buffer, size_t len, int flags)
+{
+	unsigned char *bp = (unsigned char *) buffer;
+	unsigned int ret = 0;
+
+	sunix_parport_frob_control(port, PARPORT_CONTROL_STROBE |
+	PARPORT_CONTROL_AUTOFD | PARPORT_CONTROL_SELECT |
+	PARPORT_CONTROL_INIT, PARPORT_CONTROL_INIT);
+
+	port->ops->data_reverse(port);
+
+	for (; len > 0; len--, bp++) {
+		sunix_parport_frob_control(port, PARPORT_CONTROL_SELECT,
+		PARPORT_CONTROL_SELECT);
+
+		if (sunix_parport_wait_peripheral(port, PARPORT_STATUS_BUSY, 0))
+			break;
+
+		*bp = sunix_parport_read_data(port);
+
+		sunix_parport_frob_control(port, PARPORT_CONTROL_SELECT,
+		PARPORT_CONTROL_SELECT);
+
+		if (sunix_parport_poll_peripheral(port, PARPORT_STATUS_BUSY,
+		PARPORT_STATUS_BUSY, 5))
+			break;
+
+		ret++;
+	}
+
+	port->ops->data_forward(port);
+	return ret;
+}
+
diff --git a/mfd/sunix/snx_lp.c b/mfd/sunix/snx_lp.c
new file mode 100644
index 00000000..f2478447
--- /dev/null
+++ b/mfd/sunix/snx_lp.c
@@ -0,0 +1,717 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "snx_common.h"
+#include "snx_lp.h"
+
+#undef SNX_LP_STATS
+
+static int SNX_PAL_MAJOR;
+
+#define SNX_LP_NO SNX_PAR_TOTAL_MAX
+#undef SNX_CONFIG_LP_CONSOLE
+
+static struct snx_lp_struct snx_lp_table[SNX_LP_NO];
+static unsigned int snx_lp_count;
+static struct class *snx_lp_class;
+
+#ifdef SNX_CONFIG_LP_CONSOLE
+static struct snx_parport *console_registered;
+#endif
+
+#define SNX_LP_PREEMPT_REQUEST 1
+#define SNX_LP_PARPORT_CLAIMED 2
+
+
+#define r_dtr(x)	(sunix_parport_read_data(snx_lp_table[(x)].dev->port))
+#define r_str(x)	(sunix_parport_read_status(snx_lp_table[(x)].dev->port))
+#define w_ctr(x, y)	do { sunix_parport_write_control(snx_lp_table[(x)].dev->port, (y)); } while (0)
+#define w_dtr(x, y)	do { sunix_parport_write_data(snx_lp_table[(x)].dev->port, (y)); } while (0)
+
+static void snx_lp_claim_parport_or_block(struct snx_lp_struct *this_lp)
+{
+	if (!test_and_set_bit(SNX_LP_PARPORT_CLAIMED, &this_lp->bits))
+		sunix_parport_claim_or_block(this_lp->dev);
+}
+
+
+static void snx_lp_release_parport(struct snx_lp_struct *this_lp)
+{
+	if (test_and_clear_bit(SNX_LP_PARPORT_CLAIMED, &this_lp->bits))
+		sunix_parport_release(this_lp->dev);
+}
+
+
+static int snx_lp_preempt(void *handle)
+{
+	struct snx_lp_struct *this_lp = (struct snx_lp_struct *)handle;
+
+	set_bit(SNX_LP_PREEMPT_REQUEST, &this_lp->bits);
+	return 1;
+}
+
+
+static int snx_lp_negotiate(struct snx_parport *port, int mode)
+{
+	if (sunix_parport_negotiate(port, mode) != 0) {
+		mode = IEEE1284_MODE_COMPAT;
+		sunix_parport_negotiate(port, mode);
+	}
+	return (mode);
+}
+
+
+static int snx_lp_reset(int minor)
+{
+	int retval;
+
+	snx_lp_claim_parport_or_block(&snx_lp_table[minor]);
+
+	w_ctr(minor, SNX_LP_PSELECP);
+
+	udelay(SNX_LP_DELAY);
+
+	w_ctr(minor, SNX_LP_PSELECP | SNX_LP_PINITP);
+
+	retval = r_str(minor);
+
+	snx_lp_release_parport(&snx_lp_table[minor]);
+	return retval;
+}
+
+
+static void snx_lp_error(int minor)
+{
+	DEFINE_WAIT(wait);
+
+	int polling;
+
+	if (SNX_LP_F(minor) & SNX_LP_ABORT)
+		return;
+
+	polling = snx_lp_table[minor].dev->port->irq == PARPORT_IRQ_NONE;
+
+	if (polling)
+		snx_lp_release_parport(&snx_lp_table[minor]);
+
+	prepare_to_wait(&snx_lp_table[minor].waitq, &wait, TASK_INTERRUPTIBLE);
+
+	schedule_timeout(SNX_LP_TIMEOUT_POLLED);
+	finish_wait(&snx_lp_table[minor].waitq, &wait);
+
+	if (polling)
+		snx_lp_claim_parport_or_block(&snx_lp_table[minor]);
+	else
+		sunix_parport_yield_blocking(snx_lp_table[minor].dev);
+
+}
+
+
+static int snx_lp_check_status(int minor)
+{
+	int error = 0;
+	unsigned int last = snx_lp_table[minor].last_error;
+	unsigned char status = r_str(minor);
+
+	if ((status & SNX_LP_PERRORP) && !(SNX_LP_F(minor) & SNX_LP_CAREFUL)) {
+		last = 0;
+	} else if ((status & SNX_LP_POUTPA)) {
+		if (last != SNX_LP_POUTPA) {
+			last = SNX_LP_POUTPA;
+			pr_info("SNX Info : lp%d port out of paper.\n", minor);
+		}
+		error = -ENOSPC;
+	} else if (!(status & SNX_LP_PSELECD)) {
+		if (last != SNX_LP_PSELECD) {
+			last = SNX_LP_PSELECD;
+			pr_info("SNX Info : lp%d port off-line.\n", minor);
+		}
+		error = -EIO;
+	} else if (!(status & SNX_LP_PERRORP)) {
+		if (last != SNX_LP_PERRORP) {
+			last = SNX_LP_PERRORP;
+			pr_info("SNX Info : lp%d port on fire.\n", minor);
+		}
+		error = -EIO;
+	} else {
+		last = 0;
+	}
+
+	snx_lp_table[minor].last_error = last;
+
+	if (last != 0)
+		snx_lp_error(minor);
+
+	return error;
+}
+
+
+static int snx_lp_wait_ready(int minor, int nonblock)
+{
+	int error = 0;
+
+	if (snx_lp_table[minor].current_mode != IEEE1284_MODE_COMPAT)
+		return 0;
+
+
+	do {
+		error = snx_lp_check_status(minor);
+
+		if (error && (nonblock || (SNX_LP_F(minor) & SNX_LP_ABORT)))
+			break;
+
+
+		if (signal_pending(current)) {
+			error = -EINTR;
+			break;
+		}
+	} while (error);
+
+	return error;
+}
+
+static ssize_t snx_lp_write(struct file *file,
+const char __user *buf, size_t count, loff_t *ppos)
+{
+	unsigned int minor = iminor(file->f_path.dentry->d_inode);
+
+	struct snx_parport *port = snx_lp_table[minor].dev->port;
+	char *kbuf = snx_lp_table[minor].lp_buffer;
+
+	ssize_t retv = 0;
+	ssize_t written;
+	size_t copy_size = count;
+	int nonblock = ((file->f_flags & O_NONBLOCK) ||
+	(SNX_LP_F(minor) & SNX_LP_ABORT));
+
+
+#ifdef SNX_LP_STATS
+	if (time_after(jiffies,
+		snx_lp_table[minor].lastcall + SNX_LP_TIME(minor))) {
+		snx_lp_table[minor].runchars = 0;
+	}
+
+	snx_lp_table[minor].lastcall = jiffies;
+#endif
+
+	if (copy_size > SNX_LP_BUFFER_SIZE)
+		copy_size = SNX_LP_BUFFER_SIZE;
+
+	if (mutex_lock_interruptible(&snx_lp_table[minor].port_mutex))
+		return -EINTR;
+
+	if (copy_from_user(kbuf, buf, copy_size)) {
+		retv = -EFAULT;
+		goto out_unlock;
+	}
+
+	snx_lp_claim_parport_or_block(&snx_lp_table[minor]);
+
+	snx_lp_table[minor].current_mode = snx_lp_negotiate(
+	port, snx_lp_table[minor].best_mode);
+
+	sunix_parport_set_timeout(snx_lp_table[minor].dev, (nonblock ?
+	SNX_PARPORT_INACTIVITY_O_NONBLOCK : snx_lp_table[minor].timeout));
+
+	retv = snx_lp_wait_ready(minor, nonblock);
+
+	do {
+		written = sunix_parport_write(port, kbuf, copy_size);
+		if (written > 0) {
+			copy_size -= written;
+			count -= written;
+			buf  += written;
+			retv += written;
+		}
+
+		if (signal_pending(current)) {
+			if (retv == 0)
+				retv = -EINTR;
+
+			break;
+		}
+
+		if (copy_size > 0) {
+			int error;
+
+			sunix_parport_negotiate(snx_lp_table[minor].dev->port,
+			IEEE1284_MODE_COMPAT);
+			snx_lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
+
+			error = snx_lp_wait_ready(minor, nonblock);
+
+			if (error) {
+				if (retv == 0)
+					retv = error;
+
+				break;
+			} else if (nonblock) {
+				if (retv == 0)
+					retv = -EAGAIN;
+
+				break;
+			}
+
+			sunix_parport_yield_blocking(snx_lp_table[minor].dev);
+			snx_lp_table[minor].current_mode = snx_lp_negotiate(
+			port, snx_lp_table[minor].best_mode);
+
+		} else if (need_resched()) {
+			schedule();
+		}
+
+		if (count) {
+			copy_size = count;
+			if (copy_size > SNX_LP_BUFFER_SIZE)
+				copy_size = SNX_LP_BUFFER_SIZE;
+
+
+			if (copy_from_user(kbuf, buf, copy_size)) {
+				if (retv == 0)
+					retv = -EFAULT;
+
+				break;
+			}
+		}
+	} while (count > 0);
+
+	if (test_and_clear_bit(SNX_LP_PREEMPT_REQUEST,
+		&snx_lp_table[minor].bits)) {
+		pr_info("SNX Info : lp%d releasing parport.\n", minor);
+		sunix_parport_negotiate(snx_lp_table[minor].dev->port,
+		IEEE1284_MODE_COMPAT);
+
+		snx_lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
+
+		snx_lp_release_parport(&snx_lp_table[minor]);
+	}
+
+out_unlock:
+
+	mutex_unlock(&snx_lp_table[minor].port_mutex);
+
+	return retv;
+}
+
+
+#ifdef SNX_CONFIG_PARPORT_1284
+
+static ssize_t snx_lp_read(struct file *file, char __user *buf,
+size_t count, loff_t *ppos)
+{
+	unsigned int minor = iminor(file->f_path.dentry->d_inode);
+	DEFINE_WAIT(wait);
+
+	struct snx_parport *port = snx_lp_table[minor].dev->port;
+	ssize_t retval = 0;
+	char *kbuf = snx_lp_table[minor].lp_buffer;
+	int nonblock = ((file->f_flags & O_NONBLOCK) ||
+					(SNX_LP_F(minor) & SNX_LP_ABORT));
+
+	if (count > SNX_LP_BUFFER_SIZE)
+		count = SNX_LP_BUFFER_SIZE;
+
+
+	if (down_interruptible(&snx_lp_table[minor].port_mutex))
+		return -EINTR;
+
+
+	snx_lp_claim_parport_or_block(&snx_lp_table[minor]);
+
+	sunix_parport_set_timeout(snx_lp_table[minor].dev, (nonblock ?
+	PARPORT_INACTIVITY_O_NONBLOCK : snx_lp_table[minor].timeout));
+
+	sunix_parport_negotiate(snx_lp_table[minor].dev->port,
+							IEEE1284_MODE_COMPAT);
+
+	if (sunix_parport_negotiate(snx_lp_table[minor].dev->port,
+				IEEE1284_MODE_NIBBLE)) {
+		retval = -EIO;
+		goto out;
+	}
+
+	while (retval == 0) {
+		retval = sunix_parport_read(port, kbuf, count);
+
+		if (retval > 0)
+			break;
+
+
+		if (nonblock) {
+			retval = -EAGAIN;
+			break;
+		}
+
+		if (snx_lp_table[minor].dev->port->irq == PARPORT_IRQ_NONE) {
+			sunix_parport_negotiate(snx_lp_table[minor].dev->port,
+			IEEE1284_MODE_COMPAT);
+			snx_lp_error(minor);
+
+			if (sunix_parport_negotiate(
+				snx_lp_table[minor].dev->port,
+				IEEE1284_MODE_NIBBLE)) {
+				retval = -EIO;
+				goto out;
+			}
+		} else {
+			prepare_to_wait(&snx_lp_table[minor].waitq, &wait,
+			TASK_INTERRUPTIBLE);
+			schedule_timeout(SNX_LP_TIMEOUT_POLLED);
+			finish_wait(&snx_lp_table[minor].waitq, &wait);
+		}
+
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+
+		cond_resched();
+	}
+
+	sunix_parport_negotiate(snx_lp_table[minor].dev->port,
+	IEEE1284_MODE_COMPAT);
+
+out:
+	snx_lp_release_parport(&snx_lp_table[minor]);
+
+	if (retval > 0 && copy_to_user(buf, kbuf, retval))
+		retval = -EFAULT;
+
+	up(&snx_lp_table[minor].port_mutex);
+
+	return retval;
+}
+#endif
+
+
+static int snx_lp_open(struct inode *inode, struct file *file)
+{
+	unsigned int minor = iminor(inode);
+
+	if (minor >= SNX_LP_NO)
+		return -ENXIO;
+
+
+	if ((SNX_LP_F(minor) & SNX_LP_EXIST) == 0)
+		return -ENXIO;
+
+
+	if (test_and_set_bit(SNX_LP_BUSY_BIT_POS, &SNX_LP_F(minor)))
+		return -EBUSY;
+
+
+	if ((SNX_LP_F(minor) & SNX_LP_ABORTOPEN) &&
+						!(file->f_flags & O_NONBLOCK)) {
+		int status;
+
+		snx_lp_claim_parport_or_block(&snx_lp_table[minor]);
+		status = r_str(minor);
+		snx_lp_release_parport(&snx_lp_table[minor]);
+
+		if (status & SNX_LP_POUTPA) {
+			pr_info("SNX Error: lp%d out of paper.\n", minor);
+			SNX_LP_F(minor) &= ~SNX_LP_BUSY;
+			return -ENOSPC;
+		} else if (!(status & SNX_LP_PSELECD)) {
+			pr_info("SNX Error: lp%d off-line.\n", minor);
+			SNX_LP_F(minor) &= ~SNX_LP_BUSY;
+			return -EIO;
+		} else if (!(status & SNX_LP_PERRORP)) {
+			pr_info("SNX Error: lp%d printer error.\n", minor);
+			SNX_LP_F(minor) &= ~SNX_LP_BUSY;
+			return -EIO;
+		}
+	}
+
+	snx_lp_table[minor].lp_buffer = kmalloc(SNX_LP_BUFFER_SIZE, GFP_KERNEL);
+
+	if (!snx_lp_table[minor].lp_buffer) {
+		SNX_LP_F(minor) &= ~SNX_LP_BUSY;
+		return -ENOMEM;
+	}
+
+	snx_lp_claim_parport_or_block(&snx_lp_table[minor]);
+
+	if ((snx_lp_table[minor].dev->port->modes & PARPORT_MODE_ECP) &&
+		!sunix_parport_negotiate(snx_lp_table[minor].dev->port,
+		IEEE1284_MODE_ECP)) {
+		pr_info("SNX Info : lp%d ECP mode.\n", minor);
+		snx_lp_table[minor].best_mode = IEEE1284_MODE_ECP;
+	} else {
+		snx_lp_table[minor].best_mode = IEEE1284_MODE_COMPAT;
+	}
+
+	sunix_parport_negotiate(snx_lp_table[minor].dev->port,
+	IEEE1284_MODE_COMPAT);
+
+	snx_lp_release_parport(&snx_lp_table[minor]);
+	snx_lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
+
+	return 0;
+}
+
+
+static int snx_lp_release(struct inode *inode, struct file *file)
+{
+	unsigned int minor = iminor(inode);
+
+	snx_lp_claim_parport_or_block(&snx_lp_table[minor]);
+	sunix_parport_negotiate(snx_lp_table[minor].dev->port,
+	IEEE1284_MODE_COMPAT);
+
+	snx_lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
+	snx_lp_release_parport(&snx_lp_table[minor]);
+	kfree(snx_lp_table[minor].lp_buffer);
+	snx_lp_table[minor].lp_buffer = NULL;
+	SNX_LP_F(minor) &= ~SNX_LP_BUSY;
+
+	return 0;
+}
+
+static const struct file_operations snx_lp_fops = {
+	.owner		= THIS_MODULE,
+	.write		= snx_lp_write,
+	.open		= snx_lp_open,
+	.release	= snx_lp_release,
+#ifdef SNX_CONFIG_PARPORT_1284
+	.read		= snx_lp_read,
+#endif
+};
+
+
+#ifdef SNX_CONFIG_LP_CONSOLE
+#define SNX_CONSOLE_LP 0
+
+#define SNX_CONSOLE_LP_STRICT 1
+
+static void snx_lp_console_write(struct console *co,
+const char *s, unsigned int count)
+{
+	struct snx_pardevice *dev = snx_lp_table[SNX_CONSOLE_LP].dev;
+	struct snx_parport *port = dev->port;
+	ssize_t written;
+
+	if (sunix_parport_claim(dev))
+		return;
+
+	sunix_parport_set_timeout(dev, 0);
+
+	sunix_parport_negotiate(port, IEEE1284_MODE_COMPAT);
+
+	do {
+		ssize_t canwrite = count;
+		char *lf = memchr(s, '\n', count);
+
+		if (lf)
+			canwrite = lf - s;
+
+		if (canwrite > 0) {
+			written = sunix_parport_write(port, s, canwrite);
+
+			if (written <= 0)
+				continue;
+
+			s += written;
+			count -= written;
+			canwrite -= written;
+		}
+
+		if (lf && canwrite <= 0) {
+			const char *crlf = "\r\n";
+			int i = 2;
+
+			s++;
+			count--;
+			do {
+				written = sunix_parport_write(port, crlf, i);
+				if (written > 0)
+					i -= written, crlf += written;
+
+			} while (i > 0 && (SNX_CONSOLE_LP_STRICT ||
+				written > 0));
+		}
+	} while (count > 0 && (SNX_CONSOLE_LP_STRICT || written > 0));
+
+	sunix_parport_release(dev);
+}
+
+static struct console snx_lpcons = {
+	.name		= "lx",
+	.write		= snx_lp_console_write,
+	.flags		= CON_PRINTBUFFER,
+};
+
+#endif
+
+
+static int snx_parport_nr[SNX_LP_NO] = {0, 1, 2, 3};
+static int reset;
+
+
+static int snx_lp_register(int nr, struct snx_parport *port)
+{
+	snx_lp_table[nr].dev = sunix_parport_register_device(port, "lx",
+	snx_lp_preempt, NULL, NULL, 0, (void *) &snx_lp_table[nr]);
+
+	if (snx_lp_table[nr].dev == NULL)
+		return 1;
+
+
+	snx_lp_table[nr].flags |= SNX_LP_EXIST;
+
+	if (reset)
+		snx_lp_reset(nr);
+
+
+	device_create(snx_lp_class, NULL, MKDEV(SNX_PAL_MAJOR, nr),
+	NULL, "lp%d", nr);
+
+	pr_info("SNX Info : lp%d port using %s (%s).\n", nr, port->name,
+	(port->irq == PARPORT_IRQ_NONE)?"polling":"interrupt-driven");
+
+#ifdef SNX_CONFIG_LP_CONSOLE
+
+	if (!nr) {
+		if (port->modes & PARPORT_MODE_SAFEININT) {
+			register_console(&snx_lpcons);
+			console_registered = port;
+			pr_info("SNX Info : lp%d port console ready.\n",
+			SNX_CONSOLE_LP);
+		} else {
+			pr_info("SNX Info : lp%d port cannot run console on %s.\n",
+			SNX_CONSOLE_LP, port->name);
+		}
+	}
+#endif
+	return 0;
+}
+
+
+static void snx_lp_attach(struct snx_parport *port)
+{
+	unsigned int i;
+
+	for (i = 0; i < SNX_LP_NO; i++) {
+		if (port->number == snx_parport_nr[i]) {
+			if (!snx_lp_register(i, port))
+				snx_lp_count++;
+
+			break;
+		}
+	}
+}
+
+
+static void snx_lp_detach(struct snx_parport *port)
+{
+#ifdef SNX_CONFIG_LP_CONSOLE
+	if (console_registered == port) {
+		unregister_console(&snx_lpcons);
+		console_registered = NULL;
+	}
+
+#endif
+}
+
+
+static struct snx_parport_driver snx_lp_driver = {
+	.name = "lx",
+	.attach = snx_lp_attach,
+	.detach = snx_lp_detach,
+};
+
+
+static int snx_lp_init(void)
+{
+	int i, err = 0;
+
+	for (i = 0; i < SNX_LP_NO; i++) {
+		snx_lp_table[i].dev = NULL;
+		snx_lp_table[i].flags = 0;
+		snx_lp_table[i].chars = SNX_LP_INIT_CHAR;
+		snx_lp_table[i].time = SNX_LP_INIT_TIME;
+		snx_lp_table[i].wait = SNX_LP_INIT_WAIT;
+		snx_lp_table[i].lp_buffer = NULL;
+
+#ifdef SNX_LP_STATS
+		snx_lp_table[i].lastcall = 0;
+		snx_lp_table[i].runchars = 0;
+		memset(&snx_lp_table[i].stats, 0, sizeof(struct snx_lp_stats));
+#endif
+		snx_lp_table[i].last_error = 0;
+		init_waitqueue_head(&snx_lp_table[i].waitq);
+		init_waitqueue_head(&snx_lp_table[i].dataq);
+
+		mutex_init(&snx_lp_table[i].port_mutex);
+
+		snx_lp_table[i].timeout = 10 * HZ;
+	}
+
+	SNX_PAL_MAJOR = register_chrdev(0, "lx", &snx_lp_fops);
+
+	if (SNX_PAL_MAJOR < 0) {
+		pr_info("SNX Error: lp unable to get major\n");
+		return -EIO;
+	}
+
+	snx_lp_class = class_create(THIS_MODULE, "sprinter");
+
+	if (IS_ERR(snx_lp_class)) {
+		err = PTR_ERR(snx_lp_class);
+		goto out_reg;
+	}
+
+	if (sunix_parport_register_driver(&snx_lp_driver)) {
+		pr_info("SNX Error: lp unable to register with parport.\n");
+		err = -EIO;
+		goto out_class;
+	}
+
+	if (!snx_lp_count)
+		pr_info("SNX Warng: lp driver loaded but no devices found.\n");
+
+
+	return 0;
+
+out_class:
+
+	class_destroy(snx_lp_class);
+
+out_reg:
+
+	unregister_chrdev(SNX_PAL_MAJOR, "lx");
+	return err;
+}
+
+int sunix_par_lp_init(void)
+{
+	int status = 0;
+
+	status = snx_lp_init();
+	return status;
+}
+
+void sunix_par_lp_exit(void)
+{
+	unsigned int offset;
+
+	sunix_parport_unregister_driver(&snx_lp_driver);
+
+#ifdef SNX_CONFIG_LP_CONSOLE
+	unregister_console(&snx_lpcons);
+#endif
+
+	unregister_chrdev(SNX_PAL_MAJOR, "lx");
+
+	for (offset = 0; offset < SNX_LP_NO; offset++) {
+		if (snx_lp_table[offset].dev == NULL)
+			continue;
+
+		sunix_parport_unregister_device(snx_lp_table[offset].dev);
+
+		device_destroy(snx_lp_class, MKDEV(SNX_PAL_MAJOR, offset));
+
+	}
+
+	class_destroy(snx_lp_class);
+}
+
diff --git a/mfd/sunix/snx_lp.h b/mfd/sunix/snx_lp.h
new file mode 100644
index 00000000..000127c3
--- /dev/null
+++ b/mfd/sunix/snx_lp.h
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_SNX_LP_H
+#define _LINUX_SNX_LP_H
+
+#include <linux/mutex.h>
+
+#define SNX_LP_EXIST		0x0001
+#define SNX_LP_SELEC		0x0002
+#define SNX_LP_BUSY		0x0004
+#define SNX_LP_BUSY_BIT_POS	2
+#define SNX_LP_OFFL		0x0008
+#define SNX_LP_NOPA		0x0010
+#define SNX_LP_ERR		0x0020
+#define SNX_LP_ABORT		0x0040
+#define SNX_LP_CAREFUL		0x0080
+#define SNX_LP_ABORTOPEN	0x0100
+
+#define SNX_LP_TRUST_IRQ_	0x0200
+#define SNX_LP_NO_REVERSE	0x0400
+#define SNX_LP_DATA_AVAIL	0x0800
+
+#define SNX_LP_PBUSY		0x80
+#define SNX_LP_PACK		0x40
+#define SNX_LP_POUTPA		0x20
+#define SNX_LP_PSELECD		0x10
+#define SNX_LP_PERRORP		0x08
+
+#define SNX_LP_INIT_CHAR	1000
+#define SNX_LP_INIT_WAIT	1
+#define SNX_LP_INIT_TIME	2
+
+#define SNX_LPCHAR		0x0601
+#define SNX_LPTIME		0x0602
+#define SNX_LPABORT		0x0604
+#define SNX_LPSETIRQ	0x0605
+#define SNX_LPGETIRQ	0x0606
+#define SNX_LPWAIT		0x0608
+
+#define SNX_LPCAREFUL		0x0609
+#define SNX_LPABORTOPEN		0x060a
+#define SNX_LPGETSTATUS		0x060b
+#define SNX_LPRESET			0x060c
+
+#ifdef SNX_LP_STATS
+#define SNX_LPGETSTATS		0x060d
+#endif
+
+#define SNX_LPGETFLAGS		0x060e
+#define SNX_LPSETTIMEOUT	0x060f
+
+#define SNX_LP_TIMEOUT_INTERRUPT	(60 * HZ)
+#define SNX_LP_TIMEOUT_POLLED		(10 * HZ)
+
+#define SNX_LP_PARPORT_UNSPEC	-4
+#define SNX_LP_PARPORT_AUTO	-3
+#define SNX_LP_PARPORT_OFF	-2
+#define SNX_LP_PARPORT_NONE	-1
+
+#define SNX_LP_F(minor)		snx_lp_table[(minor)].flags
+#define SNX_LP_CHAR(minor)	snx_lp_table[(minor)].chars
+#define SNX_LP_TIME(minor)	snx_lp_table[(minor)].time
+#define SNX_LP_WAIT(minor)	snx_lp_table[(minor)].wait
+#define SNX_LP_IRQ(minor)	snx_lp_table[(minor)].dev->port->irq
+
+#ifdef SNX_LP_STATS
+#define SNX_LP_STAT(minor)		snx_lp_table[(minor)].stats
+#endif
+#define SNX_LP_BUFFER_SIZE PAGE_SIZE
+
+#define SNX_LP_BASE(x)			snx_lp_table[(x)].dev->port->base
+
+#ifdef SNX_LP_STATS
+struct snx_lp_stats {
+	unsigned long	chars;
+	unsigned long	sleeps;
+	unsigned int	maxrun;
+	unsigned int	maxwait;
+	unsigned int	meanwait;
+	unsigned int	mdev;
+};
+#endif
+
+struct snx_lp_struct {
+	struct snx_pardevice	*dev;
+	unsigned long		flags;
+	unsigned int		chars;
+	unsigned int		time;
+	unsigned int		wait;
+	char			*lp_buffer;
+
+#ifdef SNX_LP_STATS
+	unsigned int		lastcall;
+	unsigned int		runchars;
+	struct snx_lp_stats	stats;
+#endif
+
+	wait_queue_head_t	waitq;
+
+	unsigned int		last_error;
+	struct mutex		port_mutex;
+
+	wait_queue_head_t	dataq;
+
+	long			timeout;
+	unsigned int		best_mode;
+	unsigned int		current_mode;
+	unsigned long		bits;
+};
+
+
+#define SNX_LP_PINTEN		0x10
+#define SNX_LP_PSELECP		0x08
+#define SNX_LP_PINITP		0x04
+#define SNX_LP_PAUTOLF		0x02
+#define SNX_LP_PSTROBE		0x01
+#define SNX_LP_DUMMY		0x00
+#define SNX_LP_DELAY		50
+
+#endif
diff --git a/mfd/sunix/snx_parallel.c b/mfd/sunix/snx_parallel.c
new file mode 100644
index 00000000..461ea4cc
--- /dev/null
+++ b/mfd/sunix/snx_parallel.c
@@ -0,0 +1,397 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "snx_common.h"
+
+static LIST_HEAD(snx_ports_list);
+
+static DEFINE_SPINLOCK(snx_ports_lock);
+
+
+static inline unsigned char sunix_parport_pc_read_control(struct snx_parport *p)
+{
+	const unsigned char rm = (PARPORT_CONTROL_STROBE |
+							PARPORT_CONTROL_AUTOFD |
+							PARPORT_CONTROL_INIT |
+							PARPORT_CONTROL_SELECT);
+
+	const struct sunix_par_port *priv = p->physport->private_data;
+
+	return priv->ctr & rm;
+}
+
+static inline void sunix_parport_pc_disable_irq(struct snx_parport *p)
+{
+	__sunix_parport_pc_frob_control(p, 0x10, 0x00);
+}
+
+
+static void sunix_frob_econtrol(struct snx_parport *, unsigned char,
+unsigned char);
+
+// ECR modes
+#define ECR_SPP 00
+#define ECR_PS2 01
+#define ECR_PPF 02
+#define ECR_ECP 03
+#define ECR_EPP 04
+#define ECR_VND 05
+#define ECR_TST 06
+#define ECR_CNF 07
+#define ECR_MODE_MASK 0xe0
+#define ECR_WRITE(p, v) sunix_frob_econtrol((p), 0xff, (v))
+
+
+static void sunix_frob_econtrol(struct snx_parport *pb, unsigned char m,
+unsigned char v)
+{
+	unsigned char ectr = 0;
+
+	if (m != 0xff)
+		ectr = inb(SNX_ECR(pb));
+
+	outb((ectr & ~m) ^ v, SNX_ECR(pb));
+}
+
+
+static inline void sunix_frob_set_mode(struct snx_parport *p, int mode)
+{
+	sunix_frob_econtrol(p, ECR_MODE_MASK, mode << 5);
+}
+
+static int sunix_clear_epp_timeout(struct snx_parport *pb)
+{
+	unsigned char dsr;
+
+	dsr = sunix_parport_pc_read_status(pb);
+
+	if (!(dsr & 0x01))
+		return 1;
+
+	sunix_parport_pc_read_status(pb);
+	dsr = sunix_parport_pc_read_status(pb);
+
+	outb(dsr | 0x01, SNX_DSR(pb));
+	outb(dsr & 0xfe, SNX_DSR(pb));
+
+	dsr = sunix_parport_pc_read_status(pb);
+
+	return !(dsr & 0x01);
+}
+
+static void sunix_parport_pc_init_state(struct snx_pardevice *dev,
+struct snx_parport_state *s)
+{
+	s->u.pc.ctr = 0xc;
+	if (dev->irq_func && dev->port->irq != PARPORT_IRQ_NONE)
+		s->u.pc.ctr |= 0x10;
+
+	s->u.pc.ecr = 0x34;
+}
+
+static void sunix_parport_pc_save_state(struct snx_parport *p,
+struct snx_parport_state *s)
+{
+	const struct sunix_par_port *priv = p->physport->private_data;
+
+	s->u.pc.ctr = priv->ctr;
+
+	if (priv->ecr)
+		s->u.pc.ecr = inb(SNX_ECR(p));
+}
+
+static void sunix_parport_pc_restore_state(struct snx_parport *p,
+struct snx_parport_state *s)
+{
+	struct sunix_par_port *priv = p->physport->private_data;
+
+	register unsigned char c = s->u.pc.ctr & priv->ctr_writable;
+
+	outb(c, SNX_DCR(p));
+	priv->ctr = c;
+
+	if (priv->ecr)
+		ECR_WRITE(p, s->u.pc.ecr);
+}
+
+
+static const struct snx_parport_ops sunix_parport_pc_ops = {
+	.write_data			= sunix_parport_pc_write_data,
+	.read_data			= sunix_parport_pc_read_data,
+	.write_control		= sunix_parport_pc_write_control,
+	.read_control		= sunix_parport_pc_read_control,
+	.frob_control		= sunix_parport_pc_frob_control,
+	.read_status		= sunix_parport_pc_read_status,
+	.enable_irq			= sunix_parport_pc_enable_irq,
+	.disable_irq		= sunix_parport_pc_disable_irq,
+	.data_forward		= sunix_parport_pc_data_forward,
+	.data_reverse		= sunix_parport_pc_data_reverse,
+	.init_state			= sunix_parport_pc_init_state,
+	.save_state			= sunix_parport_pc_save_state,
+	.restore_state		= sunix_parport_pc_restore_state,
+	.epp_write_data		= sunix_parport_ieee1284_epp_write_data,
+	.epp_read_data		= sunix_parport_ieee1284_epp_read_data,
+	.epp_write_addr		= sunix_parport_ieee1284_epp_write_addr,
+	.epp_read_addr		= sunix_parport_ieee1284_epp_read_addr,
+	.ecp_write_data		= sunix_parport_ieee1284_ecp_write_data,
+	.ecp_read_data		= sunix_parport_ieee1284_ecp_read_data,
+	.ecp_write_addr		= sunix_parport_ieee1284_ecp_write_addr,
+	.compat_write_data	= sunix_parport_ieee1284_write_compat,
+	.nibble_read_data	= sunix_parport_ieee1284_read_nibble,
+	.byte_read_data		= sunix_parport_ieee1284_read_byte,
+	.owner				= THIS_MODULE,
+};
+
+
+static int sunix_parport_SPP_supported(struct snx_parport *pb)
+{
+	unsigned char dcr, w;
+
+	sunix_clear_epp_timeout(pb);
+
+	w = 0xc;
+	outb(w, SNX_DCR(pb));
+
+	dcr = inb(SNX_DCR(pb));
+
+	if ((dcr & 0xf) == w) {
+		w = 0xe;
+		outb(w, SNX_DCR(pb));
+		dcr = inb(SNX_DCR(pb));
+		outb(0xc, SNX_DCR(pb));
+
+		if ((dcr & 0xf) == w)
+			return PARPORT_MODE_PCSPP;
+	}
+
+	w = 0xaa;
+	sunix_parport_pc_write_data(pb, w);
+
+	dcr = sunix_parport_pc_read_data(pb);
+
+	if (dcr == w) {
+		w = 0x55;
+		sunix_parport_pc_write_data(pb, w);
+		dcr = sunix_parport_pc_read_data(pb);
+
+		if (dcr == w)
+			return PARPORT_MODE_PCSPP;
+	}
+
+	return 0;
+}
+
+static int sunix_parport_ECR_present(struct snx_parport *pb)
+{
+	struct sunix_par_port *priv = pb->private_data;
+	unsigned char r = 0xc;
+
+	outb(r, SNX_DCR(pb));
+
+	if ((inb(SNX_ECR(pb)) & 0x3) == (r & 0x3)) {
+		outb(r ^ 0x2, SNX_DCR(pb));
+
+		r = inb(SNX_DCR(pb));
+
+		if ((inb(SNX_ECR(pb)) & 0x2) == (r & 0x2))
+			goto no_reg;
+	}
+
+	if ((inb(SNX_ECR(pb)) & 0x3) != 0x1)
+		goto no_reg;
+
+	ECR_WRITE(pb, 0x34);
+
+	if (inb(SNX_ECR(pb)) != 0x35)
+		goto no_reg;
+
+	priv->ecr = 1;
+	outb(0xc, SNX_DCR(pb));
+
+
+	sunix_frob_set_mode(pb, ECR_SPP);
+
+	return 1;
+
+no_reg:
+	outb(0xc, SNX_DCR(pb));
+	return 0;
+}
+
+static int  sunix_parport_PS2_supported(struct snx_parport *pb)
+{
+	return 0;
+}
+
+static int  sunix_parport_EPP_supported(struct snx_parport *pb)
+{
+	return 0;
+}
+
+
+static int  sunix_parport_ECPEPP_supported(struct snx_parport *pb)
+{
+	return 0;
+}
+
+static int  sunix_parport_ECPPS2_supported(struct snx_parport *pb)
+{
+	return 0;
+}
+
+struct snx_parport *sunix_parport_pc_probe_port(struct sunix_par_port *priv)
+{
+	struct snx_parport_ops *ops = NULL;
+	struct snx_parport *p = NULL;
+	struct resource *base_res;
+	struct resource	*ecr_res = NULL;
+
+	if (!priv)
+		goto out1;
+
+	ops = kmalloc(sizeof(struct snx_parport_ops), GFP_KERNEL);
+	if (!ops)
+		goto out1;
+
+	p = sunix_parport_register_port(priv, ops);
+	if (!p)
+		goto out2;
+
+	base_res = request_region(p->base, SNX_PAR_ADDRESS_LENGTH,
+	"snx_par_base");
+	if (!base_res)
+		goto out3;
+
+	memcpy(ops, &sunix_parport_pc_ops, sizeof(struct snx_parport_ops));
+
+	priv->ctr = 0xc;
+	priv->ctr_writable = ~0x10;
+	priv->ecr = 0;
+	priv->fifo_depth = 0;
+	priv->dma_buf = NULL;
+	priv->dma_handle = 0;
+	INIT_LIST_HEAD(&priv->list);
+
+	p->modes = PARPORT_MODE_PCSPP | PARPORT_MODE_SAFEININT;
+	p->private_data = priv;
+
+	if (p->base_hi) {
+		ecr_res = request_region(p->base_hi, SNX_PAR_ADDRESS_LENGTH,
+		"snx_par_ehan");
+		if (ecr_res)
+			sunix_parport_ECR_present(p);
+
+		if (!sunix_parport_EPP_supported(p))
+			sunix_parport_ECPEPP_supported(p);
+	}
+
+	if (!sunix_parport_SPP_supported(p))
+		goto out4;
+
+	if (priv->ecr)
+		sunix_parport_ECPPS2_supported(p);
+	else
+		sunix_parport_PS2_supported(p);
+
+	p->size = (p->modes & PARPORT_MODE_EPP)?8:3;
+
+	pr_info("SNX Info : %s - PC-style at 0x%lx", p->name, p->base);
+	if (p->base_hi && priv->ecr)
+		pr_info(" (0x%lx)\n", p->base_hi);
+
+	if (priv->ecr)
+		ECR_WRITE(p, 0x34);
+
+	sunix_parport_pc_write_data(p, 0);
+
+	sunix_parport_pc_data_forward(p);
+
+
+	spin_lock(&snx_ports_lock);
+	list_add(&priv->list, &snx_ports_list);
+	spin_unlock(&snx_ports_lock);
+
+	sunix_parport_announce_port(p);
+
+	return p;
+
+out4:
+	if (ecr_res)
+		release_region(p->base_hi, SNX_PAR_ADDRESS_LENGTH);
+
+	release_region(p->base, SNX_PAR_ADDRESS_LENGTH);
+
+out3:
+	sunix_parport_put_port(p);
+
+out2:
+	kfree(ops);
+
+out1:
+	return NULL;
+}
+
+
+void sunix_parport_pc_unregister_port(struct snx_parport *p)
+{
+	struct sunix_par_port *priv = p->private_data;
+	struct snx_parport_ops *ops = p->ops;
+
+	sunix_parport_remove_port(p);
+
+	spin_lock(&snx_ports_lock);
+	list_del_init(&priv->list);
+	spin_unlock(&snx_ports_lock);
+
+	release_region(p->base, SNX_PAR_ADDRESS_LENGTH);
+
+	if (p->base_hi)
+		release_region(p->base_hi, SNX_PAR_ADDRESS_LENGTH);
+
+	sunix_parport_put_port(p);
+
+	kfree(ops);
+}
+
+
+int sunix_par_parport_init(void)
+{
+	struct sunix_par_port *pp = NULL;
+	int status = 0;
+	int i;
+
+	for (i = 0; i < SNX_PAR_TOTAL_MAX; i++) {
+		pp = &sunix_par_table[i];
+
+		if ((pp->base > 0) && (pp->chip_flag != SUNNONE_HWID)) {
+			pp->port = sunix_parport_pc_probe_port(pp);
+			if (!pp->port) {
+				status = -ENODEV;
+				break;
+			}
+		}
+
+		if (status != 0)
+			break;
+	}
+
+	return status;
+}
+
+
+void sunix_par_parport_exit(void)
+{
+	spin_lock(&snx_ports_lock);
+	while (!list_empty(&snx_ports_list)) {
+		struct sunix_par_port *priv;
+		struct snx_parport *port;
+
+		priv = list_entry(snx_ports_list.next, struct sunix_par_port,
+		list);
+
+		port = priv->port;
+		spin_unlock(&snx_ports_lock);
+		sunix_parport_pc_unregister_port(port);
+		spin_lock(&snx_ports_lock);
+	}
+	spin_unlock(&snx_ports_lock);
+}
+
diff --git a/mfd/sunix/snx_ppdev.c b/mfd/sunix/snx_ppdev.c
new file mode 100644
index 00000000..9482ed9f
--- /dev/null
+++ b/mfd/sunix/snx_ppdev.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "snx_ppdev.h"
+
+#define SNX_PARPORT_MAX 4
+#define SNX_CHRDEV "sppdev"
+
+static int SNX_PPD_MAJOR;
+
+struct snx_pp_struct {
+	struct snx_pardevice	*pdev;
+	wait_queue_head_t	irq_wait;
+	atomic_t		irqc;
+	unsigned int	flags;
+	int				irqresponse;
+	unsigned char	irqctl;
+	struct ieee1284_info	state;
+	struct ieee1284_info	saved_state;
+	long	default_inactivity;
+};
+
+
+#define SNX_PP_CLAIMED    (1<<0)
+#define SNX_PP_EXCL       (1<<1)
+
+#define SNX_PP_INTERRUPT_TIMEOUT	(10 * HZ)
+#define SNX_PP_BUFFER_SIZE	1024
+#define SNX_PARDEVICE_MAX	SNX_PAR_TOTAL_MAX
+
+
+static inline void snx_pp_enable_irq(struct snx_pp_struct *pp)
+{
+	struct snx_parport *port = pp->pdev->port;
+
+	port->ops->enable_irq(port);
+}
+
+
+static ssize_t snx_pp_read(struct file *file,
+char __user *buf, size_t count, loff_t *ppos)
+{
+	unsigned int minor = iminor(file->f_path.dentry->d_inode);
+	struct snx_pp_struct *pp = file->private_data;
+	char *kbuffer;
+	ssize_t bytes_read = 0;
+	struct snx_parport *pport;
+	int mode;
+
+	if (!(pp->flags & SNX_PP_CLAIMED)) {
+		pr_info("SNX Warng: %x claim the port first\n", minor);
+		return -EINVAL;
+	}
+
+	if (count == 0)
+		return 0;
+
+	kbuffer = kmalloc(min_t(size_t, count, SNX_PP_BUFFER_SIZE), GFP_KERNEL);
+
+	if (!kbuffer)
+		return -ENOMEM;
+
+	pport = pp->pdev->port;
+	mode = pport->ieee1284.mode & ~(IEEE1284_DEVICEID | IEEE1284_ADDR);
+
+	sunix_parport_set_timeout(pp->pdev,
+		(file->f_flags & O_NONBLOCK) ?
+		SNX_PARPORT_INACTIVITY_O_NONBLOCK : pp->default_inactivity);
+
+	while (bytes_read == 0)	{
+
+		ssize_t need = min_t(unsigned long, count, SNX_PP_BUFFER_SIZE);
+
+		if (mode == IEEE1284_MODE_EPP) {
+			int flags = 0;
+			size_t (*fn)(struct snx_parport *, void *, size_t, int);
+
+			if (pp->flags & SNX_PP_W91284PIC)
+				flags |= PARPORT_W91284PIC;
+
+			if (pp->flags & SNX_PP_FASTREAD)
+				flags |= PARPORT_EPP_FAST;
+
+			if (pport->ieee1284.mode & IEEE1284_ADDR)
+				fn = pport->ops->epp_read_addr;
+			else
+				fn = pport->ops->epp_read_data;
+
+			bytes_read = (*fn)(pport, kbuffer, need, flags);
+		} else {
+			bytes_read = sunix_parport_read(pport, kbuffer, need);
+		}
+
+		if (bytes_read != 0)
+			break;
+
+		if (file->f_flags & O_NONBLOCK) {
+			bytes_read = -EAGAIN;
+			break;
+		}
+
+		if (signal_pending(current)) {
+			bytes_read = -ERESTARTSYS;
+			break;
+		}
+
+		cond_resched();
+
+	}
+
+	sunix_parport_set_timeout(pp->pdev, pp->default_inactivity);
+
+	if (bytes_read > 0 && copy_to_user(buf, kbuffer, bytes_read))
+		bytes_read = -EFAULT;
+
+	kfree(kbuffer);
+	snx_pp_enable_irq(pp);
+
+	return bytes_read;
+}
+
+static ssize_t snx_pp_write(struct file *file,
+const char __user *buf, size_t count, loff_t *ppos)
+{
+	unsigned int minor = iminor(file->f_path.dentry->d_inode);
+	struct snx_pp_struct *pp = file->private_data;
+	char *kbuffer;
+	ssize_t bytes_written = 0;
+	ssize_t wrote;
+	int mode;
+	struct snx_parport *pport;
+
+	if (!(pp->flags & SNX_PP_CLAIMED)) {
+		pr_info("SNX Warng: %x claim the port first\n", minor);
+		return -EINVAL;
+	}
+
+	kbuffer = kmalloc(min_t(size_t, count, SNX_PP_BUFFER_SIZE), GFP_KERNEL);
+
+	if (!kbuffer)
+		return -ENOMEM;
+
+
+	pport = pp->pdev->port;
+	mode = pport->ieee1284.mode & ~(IEEE1284_DEVICEID | IEEE1284_ADDR);
+
+	sunix_parport_set_timeout(pp->pdev,
+		(file->f_flags & O_NONBLOCK) ?
+		SNX_PARPORT_INACTIVITY_O_NONBLOCK : pp->default_inactivity);
+
+	while (bytes_written < count) {
+
+		ssize_t n = min_t(unsigned long, count - bytes_written,
+			SNX_PP_BUFFER_SIZE);
+
+		if (copy_from_user(kbuffer, buf + bytes_written, n)) {
+			bytes_written = -EFAULT;
+			break;
+		}
+
+		if ((pp->flags & SNX_PP_FASTWRITE) && (mode ==
+		IEEE1284_MODE_EPP)) {
+			if (pport->ieee1284.mode & IEEE1284_ADDR) {
+				wrote = pport->ops->epp_write_addr(pport,
+				kbuffer, n, PARPORT_EPP_FAST);
+			} else {
+				wrote = pport->ops->epp_write_data(pport,
+				kbuffer, n, PARPORT_EPP_FAST);
+			}
+		} else {
+			wrote = sunix_parport_write(pp->pdev->port, kbuffer, n);
+		}
+
+		if (wrote <= 0) {
+			if (!bytes_written)
+				bytes_written = wrote;
+
+			break;
+		}
+
+		bytes_written += wrote;
+
+		if (file->f_flags & O_NONBLOCK) {
+			if (!bytes_written)
+				bytes_written = -EAGAIN;
+
+			break;
+		}
+
+		if (signal_pending(current)) {
+			if (!bytes_written)
+				bytes_written = -EINTR;
+
+			break;
+		}
+
+		cond_resched();
+	}
+
+	sunix_parport_set_timeout(pp->pdev, pp->default_inactivity);
+
+	kfree(kbuffer);
+	snx_pp_enable_irq(pp);
+
+	return bytes_written;
+}
+
+
+static enum ieee1284_phase snx_init_phase(int mode)
+{
+		switch (mode & ~(IEEE1284_DEVICEID | IEEE1284_ADDR)) {
+		case IEEE1284_MODE_NIBBLE:
+		case IEEE1284_MODE_BYTE:
+			return IEEE1284_PH_REV_IDLE;
+	}
+
+	return IEEE1284_PH_FWD_IDLE;
+}
+
+static long snx_dump_par_ioctl(struct file *file,
+unsigned int cmd, unsigned long arg)
+{
+	unsigned int minor = 0;
+
+		switch (cmd) {
+		case SNX_PAR_DUMP_PORT_INFO:
+		{
+			struct snx_par_port_info snx_port_info;
+			struct sunix_par_port *sdn = NULL;
+
+			memset(&snx_port_info, 0,
+				(sizeof(struct snx_par_port_info)));
+
+			if (copy_from_user(&snx_port_info, (void *)arg,
+			(sizeof(struct snx_par_port_info)))) {
+				return -EFAULT;
+			}
+
+			minor = snx_port_info.minor - 2;
+
+			if (minor >= 0) {
+				sdn = (struct sunix_par_port *)
+					&sunix_par_table[minor];
+
+				memcpy(&snx_port_info.board_name_info[0],
+				&sdn->pb_info.board_name[0],
+				SNX_BOARDNAME_LENGTH);
+
+				snx_port_info.bus_number_info = sdn->bus_number;
+				snx_port_info.dev_number_info = sdn->dev_number;
+				snx_port_info.port_info	= sdn->portnum + 2;
+				snx_port_info.base_info	= sdn->base;
+				snx_port_info.base_hi_info	= sdn->base_hi;
+				snx_port_info.irq_info	= sdn->irq;
+
+				if (copy_to_user((void *)arg, &snx_port_info,
+					sizeof(struct snx_par_port_info))) {
+					return -EFAULT;
+				} else {
+					return 0;
+				}
+
+			} else {
+				return -ENXIO;
+			}
+		}
+	}
+	return 0;
+}
+
+static int snx_pp_open(struct inode *inode, struct file *file)
+{
+	unsigned int minor = iminor(inode);
+
+	struct snx_pp_struct *pp;
+
+	if (minor >= PARPORT_MAX)
+		return -ENXIO;
+
+	pp = kmalloc(sizeof(struct snx_pp_struct), GFP_KERNEL);
+	if (!pp)
+		return -ENOMEM;
+
+	pp->state.mode = IEEE1284_MODE_COMPAT;
+	pp->state.phase = snx_init_phase(pp->state.mode);
+	pp->flags = 0;
+	pp->irqresponse = 0;
+	atomic_set(&pp->irqc, 0);
+
+	init_waitqueue_head(&pp->irq_wait);
+
+
+	pp->pdev = NULL;
+	file->private_data = pp;
+
+	return 0;
+}
+
+
+static int snx_pp_release(struct inode *inode, struct file *file)
+{
+	unsigned int minor = iminor(inode);
+	struct snx_pp_struct *pp = file->private_data;
+	int compat_negot;
+
+	compat_negot = 0;
+	if (!(pp->flags & SNX_PP_CLAIMED) && pp->pdev &&
+	(pp->state.mode != IEEE1284_MODE_COMPAT)) {
+		struct ieee1284_info *info;
+
+		sunix_parport_claim_or_block(pp->pdev);
+
+		pp->flags |= SNX_PP_CLAIMED;
+		info = &pp->pdev->port->ieee1284;
+		pp->saved_state.mode = info->mode;
+		pp->saved_state.phase = info->phase;
+		info->mode = pp->state.mode;
+		info->phase = pp->state.phase;
+		compat_negot = 1;
+	} else if ((pp->flags & SNX_PP_CLAIMED) && pp->pdev &&
+			(pp->pdev->port->ieee1284.mode !=
+			IEEE1284_MODE_COMPAT)) {
+		compat_negot = 2;
+	}
+
+	if (compat_negot) {
+		sunix_parport_negotiate(pp->pdev->port, IEEE1284_MODE_COMPAT);
+		pr_info("SNX Warng: %x negotiated", minor);
+		pr_info("back to compatibility mode\n");
+	}
+
+
+	if (pp->flags & SNX_PP_CLAIMED) {
+		struct ieee1284_info *info;
+
+		info = &pp->pdev->port->ieee1284;
+		pp->state.mode = info->mode;
+		pp->state.phase = info->phase;
+		info->mode = pp->saved_state.mode;
+		info->phase = pp->saved_state.phase;
+
+		sunix_parport_release(pp->pdev);
+
+		if (compat_negot != 1)
+			pr_info("SNX Warng: %x released ", minor);
+			pr_info("pardevice because user-space forgot\n");
+
+	}
+
+
+	if (pp->pdev) {
+		const char *name = pp->pdev->name;
+
+		sunix_parport_unregister_device(pp->pdev);
+		kfree(name);
+		pp->pdev = NULL;
+	}
+
+	kfree(pp);
+	return 0;
+}
+
+
+static unsigned int snx_pp_poll(struct file *file, poll_table *wait)
+{
+	struct snx_pp_struct *pp = file->private_data;
+	unsigned int mask = 0;
+
+	poll_wait(file, &pp->irq_wait, wait);
+
+	if (atomic_read(&pp->irqc))
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+
+
+static const struct file_operations snx_pp_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= snx_pp_read,
+	.write		= snx_pp_write,
+	.poll		= snx_pp_poll,
+	.unlocked_ioctl	= snx_dump_par_ioctl,
+
+	.open		= snx_pp_open,
+	.release	= snx_pp_release,
+};
+
+static struct class *snx_ppdev_class;
+
+static void snx_pp_attach(struct snx_parport *port)
+{
+	device_create(snx_ppdev_class, NULL,
+		MKDEV(SNX_PPD_MAJOR, port->number),
+		NULL, "parport%d", port->number);
+}
+
+static void snx_pp_detach(struct snx_parport *port)
+{
+	device_destroy(snx_ppdev_class, MKDEV(SNX_PPD_MAJOR, port->number));
+}
+
+static struct snx_parport_driver snx_pp_driver = {
+	.name		= SNX_CHRDEV,
+	.attach		= snx_pp_attach,
+	.detach		= snx_pp_detach,
+};
+
+int sunix_par_ppdev_init(void)
+{
+
+	int err = 0;
+
+	SNX_PPD_MAJOR = register_chrdev(0, SNX_CHRDEV, &snx_pp_fops);
+
+	if (SNX_PPD_MAJOR < 0) {
+		pr_err("SNX Error: unable to get major\n");
+		return -EIO;
+	}
+
+	snx_ppdev_class = class_create(THIS_MODULE, SNX_CHRDEV);
+	if (IS_ERR(snx_ppdev_class)) {
+		err = PTR_ERR(snx_ppdev_class);
+		goto out_chrdev;
+	}
+
+
+	if (sunix_parport_register_driver(&snx_pp_driver)) {
+		pr_err("SNX Error: unable to register with parport\n");
+		goto out_class;
+	}
+
+	goto out;
+
+out_class:
+	class_destroy(snx_ppdev_class);
+out_chrdev:
+
+	unregister_chrdev(SNX_PPD_MAJOR, SNX_CHRDEV);
+
+out:
+
+
+	return err;
+}
+
+
+void sunix_par_ppdev_exit(void)
+{
+	sunix_parport_unregister_driver(&snx_pp_driver);
+	class_destroy(snx_ppdev_class);
+
+	unregister_chrdev(SNX_PPD_MAJOR, SNX_CHRDEV);
+}
+
diff --git a/mfd/sunix/snx_ppdev.h b/mfd/sunix/snx_ppdev.h
new file mode 100644
index 00000000..0dfec064
--- /dev/null
+++ b/mfd/sunix/snx_ppdev.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#include "snx_common.h"
+
+#define SNX_PP_IOCTL	'p'
+
+#define SNX_PP_FASTWRITE	(1<<2)
+#define SNX_PP_FASTREAD		(1<<3)
+#define SNX_PP_W91284PIC	(1<<4)
+
+struct snx_ppdev_frob_struct {
+	unsigned char mask;
+	unsigned char val;
+};
+
+
diff --git a/mfd/sunix/snx_share.c b/mfd/sunix/snx_share.c
new file mode 100644
index 00000000..ba6f86a2
--- /dev/null
+++ b/mfd/sunix/snx_share.c
@@ -0,0 +1,629 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "snx_common.h"
+#define SNX_PARPORT_DEFAULT_TIMESLICE	(HZ/5)
+
+unsigned long sunix_parport_default_timeslice = SNX_PARPORT_DEFAULT_TIMESLICE;
+int sunix_parport_default_spintime = DEFAULT_SPIN_TIME;
+
+static LIST_HEAD(snx_portlist);
+static DEFINE_SPINLOCK(snx_full_list_lock);
+
+static DEFINE_SPINLOCK(snx_parportlist_lock);
+
+static LIST_HEAD(snx_all_ports);
+static LIST_HEAD(snx_drivers);
+
+static DEFINE_SEMAPHORE(snx_registration_lock);
+
+static void sunix_dead_write_lines(
+struct snx_parport *p, unsigned char b)
+{}
+static unsigned char sunix_dead_read_lines(
+struct snx_parport *p)
+{ return 0; }
+static unsigned char sunix_dead_frob_lines(
+struct snx_parport *p, unsigned char b, unsigned char c)
+{ return 0; }
+static void sunix_dead_onearg(struct snx_parport *p)
+{}
+static void sunix_dead_initstate(
+struct snx_pardevice *d, struct snx_parport_state *s)
+{}
+static void sunix_dead_state(
+struct snx_parport *p, struct snx_parport_state *s)
+{}
+static size_t sunix_dead_write(
+struct snx_parport *p, const void *b, size_t l, int f)
+{ return 0; }
+static size_t sunix_dead_read(
+struct snx_parport *p, void *b, size_t l, int f)
+{ return 0; }
+
+
+static struct snx_parport_ops	sunix_dead_ops = {
+	.write_data			= sunix_dead_write_lines,
+	.read_data			= sunix_dead_read_lines,
+	.write_control		= sunix_dead_write_lines,
+	.read_control		= sunix_dead_read_lines,
+	.frob_control		= sunix_dead_frob_lines,
+	.read_status		= sunix_dead_read_lines,
+	.enable_irq			= sunix_dead_onearg,
+	.disable_irq		= sunix_dead_onearg,
+	.data_forward		= sunix_dead_onearg,
+	.data_reverse		= sunix_dead_onearg,
+	.init_state			= sunix_dead_initstate,
+	.save_state			= sunix_dead_state,
+	.restore_state		= sunix_dead_state,
+	.epp_write_data		= sunix_dead_write,
+	.epp_read_data		= sunix_dead_read,
+	.epp_write_addr		= sunix_dead_write,
+	.epp_read_addr		= sunix_dead_read,
+	.ecp_write_data		= sunix_dead_write,
+	.ecp_read_data		= sunix_dead_read,
+	.ecp_write_addr		= sunix_dead_write,
+	.compat_write_data	= sunix_dead_write,
+	.nibble_read_data	= sunix_dead_read,
+	.byte_read_data		= sunix_dead_read,
+	.owner				= NULL,
+};
+
+
+static void sunix_attach_driver_chain(struct snx_parport *port)
+{
+	struct snx_parport_driver *drv;
+
+	list_for_each_entry(drv, &snx_drivers, list) drv->attach(port);
+}
+
+static void sunix_detach_driver_chain(struct snx_parport *port)
+{
+	struct snx_parport_driver *drv;
+
+	list_for_each_entry(drv, &snx_drivers, list) drv->detach(port);
+}
+
+int sunix_parport_register_driver(struct snx_parport_driver *drv)
+{
+	struct snx_parport *port;
+
+	down(&snx_registration_lock);
+
+	list_for_each_entry(port, &snx_portlist, list) drv->attach(port);
+	list_add(&drv->list, &snx_drivers);
+
+	up(&snx_registration_lock);
+
+	return 0;
+}
+
+void sunix_parport_unregister_driver(struct snx_parport_driver *drv)
+{
+	struct snx_parport *port;
+
+	down(&snx_registration_lock);
+
+	list_del_init(&drv->list);
+	list_for_each_entry(port, &snx_portlist, list) drv->detach(port);
+
+	up(&snx_registration_lock);
+}
+
+static void sunix_free_port(struct snx_parport *port)
+{
+	int d;
+
+	spin_lock(&snx_full_list_lock);
+	list_del(&port->full_list);
+	spin_unlock(&snx_full_list_lock);
+
+	for (d = 0; d < 5; d++) {
+		kfree(port->probe_info[d].class_name);
+		kfree(port->probe_info[d].mfr);
+		kfree(port->probe_info[d].model);
+		kfree(port->probe_info[d].cmdset);
+		kfree(port->probe_info[d].description);
+	}
+
+	kfree(port->name);
+	kfree(port);
+}
+
+struct snx_parport *sunix_parport_get_port(struct snx_parport *port)
+{
+	atomic_inc(&port->ref_count);
+	return port;
+}
+
+
+void sunix_parport_put_port(struct snx_parport *port)
+{
+	if (atomic_dec_and_test(&port->ref_count))
+		sunix_free_port(port);
+}
+
+
+struct snx_parport *sunix_parport_register_port(struct sunix_par_port *priv,
+struct snx_parport_ops *ops)
+{
+	struct list_head *l = NULL;
+	struct snx_parport *tmp = NULL;
+	int num;
+	int device;
+	char *name;
+
+	if ((!priv) || (!ops))
+		return NULL;
+
+
+	tmp = kmalloc(sizeof(struct snx_parport), GFP_KERNEL);
+
+	if (!tmp)
+		return NULL;
+
+
+	memset(tmp, 0, sizeof(struct snx_parport));
+
+	tmp->base = priv->base;
+	tmp->irq = priv->irq;
+
+	tmp->base_hi = priv->base_hi;
+
+	tmp->muxport = tmp->daisy = tmp->muxsel = -1;
+	tmp->modes = 0;
+
+	INIT_LIST_HEAD(&tmp->list);
+
+	tmp->devices = tmp->cad = NULL;
+	tmp->flags = 0;
+	tmp->ops = ops;
+	tmp->physport = tmp;
+
+	memset(tmp->probe_info, 0, 5 * sizeof(struct snx_parport_device_info));
+
+	rwlock_init(&tmp->cad_lock);
+	spin_lock_init(&tmp->waitlist_lock);
+	spin_lock_init(&tmp->pardevice_lock);
+	tmp->ieee1284.mode = IEEE1284_MODE_COMPAT;
+	tmp->ieee1284.phase = IEEE1284_PH_FWD_IDLE;
+
+	sema_init(&tmp->ieee1284.irq, 0);
+
+	tmp->spintime = sunix_parport_default_spintime;
+
+	atomic_set(&tmp->ref_count, 1);
+
+	INIT_LIST_HEAD(&tmp->full_list);
+
+	name = kmalloc(20, GFP_KERNEL);
+	if (!name)
+		return NULL;
+
+	spin_lock(&snx_full_list_lock);
+
+	for (l = snx_all_ports.next, num = 2; l != &snx_all_ports;
+		l = l->next, num++) {
+		struct snx_parport *p = list_entry(l, struct snx_parport,
+		full_list);
+
+		if (p->number != num)
+			break;
+
+	}
+
+	tmp->portnum = tmp->number = num;
+
+	list_add_tail(&tmp->full_list, l);
+
+	spin_unlock(&snx_full_list_lock);
+
+
+	sprintf(name, "parport%d", tmp->portnum = tmp->number);
+	tmp->name = name;
+
+	for (device = 0; device < 5; device++)
+		tmp->probe_info[device].class = PARPORT_CLASS_LEGACY;
+
+	tmp->waithead = tmp->waittail = NULL;
+
+	return tmp;
+}
+
+
+void sunix_parport_announce_port(struct snx_parport *port)
+{
+	int i;
+
+	down(&snx_registration_lock);
+
+	spin_lock_irq(&snx_parportlist_lock);
+
+	list_add_tail(&port->list, &snx_portlist);
+
+	for (i = 1; i < 3; i++) {
+		struct snx_parport *slave = port->slaves[i-1];
+
+		if (slave)
+			list_add_tail(&slave->list, &snx_portlist);
+
+	}
+	spin_unlock_irq(&snx_parportlist_lock);
+
+	sunix_attach_driver_chain(port);
+
+	for (i = 1; i < 3; i++) {
+		struct snx_parport *slave = port->slaves[i-1];
+
+		if (slave)
+			sunix_attach_driver_chain(slave);
+
+	}
+
+	up(&snx_registration_lock);
+}
+
+
+void sunix_parport_remove_port(struct snx_parport *port)
+{
+	int i;
+
+	down(&snx_registration_lock);
+
+	sunix_detach_driver_chain(port);
+
+	port->ops = &sunix_dead_ops;
+	spin_lock(&snx_parportlist_lock);
+	list_del_init(&port->list);
+
+	for (i = 1; i < 3; i++) {
+		struct snx_parport *slave = port->slaves[i-1];
+
+		if (slave)
+			list_del_init(&slave->list);
+
+	}
+
+	spin_unlock(&snx_parportlist_lock);
+
+	up(&snx_registration_lock);
+
+	for (i = 1; i < 3; i++) {
+		struct snx_parport *slave = port->slaves[i-1];
+
+		if (slave)
+			sunix_parport_put_port(slave);
+
+	}
+
+}
+
+struct snx_pardevice *sunix_parport_register_device(
+	struct snx_parport	*port,
+	const char			*name,
+	int					(*pf)(void *),
+	void	(*kf)(void *),
+	void	(*irq_func)(int, void *, struct pt_regs *),
+	int		flags,
+	void	*handle
+	)
+{
+	struct snx_pardevice *tmp;
+
+	if (port->physport->flags & PARPORT_FLAG_EXCL) {
+		pr_info("SNX Warng: %s no more devices allowed\n", port->name);
+		return NULL;
+	}
+
+	if (flags & PARPORT_DEV_LURK) {
+		if (!pf || !kf) {
+			pr_info("SNX Error: %s refused to register lurking device (%s)\n",
+			port->name, name);
+			return NULL;
+		}
+	}
+
+	sunix_parport_get_port(port);
+
+	tmp = kmalloc(sizeof(struct snx_pardevice), GFP_KERNEL);
+	if (tmp == NULL)
+		goto out;
+
+	tmp->state = kmalloc(sizeof(struct snx_parport_state), GFP_KERNEL);
+	if (tmp->state == NULL)
+		goto out_free_pardevice;
+
+	tmp->name = name;
+	tmp->port = port;
+	tmp->daisy = -1;
+	tmp->preempt = pf;
+	tmp->wakeup = kf;
+	tmp->private = handle;
+	tmp->flags = flags;
+	tmp->irq_func = irq_func;
+	tmp->waiting = 0;
+	tmp->timeout = 5 * HZ;
+
+	tmp->prev = NULL;
+
+	spin_lock(&port->physport->pardevice_lock);
+
+	if (flags & PARPORT_DEV_EXCL) {
+		if (port->physport->devices) {
+			spin_unlock(&port->physport->pardevice_lock);
+			pr_info("SNX Error: %s cannot grant exclusive access for device %s\n",
+			port->name, name);
+			goto out_free_all;
+		}
+		port->flags |= PARPORT_FLAG_EXCL;
+	}
+
+	tmp->next = port->physport->devices;
+	wmb();
+
+	if (port->physport->devices)
+		port->physport->devices->prev = tmp;
+
+	port->physport->devices = tmp;
+	spin_unlock(&port->physport->pardevice_lock);
+
+	init_waitqueue_head(&tmp->wait_q);
+	tmp->timeslice = sunix_parport_default_timeslice;
+	tmp->waitnext = tmp->waitprev = NULL;
+
+	port->ops->init_state(tmp, tmp->state);
+	return tmp;
+
+out_free_all:
+	kfree(tmp->state);
+
+out_free_pardevice:
+	kfree(tmp);
+
+out:
+	sunix_parport_put_port(port);
+
+	return NULL;
+}
+
+
+void sunix_parport_unregister_device(struct snx_pardevice *dev)
+{
+	struct snx_parport *port;
+
+	if (dev == NULL)
+		return;
+
+	port = dev->port->physport;
+
+	if (port->cad == dev) {
+		pr_info("SNX Warng: %s, %s forgot to release port\n",
+		port->name, dev->name);
+		sunix_parport_release(dev);
+	}
+
+	spin_lock(&port->pardevice_lock);
+	if (dev->next)
+		dev->next->prev = dev->prev;
+
+	if (dev->prev)
+		dev->prev->next = dev->next;
+	else
+		port->devices = dev->next;
+
+	if (dev->flags & PARPORT_DEV_EXCL)
+		port->flags &= ~PARPORT_FLAG_EXCL;
+
+	spin_unlock(&port->pardevice_lock);
+
+	spin_lock(&port->waitlist_lock);
+	if (dev->waitprev || dev->waitnext || port->waithead == dev) {
+		if (dev->waitprev)
+			dev->waitprev->waitnext = dev->waitnext;
+		else
+			port->waithead = dev->waitnext;
+
+		if (dev->waitnext)
+			dev->waitnext->waitprev = dev->waitprev;
+		else
+			port->waittail = dev->waitprev;
+	}
+
+	spin_unlock(&port->waitlist_lock);
+
+	kfree(dev->state);
+	kfree(dev);
+
+	sunix_parport_put_port(port);
+}
+
+
+struct snx_parport *sunix_parport_find_number(int number)
+{
+	struct snx_parport *port, *result = NULL;
+
+	spin_lock(&snx_parportlist_lock);
+
+	list_for_each_entry(port, &snx_portlist, list) {
+		if (port->number == number) {
+			result = sunix_parport_get_port(port);
+			break;
+		}
+	}
+	spin_unlock(&snx_parportlist_lock);
+	return result;
+}
+
+
+struct snx_parport *sunix_parport_find_base(unsigned long base)
+{
+	struct snx_parport *port, *result = NULL;
+
+	spin_lock(&snx_parportlist_lock);
+
+	list_for_each_entry(port, &snx_portlist, list) {
+		if (port->base == base) {
+			result = sunix_parport_get_port(port);
+			break;
+		}
+	}
+
+	spin_unlock(&snx_parportlist_lock);
+
+	return result;
+}
+
+
+int sunix_parport_claim(struct snx_pardevice *dev)
+{
+	struct snx_pardevice *oldcad;
+	struct snx_parport *port = dev->port->physport;
+	unsigned long flags;
+
+	if (port->cad == dev) {
+		pr_info("SNX Info : %s, %s already owner\n",
+		dev->port->name, dev->name);
+		return 0;
+	}
+
+	write_lock_irqsave(&port->cad_lock, flags);
+	oldcad = port->cad;
+	if (oldcad != NULL) {
+		if (oldcad->preempt) {
+			if (oldcad->preempt(oldcad->private))
+				goto blocked;
+
+			port->ops->save_state(port, dev->state);
+		} else {
+			goto blocked;
+		}
+
+		if (port->cad != oldcad) {
+			pr_info("SNX Error: %s, %s released port when preempted!\n",
+			port->name, oldcad->name);
+			if (port->cad)
+				goto blocked;
+
+		}
+	}
+
+	if (dev->waiting & 1) {
+		dev->waiting = 0;
+
+		spin_lock_irq(&port->waitlist_lock);
+
+		if (dev->waitprev)
+			dev->waitprev->waitnext = dev->waitnext;
+		else
+			port->waithead = dev->waitnext;
+
+		if (dev->waitnext)
+			dev->waitnext->waitprev = dev->waitprev;
+		else
+			port->waittail = dev->waitprev;
+
+		spin_unlock_irq(&port->waitlist_lock);
+		dev->waitprev = dev->waitnext = NULL;
+	}
+
+	port->cad = dev;
+
+	port->ops->restore_state(port, dev->state);
+	write_unlock_irqrestore(&port->cad_lock, flags);
+	dev->time = jiffies;
+
+	return 0;
+
+blocked:
+	if (dev->waiting & 2 || dev->wakeup) {
+		spin_lock(&port->waitlist_lock);
+		if (test_and_set_bit(0, &dev->waiting) == 0) {
+			dev->waitnext = NULL;
+			dev->waitprev = port->waittail;
+			if (port->waittail) {
+				port->waittail->waitnext = dev;
+				port->waittail = dev;
+			} else {
+				port->waithead = port->waittail = dev;
+			}
+		}
+
+		spin_unlock(&port->waitlist_lock);
+	}
+
+	write_unlock_irqrestore(&port->cad_lock, flags);
+	return -EAGAIN;
+}
+
+
+int sunix_parport_claim_or_block(struct snx_pardevice *dev)
+{
+	int r;
+
+	dev->waiting = 2;
+
+	r = sunix_parport_claim(dev);
+
+	if (r == -EAGAIN) {
+
+		pr_info("SNX Warng: %s returned -EAGAIN\n", dev->name);
+
+		if (dev->waiting) {
+
+			wait_event_interruptible(dev->wait_q, !dev->waiting);
+
+			if (signal_pending(current))
+				return -EINTR;
+
+			r = 1;
+		} else {
+			r = 0;
+		}
+	}
+	dev->waiting = 0;
+
+	return r;
+}
+
+
+void sunix_parport_release(struct snx_pardevice *dev)
+{
+	struct snx_parport *port = dev->port->physport;
+	struct snx_pardevice *pd;
+	unsigned long flags;
+
+	write_lock_irqsave(&port->cad_lock, flags);
+	if (port->cad != dev) {
+		write_unlock_irqrestore(&port->cad_lock, flags);
+		pr_info("SNX Warng: %s, %s tried to release parport when notowner\n",
+		port->name, dev->name);
+		return;
+	}
+
+	port->cad = NULL;
+	write_unlock_irqrestore(&port->cad_lock, flags);
+
+	port->ops->save_state(port, dev->state);
+
+	for (pd = port->waithead; pd; pd = pd->waitnext) {
+		if (pd->waiting & 2) {
+			sunix_parport_claim(pd);
+			if (waitqueue_active(&pd->wait_q)) /* */
+				wake_up_interruptible(&pd->wait_q);
+
+			return;
+		} else if (pd->wakeup) {
+			pd->wakeup(pd->private);
+
+			if (dev->port->cad)
+				return;
+
+		} else {
+			pr_info("SNX Warng: %s don't know how to wake %s\n",
+			port->name, pd->name);
+		}
+	}
+
+	for (pd = port->devices; (port->cad == NULL) && pd; pd = pd->next) {
+		if (pd->wakeup && pd != dev)
+			pd->wakeup(pd->private);
+	}
+}
-- 
2.17.1

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ