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