18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci        pt.c    (c) 1998  Grant R. Guenther <grant@torque.net>
38c2ecf20Sopenharmony_ci                          Under the terms of the GNU General Public License.
48c2ecf20Sopenharmony_ci
58c2ecf20Sopenharmony_ci        This is the high-level driver for parallel port ATAPI tape
68c2ecf20Sopenharmony_ci        drives based on chips supported by the paride module.
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci	The driver implements both rewinding and non-rewinding
98c2ecf20Sopenharmony_ci	devices, filemarks, and the rewind ioctl.  It allocates
108c2ecf20Sopenharmony_ci	a small internal "bounce buffer" for each open device, but
118c2ecf20Sopenharmony_ci        otherwise expects buffering and blocking to be done at the
128c2ecf20Sopenharmony_ci        user level.  As with most block-structured tapes, short
138c2ecf20Sopenharmony_ci	writes are padded to full tape blocks, so reading back a file
148c2ecf20Sopenharmony_ci        may return more data than was actually written.
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci        By default, the driver will autoprobe for a single parallel
178c2ecf20Sopenharmony_ci        port ATAPI tape drive, but if their individual parameters are
188c2ecf20Sopenharmony_ci        specified, the driver can handle up to 4 drives.
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci	The rewinding devices are named /dev/pt0, /dev/pt1, ...
218c2ecf20Sopenharmony_ci	while the non-rewinding devices are /dev/npt0, /dev/npt1, etc.
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci        The behaviour of the pt driver can be altered by setting
248c2ecf20Sopenharmony_ci        some parameters from the insmod command line.  The following
258c2ecf20Sopenharmony_ci        parameters are adjustable:
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci            drive0      These four arguments can be arrays of
288c2ecf20Sopenharmony_ci            drive1      1-6 integers as follows:
298c2ecf20Sopenharmony_ci            drive2
308c2ecf20Sopenharmony_ci            drive3      <prt>,<pro>,<uni>,<mod>,<slv>,<dly>
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci                        Where,
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci                <prt>   is the base of the parallel port address for
358c2ecf20Sopenharmony_ci                        the corresponding drive.  (required)
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci                <pro>   is the protocol number for the adapter that
388c2ecf20Sopenharmony_ci                        supports this drive.  These numbers are
398c2ecf20Sopenharmony_ci                        logged by 'paride' when the protocol modules
408c2ecf20Sopenharmony_ci                        are initialised.  (0 if not given)
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci                <uni>   for those adapters that support chained
438c2ecf20Sopenharmony_ci                        devices, this is the unit selector for the
448c2ecf20Sopenharmony_ci                        chain of devices on the given port.  It should
458c2ecf20Sopenharmony_ci                        be zero for devices that don't support chaining.
468c2ecf20Sopenharmony_ci                        (0 if not given)
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci                <mod>   this can be -1 to choose the best mode, or one
498c2ecf20Sopenharmony_ci                        of the mode numbers supported by the adapter.
508c2ecf20Sopenharmony_ci                        (-1 if not given)
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci                <slv>   ATAPI devices can be jumpered to master or slave.
538c2ecf20Sopenharmony_ci                        Set this to 0 to choose the master drive, 1 to
548c2ecf20Sopenharmony_ci                        choose the slave, -1 (the default) to choose the
558c2ecf20Sopenharmony_ci                        first drive found.
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci                <dly>   some parallel ports require the driver to
588c2ecf20Sopenharmony_ci                        go more slowly.  -1 sets a default value that
598c2ecf20Sopenharmony_ci                        should work with the chosen protocol.  Otherwise,
608c2ecf20Sopenharmony_ci                        set this to a small integer, the larger it is
618c2ecf20Sopenharmony_ci                        the slower the port i/o.  In some cases, setting
628c2ecf20Sopenharmony_ci                        this to zero will speed up the device. (default -1)
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	    major	You may use this parameter to override the
658c2ecf20Sopenharmony_ci			default major number (96) that this driver
668c2ecf20Sopenharmony_ci			will use.  Be sure to change the device
678c2ecf20Sopenharmony_ci			name as well.
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	    name	This parameter is a character string that
708c2ecf20Sopenharmony_ci			contains the name the kernel will use for this
718c2ecf20Sopenharmony_ci			device (in /proc output, for instance).
728c2ecf20Sopenharmony_ci			(default "pt").
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci            verbose     This parameter controls the amount of logging
758c2ecf20Sopenharmony_ci                        that the driver will do.  Set it to 0 for
768c2ecf20Sopenharmony_ci                        normal operation, 1 to see autoprobe progress
778c2ecf20Sopenharmony_ci                        messages, or 2 to see additional debugging
788c2ecf20Sopenharmony_ci                        output.  (default 0)
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci        If this driver is built into the kernel, you can use
818c2ecf20Sopenharmony_ci        the following command line parameters, with the same values
828c2ecf20Sopenharmony_ci        as the corresponding module parameters listed above:
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci            pt.drive0
858c2ecf20Sopenharmony_ci            pt.drive1
868c2ecf20Sopenharmony_ci            pt.drive2
878c2ecf20Sopenharmony_ci            pt.drive3
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci        In addition, you can use the parameter pt.disable to disable
908c2ecf20Sopenharmony_ci        the driver entirely.
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci*/
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci/*   Changes:
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	1.01	GRG 1998.05.06	Round up transfer size, fix ready_wait,
978c2ecf20Sopenharmony_ci			        loosed interpretation of ATAPI standard
988c2ecf20Sopenharmony_ci				for clearing error status.
998c2ecf20Sopenharmony_ci				Eliminate sti();
1008c2ecf20Sopenharmony_ci	1.02    GRG 1998.06.16  Eliminate an Ugh.
1018c2ecf20Sopenharmony_ci	1.03    GRG 1998.08.15  Adjusted PT_TMO, use HZ in loop timing,
1028c2ecf20Sopenharmony_ci				extra debugging
1038c2ecf20Sopenharmony_ci	1.04    GRG 1998.09.24  Repair minor coding error, added jumbo support
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci*/
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci#define PT_VERSION      "1.04"
1088c2ecf20Sopenharmony_ci#define PT_MAJOR	96
1098c2ecf20Sopenharmony_ci#define PT_NAME		"pt"
1108c2ecf20Sopenharmony_ci#define PT_UNITS	4
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci#include <linux/types.h>
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci/* Here are things one can override from the insmod command.
1158c2ecf20Sopenharmony_ci   Most are autoprobed by paride unless set here.  Verbose is on
1168c2ecf20Sopenharmony_ci   by default.
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci*/
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_cistatic int verbose = 0;
1218c2ecf20Sopenharmony_cistatic int major = PT_MAJOR;
1228c2ecf20Sopenharmony_cistatic char *name = PT_NAME;
1238c2ecf20Sopenharmony_cistatic int disable = 0;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic int drive0[6] = { 0, 0, 0, -1, -1, -1 };
1268c2ecf20Sopenharmony_cistatic int drive1[6] = { 0, 0, 0, -1, -1, -1 };
1278c2ecf20Sopenharmony_cistatic int drive2[6] = { 0, 0, 0, -1, -1, -1 };
1288c2ecf20Sopenharmony_cistatic int drive3[6] = { 0, 0, 0, -1, -1, -1 };
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_cistatic int (*drives[4])[6] = {&drive0, &drive1, &drive2, &drive3};
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci#define D_PRT   0
1338c2ecf20Sopenharmony_ci#define D_PRO   1
1348c2ecf20Sopenharmony_ci#define D_UNI   2
1358c2ecf20Sopenharmony_ci#define D_MOD   3
1368c2ecf20Sopenharmony_ci#define D_SLV   4
1378c2ecf20Sopenharmony_ci#define D_DLY   5
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci#define DU              (*drives[unit])
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci/* end of parameters */
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci#include <linux/module.h>
1448c2ecf20Sopenharmony_ci#include <linux/init.h>
1458c2ecf20Sopenharmony_ci#include <linux/fs.h>
1468c2ecf20Sopenharmony_ci#include <linux/delay.h>
1478c2ecf20Sopenharmony_ci#include <linux/slab.h>
1488c2ecf20Sopenharmony_ci#include <linux/mtio.h>
1498c2ecf20Sopenharmony_ci#include <linux/device.h>
1508c2ecf20Sopenharmony_ci#include <linux/sched.h>	/* current, TASK_*, schedule_timeout() */
1518c2ecf20Sopenharmony_ci#include <linux/mutex.h>
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_cimodule_param(verbose, int, 0);
1568c2ecf20Sopenharmony_cimodule_param(major, int, 0);
1578c2ecf20Sopenharmony_cimodule_param(name, charp, 0);
1588c2ecf20Sopenharmony_cimodule_param_array(drive0, int, NULL, 0);
1598c2ecf20Sopenharmony_cimodule_param_array(drive1, int, NULL, 0);
1608c2ecf20Sopenharmony_cimodule_param_array(drive2, int, NULL, 0);
1618c2ecf20Sopenharmony_cimodule_param_array(drive3, int, NULL, 0);
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci#include "paride.h"
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci#define PT_MAX_RETRIES  5
1668c2ecf20Sopenharmony_ci#define PT_TMO          3000	/* interrupt timeout in jiffies */
1678c2ecf20Sopenharmony_ci#define PT_SPIN_DEL     50	/* spin delay in micro-seconds  */
1688c2ecf20Sopenharmony_ci#define PT_RESET_TMO    30	/* 30 seconds */
1698c2ecf20Sopenharmony_ci#define PT_READY_TMO	60	/* 60 seconds */
1708c2ecf20Sopenharmony_ci#define PT_REWIND_TMO	1200	/* 20 minutes */
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci#define PT_SPIN         ((1000000/(HZ*PT_SPIN_DEL))*PT_TMO)
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci#define STAT_ERR        0x00001
1758c2ecf20Sopenharmony_ci#define STAT_INDEX      0x00002
1768c2ecf20Sopenharmony_ci#define STAT_ECC        0x00004
1778c2ecf20Sopenharmony_ci#define STAT_DRQ        0x00008
1788c2ecf20Sopenharmony_ci#define STAT_SEEK       0x00010
1798c2ecf20Sopenharmony_ci#define STAT_WRERR      0x00020
1808c2ecf20Sopenharmony_ci#define STAT_READY      0x00040
1818c2ecf20Sopenharmony_ci#define STAT_BUSY       0x00080
1828c2ecf20Sopenharmony_ci#define STAT_SENSE	0x1f000
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci#define ATAPI_TEST_READY	0x00
1858c2ecf20Sopenharmony_ci#define ATAPI_REWIND		0x01
1868c2ecf20Sopenharmony_ci#define ATAPI_REQ_SENSE		0x03
1878c2ecf20Sopenharmony_ci#define ATAPI_READ_6		0x08
1888c2ecf20Sopenharmony_ci#define ATAPI_WRITE_6		0x0a
1898c2ecf20Sopenharmony_ci#define ATAPI_WFM		0x10
1908c2ecf20Sopenharmony_ci#define ATAPI_IDENTIFY		0x12
1918c2ecf20Sopenharmony_ci#define ATAPI_MODE_SENSE	0x1a
1928c2ecf20Sopenharmony_ci#define ATAPI_LOG_SENSE		0x4d
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(pt_mutex);
1958c2ecf20Sopenharmony_cistatic int pt_open(struct inode *inode, struct file *file);
1968c2ecf20Sopenharmony_cistatic long pt_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
1978c2ecf20Sopenharmony_cistatic int pt_release(struct inode *inode, struct file *file);
1988c2ecf20Sopenharmony_cistatic ssize_t pt_read(struct file *filp, char __user *buf,
1998c2ecf20Sopenharmony_ci		       size_t count, loff_t * ppos);
2008c2ecf20Sopenharmony_cistatic ssize_t pt_write(struct file *filp, const char __user *buf,
2018c2ecf20Sopenharmony_ci			size_t count, loff_t * ppos);
2028c2ecf20Sopenharmony_cistatic int pt_detect(void);
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci/* bits in tape->flags */
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci#define PT_MEDIA	1
2078c2ecf20Sopenharmony_ci#define PT_WRITE_OK	2
2088c2ecf20Sopenharmony_ci#define PT_REWIND	4
2098c2ecf20Sopenharmony_ci#define PT_WRITING      8
2108c2ecf20Sopenharmony_ci#define PT_READING     16
2118c2ecf20Sopenharmony_ci#define PT_EOF	       32
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci#define PT_NAMELEN      8
2148c2ecf20Sopenharmony_ci#define PT_BUFSIZE  16384
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_cistruct pt_unit {
2178c2ecf20Sopenharmony_ci	struct pi_adapter pia;	/* interface to paride layer */
2188c2ecf20Sopenharmony_ci	struct pi_adapter *pi;
2198c2ecf20Sopenharmony_ci	int flags;		/* various state flags */
2208c2ecf20Sopenharmony_ci	int last_sense;		/* result of last request sense */
2218c2ecf20Sopenharmony_ci	int drive;		/* drive */
2228c2ecf20Sopenharmony_ci	atomic_t available;	/* 1 if access is available 0 otherwise */
2238c2ecf20Sopenharmony_ci	int bs;			/* block size */
2248c2ecf20Sopenharmony_ci	int capacity;		/* Size of tape in KB */
2258c2ecf20Sopenharmony_ci	int present;		/* device present ? */
2268c2ecf20Sopenharmony_ci	char *bufptr;
2278c2ecf20Sopenharmony_ci	char name[PT_NAMELEN];	/* pf0, pf1, ... */
2288c2ecf20Sopenharmony_ci};
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_cistatic int pt_identify(struct pt_unit *tape);
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_cistatic struct pt_unit pt[PT_UNITS];
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_cistatic char pt_scratch[512];	/* scratch block buffer */
2358c2ecf20Sopenharmony_cistatic void *par_drv;		/* reference of parport driver */
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci/* kernel glue structures */
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_cistatic const struct file_operations pt_fops = {
2408c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
2418c2ecf20Sopenharmony_ci	.read = pt_read,
2428c2ecf20Sopenharmony_ci	.write = pt_write,
2438c2ecf20Sopenharmony_ci	.unlocked_ioctl = pt_ioctl,
2448c2ecf20Sopenharmony_ci	.open = pt_open,
2458c2ecf20Sopenharmony_ci	.release = pt_release,
2468c2ecf20Sopenharmony_ci	.llseek = noop_llseek,
2478c2ecf20Sopenharmony_ci};
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci/* sysfs class support */
2508c2ecf20Sopenharmony_cistatic struct class *pt_class;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_cistatic inline int status_reg(struct pi_adapter *pi)
2538c2ecf20Sopenharmony_ci{
2548c2ecf20Sopenharmony_ci	return pi_read_regr(pi, 1, 6);
2558c2ecf20Sopenharmony_ci}
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_cistatic inline int read_reg(struct pi_adapter *pi, int reg)
2588c2ecf20Sopenharmony_ci{
2598c2ecf20Sopenharmony_ci	return pi_read_regr(pi, 0, reg);
2608c2ecf20Sopenharmony_ci}
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_cistatic inline void write_reg(struct pi_adapter *pi, int reg, int val)
2638c2ecf20Sopenharmony_ci{
2648c2ecf20Sopenharmony_ci	pi_write_regr(pi, 0, reg, val);
2658c2ecf20Sopenharmony_ci}
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_cistatic inline u8 DRIVE(struct pt_unit *tape)
2688c2ecf20Sopenharmony_ci{
2698c2ecf20Sopenharmony_ci	return 0xa0+0x10*tape->drive;
2708c2ecf20Sopenharmony_ci}
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_cistatic int pt_wait(struct pt_unit *tape, int go, int stop, char *fun, char *msg)
2738c2ecf20Sopenharmony_ci{
2748c2ecf20Sopenharmony_ci	int j, r, e, s, p;
2758c2ecf20Sopenharmony_ci	struct pi_adapter *pi = tape->pi;
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	j = 0;
2788c2ecf20Sopenharmony_ci	while ((((r = status_reg(pi)) & go) || (stop && (!(r & stop))))
2798c2ecf20Sopenharmony_ci	       && (j++ < PT_SPIN))
2808c2ecf20Sopenharmony_ci		udelay(PT_SPIN_DEL);
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	if ((r & (STAT_ERR & stop)) || (j > PT_SPIN)) {
2838c2ecf20Sopenharmony_ci		s = read_reg(pi, 7);
2848c2ecf20Sopenharmony_ci		e = read_reg(pi, 1);
2858c2ecf20Sopenharmony_ci		p = read_reg(pi, 2);
2868c2ecf20Sopenharmony_ci		if (j > PT_SPIN)
2878c2ecf20Sopenharmony_ci			e |= 0x100;
2888c2ecf20Sopenharmony_ci		if (fun)
2898c2ecf20Sopenharmony_ci			printk("%s: %s %s: alt=0x%x stat=0x%x err=0x%x"
2908c2ecf20Sopenharmony_ci			       " loop=%d phase=%d\n",
2918c2ecf20Sopenharmony_ci			       tape->name, fun, msg, r, s, e, j, p);
2928c2ecf20Sopenharmony_ci		return (e << 8) + s;
2938c2ecf20Sopenharmony_ci	}
2948c2ecf20Sopenharmony_ci	return 0;
2958c2ecf20Sopenharmony_ci}
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_cistatic int pt_command(struct pt_unit *tape, char *cmd, int dlen, char *fun)
2988c2ecf20Sopenharmony_ci{
2998c2ecf20Sopenharmony_ci	struct pi_adapter *pi = tape->pi;
3008c2ecf20Sopenharmony_ci	pi_connect(pi);
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci	write_reg(pi, 6, DRIVE(tape));
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci	if (pt_wait(tape, STAT_BUSY | STAT_DRQ, 0, fun, "before command")) {
3058c2ecf20Sopenharmony_ci		pi_disconnect(pi);
3068c2ecf20Sopenharmony_ci		return -1;
3078c2ecf20Sopenharmony_ci	}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ci	write_reg(pi, 4, dlen % 256);
3108c2ecf20Sopenharmony_ci	write_reg(pi, 5, dlen / 256);
3118c2ecf20Sopenharmony_ci	write_reg(pi, 7, 0xa0);	/* ATAPI packet command */
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	if (pt_wait(tape, STAT_BUSY, STAT_DRQ, fun, "command DRQ")) {
3148c2ecf20Sopenharmony_ci		pi_disconnect(pi);
3158c2ecf20Sopenharmony_ci		return -1;
3168c2ecf20Sopenharmony_ci	}
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_ci	if (read_reg(pi, 2) != 1) {
3198c2ecf20Sopenharmony_ci		printk("%s: %s: command phase error\n", tape->name, fun);
3208c2ecf20Sopenharmony_ci		pi_disconnect(pi);
3218c2ecf20Sopenharmony_ci		return -1;
3228c2ecf20Sopenharmony_ci	}
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ci	pi_write_block(pi, cmd, 12);
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	return 0;
3278c2ecf20Sopenharmony_ci}
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_cistatic int pt_completion(struct pt_unit *tape, char *buf, char *fun)
3308c2ecf20Sopenharmony_ci{
3318c2ecf20Sopenharmony_ci	struct pi_adapter *pi = tape->pi;
3328c2ecf20Sopenharmony_ci	int r, s, n, p;
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_ci	r = pt_wait(tape, STAT_BUSY, STAT_DRQ | STAT_READY | STAT_ERR,
3358c2ecf20Sopenharmony_ci		    fun, "completion");
3368c2ecf20Sopenharmony_ci
3378c2ecf20Sopenharmony_ci	if (read_reg(pi, 7) & STAT_DRQ) {
3388c2ecf20Sopenharmony_ci		n = (((read_reg(pi, 4) + 256 * read_reg(pi, 5)) +
3398c2ecf20Sopenharmony_ci		      3) & 0xfffc);
3408c2ecf20Sopenharmony_ci		p = read_reg(pi, 2) & 3;
3418c2ecf20Sopenharmony_ci		if (p == 0)
3428c2ecf20Sopenharmony_ci			pi_write_block(pi, buf, n);
3438c2ecf20Sopenharmony_ci		if (p == 2)
3448c2ecf20Sopenharmony_ci			pi_read_block(pi, buf, n);
3458c2ecf20Sopenharmony_ci	}
3468c2ecf20Sopenharmony_ci
3478c2ecf20Sopenharmony_ci	s = pt_wait(tape, STAT_BUSY, STAT_READY | STAT_ERR, fun, "data done");
3488c2ecf20Sopenharmony_ci
3498c2ecf20Sopenharmony_ci	pi_disconnect(pi);
3508c2ecf20Sopenharmony_ci
3518c2ecf20Sopenharmony_ci	return (r ? r : s);
3528c2ecf20Sopenharmony_ci}
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_cistatic void pt_req_sense(struct pt_unit *tape, int quiet)
3558c2ecf20Sopenharmony_ci{
3568c2ecf20Sopenharmony_ci	char rs_cmd[12] = { ATAPI_REQ_SENSE, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0 };
3578c2ecf20Sopenharmony_ci	char buf[16];
3588c2ecf20Sopenharmony_ci	int r;
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ci	r = pt_command(tape, rs_cmd, 16, "Request sense");
3618c2ecf20Sopenharmony_ci	mdelay(1);
3628c2ecf20Sopenharmony_ci	if (!r)
3638c2ecf20Sopenharmony_ci		pt_completion(tape, buf, "Request sense");
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_ci	tape->last_sense = -1;
3668c2ecf20Sopenharmony_ci	if (!r) {
3678c2ecf20Sopenharmony_ci		if (!quiet)
3688c2ecf20Sopenharmony_ci			printk("%s: Sense key: %x, ASC: %x, ASQ: %x\n",
3698c2ecf20Sopenharmony_ci			       tape->name, buf[2] & 0xf, buf[12], buf[13]);
3708c2ecf20Sopenharmony_ci		tape->last_sense = (buf[2] & 0xf) | ((buf[12] & 0xff) << 8)
3718c2ecf20Sopenharmony_ci		    | ((buf[13] & 0xff) << 16);
3728c2ecf20Sopenharmony_ci	}
3738c2ecf20Sopenharmony_ci}
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_cistatic int pt_atapi(struct pt_unit *tape, char *cmd, int dlen, char *buf, char *fun)
3768c2ecf20Sopenharmony_ci{
3778c2ecf20Sopenharmony_ci	int r;
3788c2ecf20Sopenharmony_ci
3798c2ecf20Sopenharmony_ci	r = pt_command(tape, cmd, dlen, fun);
3808c2ecf20Sopenharmony_ci	mdelay(1);
3818c2ecf20Sopenharmony_ci	if (!r)
3828c2ecf20Sopenharmony_ci		r = pt_completion(tape, buf, fun);
3838c2ecf20Sopenharmony_ci	if (r)
3848c2ecf20Sopenharmony_ci		pt_req_sense(tape, !fun);
3858c2ecf20Sopenharmony_ci
3868c2ecf20Sopenharmony_ci	return r;
3878c2ecf20Sopenharmony_ci}
3888c2ecf20Sopenharmony_ci
3898c2ecf20Sopenharmony_cistatic void pt_sleep(int cs)
3908c2ecf20Sopenharmony_ci{
3918c2ecf20Sopenharmony_ci	schedule_timeout_interruptible(cs);
3928c2ecf20Sopenharmony_ci}
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_cistatic int pt_poll_dsc(struct pt_unit *tape, int pause, int tmo, char *msg)
3958c2ecf20Sopenharmony_ci{
3968c2ecf20Sopenharmony_ci	struct pi_adapter *pi = tape->pi;
3978c2ecf20Sopenharmony_ci	int k, e, s;
3988c2ecf20Sopenharmony_ci
3998c2ecf20Sopenharmony_ci	k = 0;
4008c2ecf20Sopenharmony_ci	e = 0;
4018c2ecf20Sopenharmony_ci	s = 0;
4028c2ecf20Sopenharmony_ci	while (k < tmo) {
4038c2ecf20Sopenharmony_ci		pt_sleep(pause);
4048c2ecf20Sopenharmony_ci		k++;
4058c2ecf20Sopenharmony_ci		pi_connect(pi);
4068c2ecf20Sopenharmony_ci		write_reg(pi, 6, DRIVE(tape));
4078c2ecf20Sopenharmony_ci		s = read_reg(pi, 7);
4088c2ecf20Sopenharmony_ci		e = read_reg(pi, 1);
4098c2ecf20Sopenharmony_ci		pi_disconnect(pi);
4108c2ecf20Sopenharmony_ci		if (s & (STAT_ERR | STAT_SEEK))
4118c2ecf20Sopenharmony_ci			break;
4128c2ecf20Sopenharmony_ci	}
4138c2ecf20Sopenharmony_ci	if ((k >= tmo) || (s & STAT_ERR)) {
4148c2ecf20Sopenharmony_ci		if (k >= tmo)
4158c2ecf20Sopenharmony_ci			printk("%s: %s DSC timeout\n", tape->name, msg);
4168c2ecf20Sopenharmony_ci		else
4178c2ecf20Sopenharmony_ci			printk("%s: %s stat=0x%x err=0x%x\n", tape->name, msg, s,
4188c2ecf20Sopenharmony_ci			       e);
4198c2ecf20Sopenharmony_ci		pt_req_sense(tape, 0);
4208c2ecf20Sopenharmony_ci		return 0;
4218c2ecf20Sopenharmony_ci	}
4228c2ecf20Sopenharmony_ci	return 1;
4238c2ecf20Sopenharmony_ci}
4248c2ecf20Sopenharmony_ci
4258c2ecf20Sopenharmony_cistatic void pt_media_access_cmd(struct pt_unit *tape, int tmo, char *cmd, char *fun)
4268c2ecf20Sopenharmony_ci{
4278c2ecf20Sopenharmony_ci	if (pt_command(tape, cmd, 0, fun)) {
4288c2ecf20Sopenharmony_ci		pt_req_sense(tape, 0);
4298c2ecf20Sopenharmony_ci		return;
4308c2ecf20Sopenharmony_ci	}
4318c2ecf20Sopenharmony_ci	pi_disconnect(tape->pi);
4328c2ecf20Sopenharmony_ci	pt_poll_dsc(tape, HZ, tmo, fun);
4338c2ecf20Sopenharmony_ci}
4348c2ecf20Sopenharmony_ci
4358c2ecf20Sopenharmony_cistatic void pt_rewind(struct pt_unit *tape)
4368c2ecf20Sopenharmony_ci{
4378c2ecf20Sopenharmony_ci	char rw_cmd[12] = { ATAPI_REWIND, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
4388c2ecf20Sopenharmony_ci
4398c2ecf20Sopenharmony_ci	pt_media_access_cmd(tape, PT_REWIND_TMO, rw_cmd, "rewind");
4408c2ecf20Sopenharmony_ci}
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_cistatic void pt_write_fm(struct pt_unit *tape)
4438c2ecf20Sopenharmony_ci{
4448c2ecf20Sopenharmony_ci	char wm_cmd[12] = { ATAPI_WFM, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 };
4458c2ecf20Sopenharmony_ci
4468c2ecf20Sopenharmony_ci	pt_media_access_cmd(tape, PT_TMO, wm_cmd, "write filemark");
4478c2ecf20Sopenharmony_ci}
4488c2ecf20Sopenharmony_ci
4498c2ecf20Sopenharmony_ci#define DBMSG(msg)      ((verbose>1)?(msg):NULL)
4508c2ecf20Sopenharmony_ci
4518c2ecf20Sopenharmony_cistatic int pt_reset(struct pt_unit *tape)
4528c2ecf20Sopenharmony_ci{
4538c2ecf20Sopenharmony_ci	struct pi_adapter *pi = tape->pi;
4548c2ecf20Sopenharmony_ci	int i, k, flg;
4558c2ecf20Sopenharmony_ci	int expect[5] = { 1, 1, 1, 0x14, 0xeb };
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_ci	pi_connect(pi);
4588c2ecf20Sopenharmony_ci	write_reg(pi, 6, DRIVE(tape));
4598c2ecf20Sopenharmony_ci	write_reg(pi, 7, 8);
4608c2ecf20Sopenharmony_ci
4618c2ecf20Sopenharmony_ci	pt_sleep(20 * HZ / 1000);
4628c2ecf20Sopenharmony_ci
4638c2ecf20Sopenharmony_ci	k = 0;
4648c2ecf20Sopenharmony_ci	while ((k++ < PT_RESET_TMO) && (status_reg(pi) & STAT_BUSY))
4658c2ecf20Sopenharmony_ci		pt_sleep(HZ / 10);
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci	flg = 1;
4688c2ecf20Sopenharmony_ci	for (i = 0; i < 5; i++)
4698c2ecf20Sopenharmony_ci		flg &= (read_reg(pi, i + 1) == expect[i]);
4708c2ecf20Sopenharmony_ci
4718c2ecf20Sopenharmony_ci	if (verbose) {
4728c2ecf20Sopenharmony_ci		printk("%s: Reset (%d) signature = ", tape->name, k);
4738c2ecf20Sopenharmony_ci		for (i = 0; i < 5; i++)
4748c2ecf20Sopenharmony_ci			printk("%3x", read_reg(pi, i + 1));
4758c2ecf20Sopenharmony_ci		if (!flg)
4768c2ecf20Sopenharmony_ci			printk(" (incorrect)");
4778c2ecf20Sopenharmony_ci		printk("\n");
4788c2ecf20Sopenharmony_ci	}
4798c2ecf20Sopenharmony_ci
4808c2ecf20Sopenharmony_ci	pi_disconnect(pi);
4818c2ecf20Sopenharmony_ci	return flg - 1;
4828c2ecf20Sopenharmony_ci}
4838c2ecf20Sopenharmony_ci
4848c2ecf20Sopenharmony_cistatic int pt_ready_wait(struct pt_unit *tape, int tmo)
4858c2ecf20Sopenharmony_ci{
4868c2ecf20Sopenharmony_ci	char tr_cmd[12] = { ATAPI_TEST_READY, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
4878c2ecf20Sopenharmony_ci	int k, p;
4888c2ecf20Sopenharmony_ci
4898c2ecf20Sopenharmony_ci	k = 0;
4908c2ecf20Sopenharmony_ci	while (k < tmo) {
4918c2ecf20Sopenharmony_ci		tape->last_sense = 0;
4928c2ecf20Sopenharmony_ci		pt_atapi(tape, tr_cmd, 0, NULL, DBMSG("test unit ready"));
4938c2ecf20Sopenharmony_ci		p = tape->last_sense;
4948c2ecf20Sopenharmony_ci		if (!p)
4958c2ecf20Sopenharmony_ci			return 0;
4968c2ecf20Sopenharmony_ci		if (!(((p & 0xffff) == 0x0402) || ((p & 0xff) == 6)))
4978c2ecf20Sopenharmony_ci			return p;
4988c2ecf20Sopenharmony_ci		k++;
4998c2ecf20Sopenharmony_ci		pt_sleep(HZ);
5008c2ecf20Sopenharmony_ci	}
5018c2ecf20Sopenharmony_ci	return 0x000020;	/* timeout */
5028c2ecf20Sopenharmony_ci}
5038c2ecf20Sopenharmony_ci
5048c2ecf20Sopenharmony_cistatic void xs(char *buf, char *targ, int offs, int len)
5058c2ecf20Sopenharmony_ci{
5068c2ecf20Sopenharmony_ci	int j, k, l;
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ci	j = 0;
5098c2ecf20Sopenharmony_ci	l = 0;
5108c2ecf20Sopenharmony_ci	for (k = 0; k < len; k++)
5118c2ecf20Sopenharmony_ci		if ((buf[k + offs] != 0x20) || (buf[k + offs] != l))
5128c2ecf20Sopenharmony_ci			l = targ[j++] = buf[k + offs];
5138c2ecf20Sopenharmony_ci	if (l == 0x20)
5148c2ecf20Sopenharmony_ci		j--;
5158c2ecf20Sopenharmony_ci	targ[j] = 0;
5168c2ecf20Sopenharmony_ci}
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_cistatic int xn(char *buf, int offs, int size)
5198c2ecf20Sopenharmony_ci{
5208c2ecf20Sopenharmony_ci	int v, k;
5218c2ecf20Sopenharmony_ci
5228c2ecf20Sopenharmony_ci	v = 0;
5238c2ecf20Sopenharmony_ci	for (k = 0; k < size; k++)
5248c2ecf20Sopenharmony_ci		v = v * 256 + (buf[k + offs] & 0xff);
5258c2ecf20Sopenharmony_ci	return v;
5268c2ecf20Sopenharmony_ci}
5278c2ecf20Sopenharmony_ci
5288c2ecf20Sopenharmony_cistatic int pt_identify(struct pt_unit *tape)
5298c2ecf20Sopenharmony_ci{
5308c2ecf20Sopenharmony_ci	int dt, s;
5318c2ecf20Sopenharmony_ci	char *ms[2] = { "master", "slave" };
5328c2ecf20Sopenharmony_ci	char mf[10], id[18];
5338c2ecf20Sopenharmony_ci	char id_cmd[12] = { ATAPI_IDENTIFY, 0, 0, 0, 36, 0, 0, 0, 0, 0, 0, 0 };
5348c2ecf20Sopenharmony_ci	char ms_cmd[12] =
5358c2ecf20Sopenharmony_ci	    { ATAPI_MODE_SENSE, 0, 0x2a, 0, 36, 0, 0, 0, 0, 0, 0, 0 };
5368c2ecf20Sopenharmony_ci	char ls_cmd[12] =
5378c2ecf20Sopenharmony_ci	    { ATAPI_LOG_SENSE, 0, 0x71, 0, 0, 0, 0, 0, 36, 0, 0, 0 };
5388c2ecf20Sopenharmony_ci	char buf[36];
5398c2ecf20Sopenharmony_ci
5408c2ecf20Sopenharmony_ci	s = pt_atapi(tape, id_cmd, 36, buf, "identify");
5418c2ecf20Sopenharmony_ci	if (s)
5428c2ecf20Sopenharmony_ci		return -1;
5438c2ecf20Sopenharmony_ci
5448c2ecf20Sopenharmony_ci	dt = buf[0] & 0x1f;
5458c2ecf20Sopenharmony_ci	if (dt != 1) {
5468c2ecf20Sopenharmony_ci		if (verbose)
5478c2ecf20Sopenharmony_ci			printk("%s: Drive %d, unsupported type %d\n",
5488c2ecf20Sopenharmony_ci			       tape->name, tape->drive, dt);
5498c2ecf20Sopenharmony_ci		return -1;
5508c2ecf20Sopenharmony_ci	}
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_ci	xs(buf, mf, 8, 8);
5538c2ecf20Sopenharmony_ci	xs(buf, id, 16, 16);
5548c2ecf20Sopenharmony_ci
5558c2ecf20Sopenharmony_ci	tape->flags = 0;
5568c2ecf20Sopenharmony_ci	tape->capacity = 0;
5578c2ecf20Sopenharmony_ci	tape->bs = 0;
5588c2ecf20Sopenharmony_ci
5598c2ecf20Sopenharmony_ci	if (!pt_ready_wait(tape, PT_READY_TMO))
5608c2ecf20Sopenharmony_ci		tape->flags |= PT_MEDIA;
5618c2ecf20Sopenharmony_ci
5628c2ecf20Sopenharmony_ci	if (!pt_atapi(tape, ms_cmd, 36, buf, "mode sense")) {
5638c2ecf20Sopenharmony_ci		if (!(buf[2] & 0x80))
5648c2ecf20Sopenharmony_ci			tape->flags |= PT_WRITE_OK;
5658c2ecf20Sopenharmony_ci		tape->bs = xn(buf, 10, 2);
5668c2ecf20Sopenharmony_ci	}
5678c2ecf20Sopenharmony_ci
5688c2ecf20Sopenharmony_ci	if (!pt_atapi(tape, ls_cmd, 36, buf, "log sense"))
5698c2ecf20Sopenharmony_ci		tape->capacity = xn(buf, 24, 4);
5708c2ecf20Sopenharmony_ci
5718c2ecf20Sopenharmony_ci	printk("%s: %s %s, %s", tape->name, mf, id, ms[tape->drive]);
5728c2ecf20Sopenharmony_ci	if (!(tape->flags & PT_MEDIA))
5738c2ecf20Sopenharmony_ci		printk(", no media\n");
5748c2ecf20Sopenharmony_ci	else {
5758c2ecf20Sopenharmony_ci		if (!(tape->flags & PT_WRITE_OK))
5768c2ecf20Sopenharmony_ci			printk(", RO");
5778c2ecf20Sopenharmony_ci		printk(", blocksize %d, %d MB\n", tape->bs, tape->capacity / 1024);
5788c2ecf20Sopenharmony_ci	}
5798c2ecf20Sopenharmony_ci
5808c2ecf20Sopenharmony_ci	return 0;
5818c2ecf20Sopenharmony_ci}
5828c2ecf20Sopenharmony_ci
5838c2ecf20Sopenharmony_ci
5848c2ecf20Sopenharmony_ci/*
5858c2ecf20Sopenharmony_ci * returns  0, with id set if drive is detected
5868c2ecf20Sopenharmony_ci *	   -1, if drive detection failed
5878c2ecf20Sopenharmony_ci */
5888c2ecf20Sopenharmony_cistatic int pt_probe(struct pt_unit *tape)
5898c2ecf20Sopenharmony_ci{
5908c2ecf20Sopenharmony_ci	if (tape->drive == -1) {
5918c2ecf20Sopenharmony_ci		for (tape->drive = 0; tape->drive <= 1; tape->drive++)
5928c2ecf20Sopenharmony_ci			if (!pt_reset(tape))
5938c2ecf20Sopenharmony_ci				return pt_identify(tape);
5948c2ecf20Sopenharmony_ci	} else {
5958c2ecf20Sopenharmony_ci		if (!pt_reset(tape))
5968c2ecf20Sopenharmony_ci			return pt_identify(tape);
5978c2ecf20Sopenharmony_ci	}
5988c2ecf20Sopenharmony_ci	return -1;
5998c2ecf20Sopenharmony_ci}
6008c2ecf20Sopenharmony_ci
6018c2ecf20Sopenharmony_cistatic int pt_detect(void)
6028c2ecf20Sopenharmony_ci{
6038c2ecf20Sopenharmony_ci	struct pt_unit *tape;
6048c2ecf20Sopenharmony_ci	int specified = 0, found = 0;
6058c2ecf20Sopenharmony_ci	int unit;
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ci	printk("%s: %s version %s, major %d\n", name, name, PT_VERSION, major);
6088c2ecf20Sopenharmony_ci
6098c2ecf20Sopenharmony_ci	par_drv = pi_register_driver(name);
6108c2ecf20Sopenharmony_ci	if (!par_drv) {
6118c2ecf20Sopenharmony_ci		pr_err("failed to register %s driver\n", name);
6128c2ecf20Sopenharmony_ci		return -1;
6138c2ecf20Sopenharmony_ci	}
6148c2ecf20Sopenharmony_ci
6158c2ecf20Sopenharmony_ci	specified = 0;
6168c2ecf20Sopenharmony_ci	for (unit = 0; unit < PT_UNITS; unit++) {
6178c2ecf20Sopenharmony_ci		struct pt_unit *tape = &pt[unit];
6188c2ecf20Sopenharmony_ci		tape->pi = &tape->pia;
6198c2ecf20Sopenharmony_ci		atomic_set(&tape->available, 1);
6208c2ecf20Sopenharmony_ci		tape->flags = 0;
6218c2ecf20Sopenharmony_ci		tape->last_sense = 0;
6228c2ecf20Sopenharmony_ci		tape->present = 0;
6238c2ecf20Sopenharmony_ci		tape->bufptr = NULL;
6248c2ecf20Sopenharmony_ci		tape->drive = DU[D_SLV];
6258c2ecf20Sopenharmony_ci		snprintf(tape->name, PT_NAMELEN, "%s%d", name, unit);
6268c2ecf20Sopenharmony_ci		if (!DU[D_PRT])
6278c2ecf20Sopenharmony_ci			continue;
6288c2ecf20Sopenharmony_ci		specified++;
6298c2ecf20Sopenharmony_ci		if (pi_init(tape->pi, 0, DU[D_PRT], DU[D_MOD], DU[D_UNI],
6308c2ecf20Sopenharmony_ci		     DU[D_PRO], DU[D_DLY], pt_scratch, PI_PT,
6318c2ecf20Sopenharmony_ci		     verbose, tape->name)) {
6328c2ecf20Sopenharmony_ci			if (!pt_probe(tape)) {
6338c2ecf20Sopenharmony_ci				tape->present = 1;
6348c2ecf20Sopenharmony_ci				found++;
6358c2ecf20Sopenharmony_ci			} else
6368c2ecf20Sopenharmony_ci				pi_release(tape->pi);
6378c2ecf20Sopenharmony_ci		}
6388c2ecf20Sopenharmony_ci	}
6398c2ecf20Sopenharmony_ci	if (specified == 0) {
6408c2ecf20Sopenharmony_ci		tape = pt;
6418c2ecf20Sopenharmony_ci		if (pi_init(tape->pi, 1, -1, -1, -1, -1, -1, pt_scratch,
6428c2ecf20Sopenharmony_ci			    PI_PT, verbose, tape->name)) {
6438c2ecf20Sopenharmony_ci			if (!pt_probe(tape)) {
6448c2ecf20Sopenharmony_ci				tape->present = 1;
6458c2ecf20Sopenharmony_ci				found++;
6468c2ecf20Sopenharmony_ci			} else
6478c2ecf20Sopenharmony_ci				pi_release(tape->pi);
6488c2ecf20Sopenharmony_ci		}
6498c2ecf20Sopenharmony_ci
6508c2ecf20Sopenharmony_ci	}
6518c2ecf20Sopenharmony_ci	if (found)
6528c2ecf20Sopenharmony_ci		return 0;
6538c2ecf20Sopenharmony_ci
6548c2ecf20Sopenharmony_ci	pi_unregister_driver(par_drv);
6558c2ecf20Sopenharmony_ci	printk("%s: No ATAPI tape drive detected\n", name);
6568c2ecf20Sopenharmony_ci	return -1;
6578c2ecf20Sopenharmony_ci}
6588c2ecf20Sopenharmony_ci
6598c2ecf20Sopenharmony_cistatic int pt_open(struct inode *inode, struct file *file)
6608c2ecf20Sopenharmony_ci{
6618c2ecf20Sopenharmony_ci	int unit = iminor(inode) & 0x7F;
6628c2ecf20Sopenharmony_ci	struct pt_unit *tape = pt + unit;
6638c2ecf20Sopenharmony_ci	int err;
6648c2ecf20Sopenharmony_ci
6658c2ecf20Sopenharmony_ci	mutex_lock(&pt_mutex);
6668c2ecf20Sopenharmony_ci	if (unit >= PT_UNITS || (!tape->present)) {
6678c2ecf20Sopenharmony_ci		mutex_unlock(&pt_mutex);
6688c2ecf20Sopenharmony_ci		return -ENODEV;
6698c2ecf20Sopenharmony_ci	}
6708c2ecf20Sopenharmony_ci
6718c2ecf20Sopenharmony_ci	err = -EBUSY;
6728c2ecf20Sopenharmony_ci	if (!atomic_dec_and_test(&tape->available))
6738c2ecf20Sopenharmony_ci		goto out;
6748c2ecf20Sopenharmony_ci
6758c2ecf20Sopenharmony_ci	pt_identify(tape);
6768c2ecf20Sopenharmony_ci
6778c2ecf20Sopenharmony_ci	err = -ENODEV;
6788c2ecf20Sopenharmony_ci	if (!(tape->flags & PT_MEDIA))
6798c2ecf20Sopenharmony_ci		goto out;
6808c2ecf20Sopenharmony_ci
6818c2ecf20Sopenharmony_ci	err = -EROFS;
6828c2ecf20Sopenharmony_ci	if ((!(tape->flags & PT_WRITE_OK)) && (file->f_mode & FMODE_WRITE))
6838c2ecf20Sopenharmony_ci		goto out;
6848c2ecf20Sopenharmony_ci
6858c2ecf20Sopenharmony_ci	if (!(iminor(inode) & 128))
6868c2ecf20Sopenharmony_ci		tape->flags |= PT_REWIND;
6878c2ecf20Sopenharmony_ci
6888c2ecf20Sopenharmony_ci	err = -ENOMEM;
6898c2ecf20Sopenharmony_ci	tape->bufptr = kmalloc(PT_BUFSIZE, GFP_KERNEL);
6908c2ecf20Sopenharmony_ci	if (tape->bufptr == NULL) {
6918c2ecf20Sopenharmony_ci		printk("%s: buffer allocation failed\n", tape->name);
6928c2ecf20Sopenharmony_ci		goto out;
6938c2ecf20Sopenharmony_ci	}
6948c2ecf20Sopenharmony_ci
6958c2ecf20Sopenharmony_ci	file->private_data = tape;
6968c2ecf20Sopenharmony_ci	mutex_unlock(&pt_mutex);
6978c2ecf20Sopenharmony_ci	return 0;
6988c2ecf20Sopenharmony_ci
6998c2ecf20Sopenharmony_ciout:
7008c2ecf20Sopenharmony_ci	atomic_inc(&tape->available);
7018c2ecf20Sopenharmony_ci	mutex_unlock(&pt_mutex);
7028c2ecf20Sopenharmony_ci	return err;
7038c2ecf20Sopenharmony_ci}
7048c2ecf20Sopenharmony_ci
7058c2ecf20Sopenharmony_cistatic long pt_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
7068c2ecf20Sopenharmony_ci{
7078c2ecf20Sopenharmony_ci	struct pt_unit *tape = file->private_data;
7088c2ecf20Sopenharmony_ci	struct mtop __user *p = (void __user *)arg;
7098c2ecf20Sopenharmony_ci	struct mtop mtop;
7108c2ecf20Sopenharmony_ci
7118c2ecf20Sopenharmony_ci	switch (cmd) {
7128c2ecf20Sopenharmony_ci	case MTIOCTOP:
7138c2ecf20Sopenharmony_ci		if (copy_from_user(&mtop, p, sizeof(struct mtop)))
7148c2ecf20Sopenharmony_ci			return -EFAULT;
7158c2ecf20Sopenharmony_ci
7168c2ecf20Sopenharmony_ci		switch (mtop.mt_op) {
7178c2ecf20Sopenharmony_ci
7188c2ecf20Sopenharmony_ci		case MTREW:
7198c2ecf20Sopenharmony_ci			mutex_lock(&pt_mutex);
7208c2ecf20Sopenharmony_ci			pt_rewind(tape);
7218c2ecf20Sopenharmony_ci			mutex_unlock(&pt_mutex);
7228c2ecf20Sopenharmony_ci			return 0;
7238c2ecf20Sopenharmony_ci
7248c2ecf20Sopenharmony_ci		case MTWEOF:
7258c2ecf20Sopenharmony_ci			mutex_lock(&pt_mutex);
7268c2ecf20Sopenharmony_ci			pt_write_fm(tape);
7278c2ecf20Sopenharmony_ci			mutex_unlock(&pt_mutex);
7288c2ecf20Sopenharmony_ci			return 0;
7298c2ecf20Sopenharmony_ci
7308c2ecf20Sopenharmony_ci		default:
7318c2ecf20Sopenharmony_ci			/* FIXME: rate limit ?? */
7328c2ecf20Sopenharmony_ci			printk(KERN_DEBUG "%s: Unimplemented mt_op %d\n", tape->name,
7338c2ecf20Sopenharmony_ci			       mtop.mt_op);
7348c2ecf20Sopenharmony_ci			return -EINVAL;
7358c2ecf20Sopenharmony_ci		}
7368c2ecf20Sopenharmony_ci
7378c2ecf20Sopenharmony_ci	default:
7388c2ecf20Sopenharmony_ci		return -ENOTTY;
7398c2ecf20Sopenharmony_ci	}
7408c2ecf20Sopenharmony_ci}
7418c2ecf20Sopenharmony_ci
7428c2ecf20Sopenharmony_cistatic int
7438c2ecf20Sopenharmony_cipt_release(struct inode *inode, struct file *file)
7448c2ecf20Sopenharmony_ci{
7458c2ecf20Sopenharmony_ci	struct pt_unit *tape = file->private_data;
7468c2ecf20Sopenharmony_ci
7478c2ecf20Sopenharmony_ci	if (atomic_read(&tape->available) > 1)
7488c2ecf20Sopenharmony_ci		return -EINVAL;
7498c2ecf20Sopenharmony_ci
7508c2ecf20Sopenharmony_ci	if (tape->flags & PT_WRITING)
7518c2ecf20Sopenharmony_ci		pt_write_fm(tape);
7528c2ecf20Sopenharmony_ci
7538c2ecf20Sopenharmony_ci	if (tape->flags & PT_REWIND)
7548c2ecf20Sopenharmony_ci		pt_rewind(tape);
7558c2ecf20Sopenharmony_ci
7568c2ecf20Sopenharmony_ci	kfree(tape->bufptr);
7578c2ecf20Sopenharmony_ci	tape->bufptr = NULL;
7588c2ecf20Sopenharmony_ci
7598c2ecf20Sopenharmony_ci	atomic_inc(&tape->available);
7608c2ecf20Sopenharmony_ci
7618c2ecf20Sopenharmony_ci	return 0;
7628c2ecf20Sopenharmony_ci
7638c2ecf20Sopenharmony_ci}
7648c2ecf20Sopenharmony_ci
7658c2ecf20Sopenharmony_cistatic ssize_t pt_read(struct file *filp, char __user *buf, size_t count, loff_t * ppos)
7668c2ecf20Sopenharmony_ci{
7678c2ecf20Sopenharmony_ci	struct pt_unit *tape = filp->private_data;
7688c2ecf20Sopenharmony_ci	struct pi_adapter *pi = tape->pi;
7698c2ecf20Sopenharmony_ci	char rd_cmd[12] = { ATAPI_READ_6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
7708c2ecf20Sopenharmony_ci	int k, n, r, p, s, t, b;
7718c2ecf20Sopenharmony_ci
7728c2ecf20Sopenharmony_ci	if (!(tape->flags & (PT_READING | PT_WRITING))) {
7738c2ecf20Sopenharmony_ci		tape->flags |= PT_READING;
7748c2ecf20Sopenharmony_ci		if (pt_atapi(tape, rd_cmd, 0, NULL, "start read-ahead"))
7758c2ecf20Sopenharmony_ci			return -EIO;
7768c2ecf20Sopenharmony_ci	} else if (tape->flags & PT_WRITING)
7778c2ecf20Sopenharmony_ci		return -EIO;
7788c2ecf20Sopenharmony_ci
7798c2ecf20Sopenharmony_ci	if (tape->flags & PT_EOF)
7808c2ecf20Sopenharmony_ci		return 0;
7818c2ecf20Sopenharmony_ci
7828c2ecf20Sopenharmony_ci	t = 0;
7838c2ecf20Sopenharmony_ci
7848c2ecf20Sopenharmony_ci	while (count > 0) {
7858c2ecf20Sopenharmony_ci
7868c2ecf20Sopenharmony_ci		if (!pt_poll_dsc(tape, HZ / 100, PT_TMO, "read"))
7878c2ecf20Sopenharmony_ci			return -EIO;
7888c2ecf20Sopenharmony_ci
7898c2ecf20Sopenharmony_ci		n = count;
7908c2ecf20Sopenharmony_ci		if (n > 32768)
7918c2ecf20Sopenharmony_ci			n = 32768;	/* max per command */
7928c2ecf20Sopenharmony_ci		b = (n - 1 + tape->bs) / tape->bs;
7938c2ecf20Sopenharmony_ci		n = b * tape->bs;	/* rounded up to even block */
7948c2ecf20Sopenharmony_ci
7958c2ecf20Sopenharmony_ci		rd_cmd[4] = b;
7968c2ecf20Sopenharmony_ci
7978c2ecf20Sopenharmony_ci		r = pt_command(tape, rd_cmd, n, "read");
7988c2ecf20Sopenharmony_ci
7998c2ecf20Sopenharmony_ci		mdelay(1);
8008c2ecf20Sopenharmony_ci
8018c2ecf20Sopenharmony_ci		if (r) {
8028c2ecf20Sopenharmony_ci			pt_req_sense(tape, 0);
8038c2ecf20Sopenharmony_ci			return -EIO;
8048c2ecf20Sopenharmony_ci		}
8058c2ecf20Sopenharmony_ci
8068c2ecf20Sopenharmony_ci		while (1) {
8078c2ecf20Sopenharmony_ci
8088c2ecf20Sopenharmony_ci			r = pt_wait(tape, STAT_BUSY,
8098c2ecf20Sopenharmony_ci				    STAT_DRQ | STAT_ERR | STAT_READY,
8108c2ecf20Sopenharmony_ci				    DBMSG("read DRQ"), "");
8118c2ecf20Sopenharmony_ci
8128c2ecf20Sopenharmony_ci			if (r & STAT_SENSE) {
8138c2ecf20Sopenharmony_ci				pi_disconnect(pi);
8148c2ecf20Sopenharmony_ci				pt_req_sense(tape, 0);
8158c2ecf20Sopenharmony_ci				return -EIO;
8168c2ecf20Sopenharmony_ci			}
8178c2ecf20Sopenharmony_ci
8188c2ecf20Sopenharmony_ci			if (r)
8198c2ecf20Sopenharmony_ci				tape->flags |= PT_EOF;
8208c2ecf20Sopenharmony_ci
8218c2ecf20Sopenharmony_ci			s = read_reg(pi, 7);
8228c2ecf20Sopenharmony_ci
8238c2ecf20Sopenharmony_ci			if (!(s & STAT_DRQ))
8248c2ecf20Sopenharmony_ci				break;
8258c2ecf20Sopenharmony_ci
8268c2ecf20Sopenharmony_ci			n = (read_reg(pi, 4) + 256 * read_reg(pi, 5));
8278c2ecf20Sopenharmony_ci			p = (read_reg(pi, 2) & 3);
8288c2ecf20Sopenharmony_ci			if (p != 2) {
8298c2ecf20Sopenharmony_ci				pi_disconnect(pi);
8308c2ecf20Sopenharmony_ci				printk("%s: Phase error on read: %d\n", tape->name,
8318c2ecf20Sopenharmony_ci				       p);
8328c2ecf20Sopenharmony_ci				return -EIO;
8338c2ecf20Sopenharmony_ci			}
8348c2ecf20Sopenharmony_ci
8358c2ecf20Sopenharmony_ci			while (n > 0) {
8368c2ecf20Sopenharmony_ci				k = n;
8378c2ecf20Sopenharmony_ci				if (k > PT_BUFSIZE)
8388c2ecf20Sopenharmony_ci					k = PT_BUFSIZE;
8398c2ecf20Sopenharmony_ci				pi_read_block(pi, tape->bufptr, k);
8408c2ecf20Sopenharmony_ci				n -= k;
8418c2ecf20Sopenharmony_ci				b = k;
8428c2ecf20Sopenharmony_ci				if (b > count)
8438c2ecf20Sopenharmony_ci					b = count;
8448c2ecf20Sopenharmony_ci				if (copy_to_user(buf + t, tape->bufptr, b)) {
8458c2ecf20Sopenharmony_ci					pi_disconnect(pi);
8468c2ecf20Sopenharmony_ci					return -EFAULT;
8478c2ecf20Sopenharmony_ci				}
8488c2ecf20Sopenharmony_ci				t += b;
8498c2ecf20Sopenharmony_ci				count -= b;
8508c2ecf20Sopenharmony_ci			}
8518c2ecf20Sopenharmony_ci
8528c2ecf20Sopenharmony_ci		}
8538c2ecf20Sopenharmony_ci		pi_disconnect(pi);
8548c2ecf20Sopenharmony_ci		if (tape->flags & PT_EOF)
8558c2ecf20Sopenharmony_ci			break;
8568c2ecf20Sopenharmony_ci	}
8578c2ecf20Sopenharmony_ci
8588c2ecf20Sopenharmony_ci	return t;
8598c2ecf20Sopenharmony_ci
8608c2ecf20Sopenharmony_ci}
8618c2ecf20Sopenharmony_ci
8628c2ecf20Sopenharmony_cistatic ssize_t pt_write(struct file *filp, const char __user *buf, size_t count, loff_t * ppos)
8638c2ecf20Sopenharmony_ci{
8648c2ecf20Sopenharmony_ci	struct pt_unit *tape = filp->private_data;
8658c2ecf20Sopenharmony_ci	struct pi_adapter *pi = tape->pi;
8668c2ecf20Sopenharmony_ci	char wr_cmd[12] = { ATAPI_WRITE_6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
8678c2ecf20Sopenharmony_ci	int k, n, r, p, s, t, b;
8688c2ecf20Sopenharmony_ci
8698c2ecf20Sopenharmony_ci	if (!(tape->flags & PT_WRITE_OK))
8708c2ecf20Sopenharmony_ci		return -EROFS;
8718c2ecf20Sopenharmony_ci
8728c2ecf20Sopenharmony_ci	if (!(tape->flags & (PT_READING | PT_WRITING))) {
8738c2ecf20Sopenharmony_ci		tape->flags |= PT_WRITING;
8748c2ecf20Sopenharmony_ci		if (pt_atapi
8758c2ecf20Sopenharmony_ci		    (tape, wr_cmd, 0, NULL, "start buffer-available mode"))
8768c2ecf20Sopenharmony_ci			return -EIO;
8778c2ecf20Sopenharmony_ci	} else if (tape->flags & PT_READING)
8788c2ecf20Sopenharmony_ci		return -EIO;
8798c2ecf20Sopenharmony_ci
8808c2ecf20Sopenharmony_ci	if (tape->flags & PT_EOF)
8818c2ecf20Sopenharmony_ci		return -ENOSPC;
8828c2ecf20Sopenharmony_ci
8838c2ecf20Sopenharmony_ci	t = 0;
8848c2ecf20Sopenharmony_ci
8858c2ecf20Sopenharmony_ci	while (count > 0) {
8868c2ecf20Sopenharmony_ci
8878c2ecf20Sopenharmony_ci		if (!pt_poll_dsc(tape, HZ / 100, PT_TMO, "write"))
8888c2ecf20Sopenharmony_ci			return -EIO;
8898c2ecf20Sopenharmony_ci
8908c2ecf20Sopenharmony_ci		n = count;
8918c2ecf20Sopenharmony_ci		if (n > 32768)
8928c2ecf20Sopenharmony_ci			n = 32768;	/* max per command */
8938c2ecf20Sopenharmony_ci		b = (n - 1 + tape->bs) / tape->bs;
8948c2ecf20Sopenharmony_ci		n = b * tape->bs;	/* rounded up to even block */
8958c2ecf20Sopenharmony_ci
8968c2ecf20Sopenharmony_ci		wr_cmd[4] = b;
8978c2ecf20Sopenharmony_ci
8988c2ecf20Sopenharmony_ci		r = pt_command(tape, wr_cmd, n, "write");
8998c2ecf20Sopenharmony_ci
9008c2ecf20Sopenharmony_ci		mdelay(1);
9018c2ecf20Sopenharmony_ci
9028c2ecf20Sopenharmony_ci		if (r) {	/* error delivering command only */
9038c2ecf20Sopenharmony_ci			pt_req_sense(tape, 0);
9048c2ecf20Sopenharmony_ci			return -EIO;
9058c2ecf20Sopenharmony_ci		}
9068c2ecf20Sopenharmony_ci
9078c2ecf20Sopenharmony_ci		while (1) {
9088c2ecf20Sopenharmony_ci
9098c2ecf20Sopenharmony_ci			r = pt_wait(tape, STAT_BUSY,
9108c2ecf20Sopenharmony_ci				    STAT_DRQ | STAT_ERR | STAT_READY,
9118c2ecf20Sopenharmony_ci				    DBMSG("write DRQ"), NULL);
9128c2ecf20Sopenharmony_ci
9138c2ecf20Sopenharmony_ci			if (r & STAT_SENSE) {
9148c2ecf20Sopenharmony_ci				pi_disconnect(pi);
9158c2ecf20Sopenharmony_ci				pt_req_sense(tape, 0);
9168c2ecf20Sopenharmony_ci				return -EIO;
9178c2ecf20Sopenharmony_ci			}
9188c2ecf20Sopenharmony_ci
9198c2ecf20Sopenharmony_ci			if (r)
9208c2ecf20Sopenharmony_ci				tape->flags |= PT_EOF;
9218c2ecf20Sopenharmony_ci
9228c2ecf20Sopenharmony_ci			s = read_reg(pi, 7);
9238c2ecf20Sopenharmony_ci
9248c2ecf20Sopenharmony_ci			if (!(s & STAT_DRQ))
9258c2ecf20Sopenharmony_ci				break;
9268c2ecf20Sopenharmony_ci
9278c2ecf20Sopenharmony_ci			n = (read_reg(pi, 4) + 256 * read_reg(pi, 5));
9288c2ecf20Sopenharmony_ci			p = (read_reg(pi, 2) & 3);
9298c2ecf20Sopenharmony_ci			if (p != 0) {
9308c2ecf20Sopenharmony_ci				pi_disconnect(pi);
9318c2ecf20Sopenharmony_ci				printk("%s: Phase error on write: %d \n",
9328c2ecf20Sopenharmony_ci				       tape->name, p);
9338c2ecf20Sopenharmony_ci				return -EIO;
9348c2ecf20Sopenharmony_ci			}
9358c2ecf20Sopenharmony_ci
9368c2ecf20Sopenharmony_ci			while (n > 0) {
9378c2ecf20Sopenharmony_ci				k = n;
9388c2ecf20Sopenharmony_ci				if (k > PT_BUFSIZE)
9398c2ecf20Sopenharmony_ci					k = PT_BUFSIZE;
9408c2ecf20Sopenharmony_ci				b = k;
9418c2ecf20Sopenharmony_ci				if (b > count)
9428c2ecf20Sopenharmony_ci					b = count;
9438c2ecf20Sopenharmony_ci				if (copy_from_user(tape->bufptr, buf + t, b)) {
9448c2ecf20Sopenharmony_ci					pi_disconnect(pi);
9458c2ecf20Sopenharmony_ci					return -EFAULT;
9468c2ecf20Sopenharmony_ci				}
9478c2ecf20Sopenharmony_ci				pi_write_block(pi, tape->bufptr, k);
9488c2ecf20Sopenharmony_ci				t += b;
9498c2ecf20Sopenharmony_ci				count -= b;
9508c2ecf20Sopenharmony_ci				n -= k;
9518c2ecf20Sopenharmony_ci			}
9528c2ecf20Sopenharmony_ci
9538c2ecf20Sopenharmony_ci		}
9548c2ecf20Sopenharmony_ci		pi_disconnect(pi);
9558c2ecf20Sopenharmony_ci		if (tape->flags & PT_EOF)
9568c2ecf20Sopenharmony_ci			break;
9578c2ecf20Sopenharmony_ci	}
9588c2ecf20Sopenharmony_ci
9598c2ecf20Sopenharmony_ci	return t;
9608c2ecf20Sopenharmony_ci}
9618c2ecf20Sopenharmony_ci
9628c2ecf20Sopenharmony_cistatic int __init pt_init(void)
9638c2ecf20Sopenharmony_ci{
9648c2ecf20Sopenharmony_ci	int unit;
9658c2ecf20Sopenharmony_ci	int err;
9668c2ecf20Sopenharmony_ci
9678c2ecf20Sopenharmony_ci	if (disable) {
9688c2ecf20Sopenharmony_ci		err = -EINVAL;
9698c2ecf20Sopenharmony_ci		goto out;
9708c2ecf20Sopenharmony_ci	}
9718c2ecf20Sopenharmony_ci
9728c2ecf20Sopenharmony_ci	if (pt_detect()) {
9738c2ecf20Sopenharmony_ci		err = -ENODEV;
9748c2ecf20Sopenharmony_ci		goto out;
9758c2ecf20Sopenharmony_ci	}
9768c2ecf20Sopenharmony_ci
9778c2ecf20Sopenharmony_ci	err = register_chrdev(major, name, &pt_fops);
9788c2ecf20Sopenharmony_ci	if (err < 0) {
9798c2ecf20Sopenharmony_ci		printk("pt_init: unable to get major number %d\n", major);
9808c2ecf20Sopenharmony_ci		for (unit = 0; unit < PT_UNITS; unit++)
9818c2ecf20Sopenharmony_ci			if (pt[unit].present)
9828c2ecf20Sopenharmony_ci				pi_release(pt[unit].pi);
9838c2ecf20Sopenharmony_ci		goto out;
9848c2ecf20Sopenharmony_ci	}
9858c2ecf20Sopenharmony_ci	major = err;
9868c2ecf20Sopenharmony_ci	pt_class = class_create(THIS_MODULE, "pt");
9878c2ecf20Sopenharmony_ci	if (IS_ERR(pt_class)) {
9888c2ecf20Sopenharmony_ci		err = PTR_ERR(pt_class);
9898c2ecf20Sopenharmony_ci		goto out_chrdev;
9908c2ecf20Sopenharmony_ci	}
9918c2ecf20Sopenharmony_ci
9928c2ecf20Sopenharmony_ci	for (unit = 0; unit < PT_UNITS; unit++)
9938c2ecf20Sopenharmony_ci		if (pt[unit].present) {
9948c2ecf20Sopenharmony_ci			device_create(pt_class, NULL, MKDEV(major, unit), NULL,
9958c2ecf20Sopenharmony_ci				      "pt%d", unit);
9968c2ecf20Sopenharmony_ci			device_create(pt_class, NULL, MKDEV(major, unit + 128),
9978c2ecf20Sopenharmony_ci				      NULL, "pt%dn", unit);
9988c2ecf20Sopenharmony_ci		}
9998c2ecf20Sopenharmony_ci	goto out;
10008c2ecf20Sopenharmony_ci
10018c2ecf20Sopenharmony_ciout_chrdev:
10028c2ecf20Sopenharmony_ci	unregister_chrdev(major, "pt");
10038c2ecf20Sopenharmony_ciout:
10048c2ecf20Sopenharmony_ci	return err;
10058c2ecf20Sopenharmony_ci}
10068c2ecf20Sopenharmony_ci
10078c2ecf20Sopenharmony_cistatic void __exit pt_exit(void)
10088c2ecf20Sopenharmony_ci{
10098c2ecf20Sopenharmony_ci	int unit;
10108c2ecf20Sopenharmony_ci	for (unit = 0; unit < PT_UNITS; unit++)
10118c2ecf20Sopenharmony_ci		if (pt[unit].present) {
10128c2ecf20Sopenharmony_ci			device_destroy(pt_class, MKDEV(major, unit));
10138c2ecf20Sopenharmony_ci			device_destroy(pt_class, MKDEV(major, unit + 128));
10148c2ecf20Sopenharmony_ci		}
10158c2ecf20Sopenharmony_ci	class_destroy(pt_class);
10168c2ecf20Sopenharmony_ci	unregister_chrdev(major, name);
10178c2ecf20Sopenharmony_ci	for (unit = 0; unit < PT_UNITS; unit++)
10188c2ecf20Sopenharmony_ci		if (pt[unit].present)
10198c2ecf20Sopenharmony_ci			pi_release(pt[unit].pi);
10208c2ecf20Sopenharmony_ci}
10218c2ecf20Sopenharmony_ci
10228c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
10238c2ecf20Sopenharmony_cimodule_init(pt_init)
10248c2ecf20Sopenharmony_cimodule_exit(pt_exit)
1025