root/drivers/ata/pata_parport/bpck6.c
// 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);