diff options
Diffstat (limited to 'drivers/ata/pata_parport/bpck6.c')
-rw-r--r-- | drivers/ata/pata_parport/bpck6.c | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/drivers/ata/pata_parport/bpck6.c b/drivers/ata/pata_parport/bpck6.c new file mode 100644 index 0000000000..62c2b53325 --- /dev/null +++ b/drivers/ata/pata_parport/bpck6.c @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * (c) 2001 Micro Solutions Inc. + * + * backpack.c is a low-level protocol driver for the Micro Solutions + * "BACKPACK" parallel port IDE adapter (works on Series 6 drives). + * + * Written by: Ken Hahn (linux-dev@micro-solutions.com) + * Clive Turvey (linux-dev@micro-solutions.com) + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/parport.h> +#include "pata_parport.h" + +/* 60772 Commands */ +#define ACCESS_REG 0x00 +#define ACCESS_PORT 0x40 + +#define ACCESS_READ 0x00 +#define ACCESS_WRITE 0x20 + +/* 60772 Command Prefix */ +#define CMD_PREFIX_SET 0xe0 // Special command that modifies next command's operation +#define CMD_PREFIX_RESET 0xc0 // Resets current cmd modifier reg bits + #define PREFIX_IO16 0x01 // perform 16-bit wide I/O + #define PREFIX_FASTWR 0x04 // enable PPC mode fast-write + #define PREFIX_BLK 0x08 // enable block transfer mode + +/* 60772 Registers */ +#define REG_STATUS 0x00 // status register + #define STATUS_IRQA 0x01 // Peripheral IRQA line + #define STATUS_EEPROM_DO 0x40 // Serial EEPROM data bit +#define REG_VERSION 0x01 // PPC version register (read) +#define REG_HWCFG 0x02 // Hardware Config register +#define REG_RAMSIZE 0x03 // Size of RAM Buffer + #define RAMSIZE_128K 0x02 +#define REG_EEPROM 0x06 // EEPROM control register + #define EEPROM_SK 0x01 // eeprom SK bit + #define EEPROM_DI 0x02 // eeprom DI bit + #define EEPROM_CS 0x04 // eeprom CS bit + #define EEPROM_EN 0x08 // eeprom output enable +#define REG_BLKSIZE 0x08 // Block transfer len (24 bit) + +/* flags */ +#define fifo_wait 0x10 + +/* DONT CHANGE THESE LEST YOU BREAK EVERYTHING - BIT FIELD DEPENDENCIES */ +#define PPCMODE_UNI_SW 0 +#define PPCMODE_UNI_FW 1 +#define PPCMODE_BI_SW 2 +#define PPCMODE_BI_FW 3 +#define PPCMODE_EPP_BYTE 4 +#define PPCMODE_EPP_WORD 5 +#define PPCMODE_EPP_DWORD 6 + +static int mode_map[] = { PPCMODE_UNI_FW, PPCMODE_BI_FW, PPCMODE_EPP_BYTE, + PPCMODE_EPP_WORD, PPCMODE_EPP_DWORD }; + +static void bpck6_send_cmd(struct pi_adapter *pi, u8 cmd) +{ + switch (mode_map[pi->mode]) { + case PPCMODE_UNI_SW: + case PPCMODE_UNI_FW: + case PPCMODE_BI_SW: + case PPCMODE_BI_FW: + parport_write_data(pi->pardev->port, cmd); + parport_frob_control(pi->pardev->port, 0, PARPORT_CONTROL_AUTOFD); + break; + case PPCMODE_EPP_BYTE: + case PPCMODE_EPP_WORD: + case PPCMODE_EPP_DWORD: + pi->pardev->port->ops->epp_write_addr(pi->pardev->port, &cmd, 1, 0); + break; + } +} + +static u8 bpck6_rd_data_byte(struct pi_adapter *pi) +{ + u8 data = 0; + + switch (mode_map[pi->mode]) { + case PPCMODE_UNI_SW: + case PPCMODE_UNI_FW: + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_STROBE, + PARPORT_CONTROL_INIT); + data = parport_read_status(pi->pardev->port); + data = ((data & 0x80) >> 1) | ((data & 0x38) >> 3); + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_STROBE, + PARPORT_CONTROL_STROBE); + data |= parport_read_status(pi->pardev->port) & 0xB8; + break; + case PPCMODE_BI_SW: + case PPCMODE_BI_FW: + parport_data_reverse(pi->pardev->port); + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_STROBE, + PARPORT_CONTROL_STROBE | PARPORT_CONTROL_INIT); + data = parport_read_data(pi->pardev->port); + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_STROBE, 0); + parport_data_forward(pi->pardev->port); + break; + case PPCMODE_EPP_BYTE: + case PPCMODE_EPP_WORD: + case PPCMODE_EPP_DWORD: + pi->pardev->port->ops->epp_read_data(pi->pardev->port, &data, 1, 0); + break; + } + + return data; +} + +static void bpck6_wr_data_byte(struct pi_adapter *pi, u8 data) +{ + switch (mode_map[pi->mode]) { + case PPCMODE_UNI_SW: + case PPCMODE_UNI_FW: + case PPCMODE_BI_SW: + case PPCMODE_BI_FW: + parport_write_data(pi->pardev->port, data); + parport_frob_control(pi->pardev->port, 0, PARPORT_CONTROL_INIT); + break; + case PPCMODE_EPP_BYTE: + case PPCMODE_EPP_WORD: + case PPCMODE_EPP_DWORD: + pi->pardev->port->ops->epp_write_data(pi->pardev->port, &data, 1, 0); + break; + } +} + +static int bpck6_read_regr(struct pi_adapter *pi, int cont, int reg) +{ + u8 port = cont ? reg | 8 : reg; + + bpck6_send_cmd(pi, port | ACCESS_PORT | ACCESS_READ); + return bpck6_rd_data_byte(pi); +} + +static void bpck6_write_regr(struct pi_adapter *pi, int cont, int reg, int val) +{ + u8 port = cont ? reg | 8 : reg; + + bpck6_send_cmd(pi, port | ACCESS_PORT | ACCESS_WRITE); + bpck6_wr_data_byte(pi, val); +} + +static void bpck6_wait_for_fifo(struct pi_adapter *pi) +{ + int i; + + if (pi->private & fifo_wait) { + for (i = 0; i < 20; i++) + parport_read_status(pi->pardev->port); + } +} + +static void bpck6_write_block(struct pi_adapter *pi, char *buf, int len) +{ + u8 this, last; + + bpck6_send_cmd(pi, REG_BLKSIZE | ACCESS_REG | ACCESS_WRITE); + bpck6_wr_data_byte(pi, (u8)len); + bpck6_wr_data_byte(pi, (u8)(len >> 8)); + bpck6_wr_data_byte(pi, 0); + + bpck6_send_cmd(pi, CMD_PREFIX_SET | PREFIX_IO16 | PREFIX_BLK); + bpck6_send_cmd(pi, ATA_REG_DATA | ACCESS_PORT | ACCESS_WRITE); + + switch (mode_map[pi->mode]) { + case PPCMODE_UNI_SW: + case PPCMODE_BI_SW: + while (len--) { + parport_write_data(pi->pardev->port, *buf++); + parport_frob_control(pi->pardev->port, 0, + PARPORT_CONTROL_INIT); + } + break; + case PPCMODE_UNI_FW: + case PPCMODE_BI_FW: + bpck6_send_cmd(pi, CMD_PREFIX_SET | PREFIX_FASTWR); + + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_STROBE, + PARPORT_CONTROL_STROBE); + + last = *buf; + + parport_write_data(pi->pardev->port, last); + + while (len) { + this = *buf++; + len--; + + if (this == last) { + parport_frob_control(pi->pardev->port, 0, + PARPORT_CONTROL_INIT); + } else { + parport_write_data(pi->pardev->port, this); + last = this; + } + } + + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_STROBE, + 0); + bpck6_send_cmd(pi, CMD_PREFIX_RESET | PREFIX_FASTWR); + break; + case PPCMODE_EPP_BYTE: + pi->pardev->port->ops->epp_write_data(pi->pardev->port, buf, + len, PARPORT_EPP_FAST_8); + bpck6_wait_for_fifo(pi); + break; + case PPCMODE_EPP_WORD: + pi->pardev->port->ops->epp_write_data(pi->pardev->port, buf, + len, PARPORT_EPP_FAST_16); + bpck6_wait_for_fifo(pi); + break; + case PPCMODE_EPP_DWORD: + pi->pardev->port->ops->epp_write_data(pi->pardev->port, buf, + len, PARPORT_EPP_FAST_32); + bpck6_wait_for_fifo(pi); + break; + } + + bpck6_send_cmd(pi, CMD_PREFIX_RESET | PREFIX_IO16 | PREFIX_BLK); +} + +static void bpck6_read_block(struct pi_adapter *pi, char *buf, int len) +{ + bpck6_send_cmd(pi, REG_BLKSIZE | ACCESS_REG | ACCESS_WRITE); + bpck6_wr_data_byte(pi, (u8)len); + bpck6_wr_data_byte(pi, (u8)(len >> 8)); + bpck6_wr_data_byte(pi, 0); + + bpck6_send_cmd(pi, CMD_PREFIX_SET | PREFIX_IO16 | PREFIX_BLK); + bpck6_send_cmd(pi, ATA_REG_DATA | ACCESS_PORT | ACCESS_READ); + + switch (mode_map[pi->mode]) { + case PPCMODE_UNI_SW: + case PPCMODE_UNI_FW: + while (len) { + u8 d; + + parport_frob_control(pi->pardev->port, + PARPORT_CONTROL_STROBE, + PARPORT_CONTROL_INIT); /* DATA STROBE */ + d = parport_read_status(pi->pardev->port); + d = ((d & 0x80) >> 1) | ((d & 0x38) >> 3); + parport_frob_control(pi->pardev->port, + PARPORT_CONTROL_STROBE, + PARPORT_CONTROL_STROBE); + d |= parport_read_status(pi->pardev->port) & 0xB8; + *buf++ = d; + len--; + } + break; + case PPCMODE_BI_SW: + case PPCMODE_BI_FW: + parport_data_reverse(pi->pardev->port); + while (len) { + parport_frob_control(pi->pardev->port, + PARPORT_CONTROL_STROBE, + PARPORT_CONTROL_STROBE | PARPORT_CONTROL_INIT); + *buf++ = parport_read_data(pi->pardev->port); + len--; + } + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_STROBE, + 0); + parport_data_forward(pi->pardev->port); + break; + case PPCMODE_EPP_BYTE: + pi->pardev->port->ops->epp_read_data(pi->pardev->port, buf, len, + PARPORT_EPP_FAST_8); + break; + case PPCMODE_EPP_WORD: + pi->pardev->port->ops->epp_read_data(pi->pardev->port, buf, len, + PARPORT_EPP_FAST_16); + break; + case PPCMODE_EPP_DWORD: + pi->pardev->port->ops->epp_read_data(pi->pardev->port, buf, len, + PARPORT_EPP_FAST_32); + break; + } + + bpck6_send_cmd(pi, CMD_PREFIX_RESET | PREFIX_IO16 | PREFIX_BLK); +} + +static int bpck6_open(struct pi_adapter *pi) +{ + u8 i, j, k; + + pi->saved_r0 = parport_read_data(pi->pardev->port); + pi->saved_r2 = parport_read_control(pi->pardev->port) & 0x5F; + + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_SELECT, + PARPORT_CONTROL_SELECT); + if (pi->saved_r0 == 'b') + parport_write_data(pi->pardev->port, 'x'); + parport_write_data(pi->pardev->port, 'b'); + parport_write_data(pi->pardev->port, 'p'); + parport_write_data(pi->pardev->port, pi->unit); + parport_write_data(pi->pardev->port, ~pi->unit); + + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_SELECT, 0); + parport_write_control(pi->pardev->port, PARPORT_CONTROL_INIT); + + i = mode_map[pi->mode] & 0x0C; + if (i == 0) + i = (mode_map[pi->mode] & 2) | 1; + parport_write_data(pi->pardev->port, i); + + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_SELECT, + PARPORT_CONTROL_SELECT); + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_AUTOFD, + PARPORT_CONTROL_AUTOFD); + + j = ((i & 0x08) << 4) | ((i & 0x07) << 3); + k = parport_read_status(pi->pardev->port) & 0xB8; + if (j != k) + goto fail; + + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_AUTOFD, 0); + k = (parport_read_status(pi->pardev->port) & 0xB8) ^ 0xB8; + if (j != k) + goto fail; + + if (i & 4) { + /* EPP */ + parport_frob_control(pi->pardev->port, + PARPORT_CONTROL_SELECT | PARPORT_CONTROL_INIT, 0); + } else { + /* PPC/ECP */ + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_SELECT, 0); + } + + pi->private = 0; + + bpck6_send_cmd(pi, ACCESS_REG | ACCESS_WRITE | REG_RAMSIZE); + bpck6_wr_data_byte(pi, RAMSIZE_128K); + + bpck6_send_cmd(pi, ACCESS_REG | ACCESS_READ | REG_VERSION); + if ((bpck6_rd_data_byte(pi) & 0x3F) == 0x0C) + pi->private |= fifo_wait; + + return 1; + +fail: + parport_write_control(pi->pardev->port, pi->saved_r2); + parport_write_data(pi->pardev->port, pi->saved_r0); + + return 0; +} + +static void bpck6_deselect(struct pi_adapter *pi) +{ + if (mode_map[pi->mode] & 4) { + /* EPP */ + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_INIT, + PARPORT_CONTROL_INIT); + } else { + /* PPC/ECP */ + parport_frob_control(pi->pardev->port, PARPORT_CONTROL_SELECT, + PARPORT_CONTROL_SELECT); + } + + parport_write_data(pi->pardev->port, pi->saved_r0); + parport_write_control(pi->pardev->port, + pi->saved_r2 | PARPORT_CONTROL_SELECT); + parport_write_control(pi->pardev->port, pi->saved_r2); +} + +static void bpck6_wr_extout(struct pi_adapter *pi, u8 regdata) +{ + bpck6_send_cmd(pi, REG_VERSION | ACCESS_REG | ACCESS_WRITE); + bpck6_wr_data_byte(pi, (u8)((regdata & 0x03) << 6)); +} + +static void bpck6_connect(struct pi_adapter *pi) +{ + dev_dbg(&pi->dev, "connect\n"); + + bpck6_open(pi); + bpck6_wr_extout(pi, 0x3); +} + +static void bpck6_disconnect(struct pi_adapter *pi) +{ + dev_dbg(&pi->dev, "disconnect\n"); + bpck6_wr_extout(pi, 0x0); + bpck6_deselect(pi); +} + +/* check for 8-bit port */ +static int bpck6_test_port(struct pi_adapter *pi) +{ + dev_dbg(&pi->dev, "PARPORT indicates modes=%x for lp=0x%lx\n", + pi->pardev->port->modes, pi->pardev->port->base); + + /* look at the parport device to see what modes we can use */ + if (pi->pardev->port->modes & PARPORT_MODE_EPP) + return 5; /* Can do EPP */ + if (pi->pardev->port->modes & PARPORT_MODE_TRISTATE) + return 2; + return 1; /* Just flat SPP */ +} + +static int bpck6_probe_unit(struct pi_adapter *pi) +{ + int out, saved_mode; + + dev_dbg(&pi->dev, "PROBE UNIT %x on port:%x\n", pi->unit, pi->port); + + saved_mode = pi->mode; + /*LOWER DOWN TO UNIDIRECTIONAL*/ + pi->mode = 0; + + out = bpck6_open(pi); + + dev_dbg(&pi->dev, "ppc_open returned %2x\n", out); + + if (out) { + bpck6_deselect(pi); + dev_dbg(&pi->dev, "leaving probe\n"); + pi->mode = saved_mode; + return 1; + } + + dev_dbg(&pi->dev, "Failed open\n"); + pi->mode = saved_mode; + + return 0; +} + +static void bpck6_log_adapter(struct pi_adapter *pi) +{ + char *mode_string[5] = { "4-bit", "8-bit", "EPP-8", "EPP-16", "EPP-32" }; + + dev_info(&pi->dev, + "Micro Solutions BACKPACK Drive unit %d at 0x%x, mode:%d (%s), delay %d\n", + pi->unit, pi->port, pi->mode, mode_string[pi->mode], pi->delay); +} + +static struct pi_protocol bpck6 = { + .owner = THIS_MODULE, + .name = "bpck6", + .max_mode = 5, + .epp_first = 2, /* 2-5 use epp (need 8 ports) */ + .max_units = 255, + .write_regr = bpck6_write_regr, + .read_regr = bpck6_read_regr, + .write_block = bpck6_write_block, + .read_block = bpck6_read_block, + .connect = bpck6_connect, + .disconnect = bpck6_disconnect, + .test_port = bpck6_test_port, + .probe_unit = bpck6_probe_unit, + .log_adapter = bpck6_log_adapter, +}; + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Micro Solutions Inc."); +MODULE_DESCRIPTION("Micro Solutions BACKPACK parallel port IDE adapter " + "(version 6 drives) protocol driver"); +module_pata_parport_driver(bpck6); |