linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 4/4] Add support for SUNIX Multi-I/O board
@ 2019-03-19 12:08 Morris Ku
  2019-03-22 16:55 ` Enrico Weigelt, metux IT consult
  2019-04-02  6:26 ` Lee Jones
  0 siblings, 2 replies; 4+ messages in thread
From: Morris Ku @ 2019-03-19 12:08 UTC (permalink / raw)
  To: lee.jones; +Cc: linux-kernel, morris_ku, Morris Ku

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@gmail.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


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH 4/4] Add support for SUNIX Multi-I/O board
  2019-03-19 12:08 [PATCH 4/4] Add support for SUNIX Multi-I/O board Morris Ku
@ 2019-03-22 16:55 ` Enrico Weigelt, metux IT consult
  2019-04-02  6:26 ` Lee Jones
  1 sibling, 0 replies; 4+ messages in thread
From: Enrico Weigelt, metux IT consult @ 2019-03-22 16:55 UTC (permalink / raw)
  To: Morris Ku, lee.jones; +Cc: linux-kernel, morris_ku

On 19.03.19 13:08, Morris Ku wrote:

> 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

<snip>

> +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;
> +}

Why are these all no-ops ?

> 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

<snip>

> +#undef SNX_LP_STATS

what is this ?

> +static int SNX_PAL_MAJOR;
> +
> +#define SNX_LP_NO SNX_PAR_TOTAL_MAX
> +#undef SNX_CONFIG_LP_CONSOLE

and this ?

> +#ifdef SNX_CONFIG_PARPORT_1284

dont do #define in the middle of the code.

> +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
> +};

dont reimplement existing standard functionality your own weird way.
use the partport subsystem. see: Documentation/parport-lowlevel.txt

> +static struct snx_parport_driver snx_lp_driver = {
> +	.name = "lx",
> +	.attach = snx_lp_attach,
> +	.detach = snx_lp_detach,
> +};

yet another case of duplication of some standard struct and hard-
typecasting ? use struct parport_driver here.

> +	SNX_PAL_MAJOR = register_chrdev(0, "lx", &snx_lp_fops);

dont register your own chardev - use the parport subsystem.

> diff --git a/mfd/sunix/snx_parallel.c b/mfd/sunix/snx_parallel.c
> new file mode 100644
> index 00000000..461ea4cc
> --- /dev/null

<snip>

> +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);

why not kzmalloc ?

> 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

<snip>

> +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,
> +};

don't reimplement existing standard functionality - use the parport
subsystem.

> 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)

use the BIT() macro

> 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,
> +};

don't reimplement existing standard functionality. use the parport
subsystem.


--mtx

-- 
Enrico Weigelt, metux IT consult
Free software and Linux embedded engineering
info@metux.net -- +49-151-27565287

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH 4/4] Add support for SUNIX Multi-I/O board
  2019-03-19 12:08 [PATCH 4/4] Add support for SUNIX Multi-I/O board Morris Ku
  2019-03-22 16:55 ` Enrico Weigelt, metux IT consult
@ 2019-04-02  6:26 ` Lee Jones
       [not found]   ` <HK2PR03MB45295EF1E814CE40BDFB798995560@HK2PR03MB4529.apcprd03.prod.outlook.com>
  1 sibling, 1 reply; 4+ messages in thread
From: Lee Jones @ 2019-04-02  6:26 UTC (permalink / raw)
  To: Morris Ku; +Cc: linux-kernel, morris_ku

On Tue, 19 Mar 2019, Morris Ku wrote:

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

Parallel port drivers should live in 'drivers/parport' and
'drivers/char'.  LP drivers should live in 'drivers/char'.

Please them there.

> Signed-off-by: Morris Ku <saumah@gmail.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

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH 4/4] Add support for SUNIX Multi-I/O board
       [not found]   ` <HK2PR03MB45295EF1E814CE40BDFB798995560@HK2PR03MB4529.apcprd03.prod.outlook.com>
@ 2019-04-03  3:40     ` Lee Jones
  0 siblings, 0 replies; 4+ messages in thread
From: Lee Jones @ 2019-04-03  3:40 UTC (permalink / raw)
  To: Morris Ku 古文俊; +Cc: linux-kernel

Please do not drop the list from your recipients.

On Tue, 02 Apr 2019, Morris Ku 古文俊 wrote:

> Hello , Lee ,
> 
> 
> Sunix multi-io card is a composite device,
> contains serial and parallel port , before we
> trying to place the driver in the /drivers/char,
> but maintainer suggests go to /driver/mfd,so we move to here.
> this driver is special,it supports more than one interface,
> so we combine to a single driver.

The MFD subsystem is designed to register child devices.  MFD drivers
do not contain any true functionality themselves.  If this device does
multiple things, you can split the functionality into their relevant
subsystems and register each of them from here.

There are 10's of examples available.  Please take a look around some
of the existing drivers to see how their functionality is split.

> On Tue, 19 Mar 2019, Morris Ku wrote:
> 
> > Driver for SUNIX Multi-I/O card.Based on parport_pc.c, ppdev.c
> > and lp.c by Linus Torvalds, Theodore Ts'o.
> 
> Parallel port drivers should live in 'drivers/parport' and
> 'drivers/char'.  LP drivers should live in 'drivers/char'.
> 
> Please them there.
> 
> > Signed-off-by: Morris Ku <saumah@gmail.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
> 

-- 
Lee Jones [李琼斯]
Linaro Services Technical Lead
Linaro.org │ Open source software for ARM SoCs
Follow Linaro: Facebook | Twitter | Blog

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2019-04-03  3:41 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-03-19 12:08 [PATCH 4/4] Add support for SUNIX Multi-I/O board Morris Ku
2019-03-22 16:55 ` Enrico Weigelt, metux IT consult
2019-04-02  6:26 ` Lee Jones
     [not found]   ` <HK2PR03MB45295EF1E814CE40BDFB798995560@HK2PR03MB4529.apcprd03.prod.outlook.com>
2019-04-03  3:40     ` Lee Jones

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).