aboutsummaryrefslogtreecommitdiff
path: root/ch375.h
diff options
context:
space:
mode:
Diffstat (limited to 'ch375.h')
-rw-r--r--ch375.h423
1 files changed, 423 insertions, 0 deletions
diff --git a/ch375.h b/ch375.h
new file mode 100644
index 0000000..a1ff1b0
--- /dev/null
+++ b/ch375.h
@@ -0,0 +1,423 @@
+/*
+ * CH375 hardware header
+ * Copyright (C) 2024 Javier S. Pedro
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef CH375_H
+#define CH375_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <conio.h>
+#include <i86.h>
+
+#include "usb.h"
+
+extern unsigned short ch375_port;
+
+enum {
+ CH375_PORT_DATA = 0,
+ CH375_PORT_CMD = 1,
+};
+
+enum ch375_command {
+ /** Returns chip and firmware version in bit 5-0.
+ * output: 1byte.
+ * C375B is 0x37. */
+ CH375_GET_IC_VERSION = 0x01,
+ /** (Host mode) Set serial port baud rate. */
+ CH375_SET_BAUDRATE = 0x02,
+ /** Enter low power mode. Wakes up with USB or host activity. */
+ CH375_ENTER_SLEEP = 0x03,
+ /** Manualy force USB speed.
+ * Input: 1 byte:
+ * 0: Full speed
+ * 1: Full speed with low speed clock frequency (??)
+ * 2: Low speed */
+ CH375_SET_USB_SPEED = 0x04,
+ /** Hardware reset. May take up to 40ms. */
+ CH375_RESET = 0x05,
+ /** Simple eco function. Returns input byte negated.
+ * Input: 1 byte: x
+ * Output: 1 byte: ~x */
+ CH375_CHECK_EXIST = 0x06,
+
+ CH375_READ_REG = 0x0A,
+ CH375_WRITE_REG = 0x0B,
+ /** Sets a 100us timer.
+ * Output: infinite bytes. 0 while timer not expired, non-0 when expired. */
+ CH375_DELAY_100US = 0x0F,
+ /** (Device mode) set our USB vid and PID.
+ * Input: 4 bytes: VID LSB, VID MSB, PID LSB, PID MSB */
+ CH375_SET_USB_ID = 0x12,
+ /** (Device mode) set USB address. ?? */
+ CH375_SET_USB_ADDR = 0x13,
+ /** Set USB mode. Usually takes 20µs.
+ * Input: 1 byte:
+ * 0 : Device mode disabled (i.e. device plug-in detection)
+ * 1 : Device mode with external firmware
+ * 2 : Device mode with built-in firmware
+ * 4 : Host mode disabled (i.e. device plug-in detection)
+ * 5 : Host mode without automatic SOF packets.
+ * 6 : Host mode with automatic SOF packets.
+ * 7 : Host mode, initiate bus reset
+ * Output: 1 byte:
+ * CMD_RET_SUCCESS if OK
+ * CMD_RET_ABORT, etc. if error
+ */
+ CH375_SET_USB_MODE = 0x15,
+ /** (Host mode) Check the connection status.
+ * Output: 1 byte: either USB_INT_CONNECT or USB_INT_DISCONNECT or USB_INT_USB_READY. */
+ CH375_TEST_CONNECT = 0x16,
+ /** Abandon retrying and return NAK. */
+ CH375_ABORT_NAK = 0x17,
+
+ CH375_SET_ENDP2 = 0x18,
+ CH375_SET_ENDP3 = 0x19,
+ CH375_SET_ENDP4 = 0x1A,
+ CH375_SET_ENDP5 = 0x1B,
+ CH375_SET_ENDP6 = 0x1C,
+ CH375_SET_ENDP7 = 0x1D,
+
+ /** Get interrupt status and ack pending interrupts.
+ * Output: 1 byte */
+ CH375_GET_INT_STATUS = 0x22,
+ /** (Device mode) Release current USB buffer. */
+ CH375_UNLOCK_USB = 0x23,
+ /** Read buffer of endpoint of current interrupt.
+ * Output: length byte + data. */
+ CH375_RD_USB_DATA0 = 0x27,
+ /** Equivalent to RD_USB_DATA0 + UNLOCK_USB. */
+ CH375_RD_USB_DATA = 0x28,
+ CH375_WR_USB_DATA3 = 0x29,
+ CH375_WR_USB_DATA5 = 0x2A,
+ CH375_WR_USB_DATA7 = 0x2B,
+ /** (Host mode) Clear error state on endpoint.
+ * Input: 1 byte: endpoint number.
+ * On done: Triggers interrupt. */
+ CH375_CLR_STALL = 0x41,
+ /** (Host mode) Set USB address.
+ * Input: USB address.
+ * On done: Triggers interrupt. */
+ CH375_SET_ADDRESS = 0x45,
+ CH375_GET_DESCR = 0x46,
+ CH375_SET_CONFIG = 0x49,
+ CH375_AUTO_SETUP = 0x4D,
+ CH375_ISSUE_TKN_X = 0x4E,
+ CH375_ISSUE_TOKEN = 0x4F,
+
+ CH375_DISK_BOC = 0x50,
+ CH375_DISK_INIT = 0x51,
+ CH375_DISK_RESET = 0x52,
+ CH375_DISK_SIZE = 0x53,
+ CH375_DISK_READ = 0x54,
+ CH375_DISK_RD_GO = 0x55,
+ CH375_DISK_WRITE = 0x56,
+ CH375_DISK_WR_GO = 0x57,
+ CH375_DISK_INQUIRY = 0x58,
+ CH375_DISK_READY = 0x59,
+ CH375_DISK_R_SENSE = 0x5A,
+};
+
+enum ch375_cmd_ret {
+ CH375_RET_SUCESS = 0x51,
+ CH375_RET_ABORT = 0x5F
+};
+
+enum ch375_int {
+ /** if (x & mask) == val, this is a USB device mode interrupt. */
+ CH375_INT_DEVICE_MASK = 0xF0,
+ CH375_INT_DEVICE_VAL = 0x00,
+
+ CH375_INT_HOST_MASK = 0xE0,
+ CH375_INT_HOST_VAL = 0x20,
+
+ CH375_INT_BUS_RESET1 = 0x03,
+ CH375_INT_BUS_RESET2 = 0x07,
+ CH375_INT_BUS_RESET3 = 0x0B,
+ CH375_INT_BUS_RESET4 = 0x0F,
+
+ /** USB bus suspend event. */
+ CH375_INT_USB_SUSPEND = 0x05,
+ /** Wakeup from USB suspend. */
+ CH375_INT_USB_WAKE_UP = 0x06,
+ CH375_INT_USB_SUCCESS = 0x14,
+ CH375_INT_USB_CONNECT = 0x15,
+ CH375_INT_USB_DISCONNECT = 0x16,
+ CH375_INT_USB_BUF_OVER = 0x17,
+ CH375_INT_USB_READY = 0x18,
+ CH375_INT_USB_DISK_READ = 0x1D,
+ CH375_INT_USB_DISK_WRITE = 0x1E,
+ CH375_INT_USB_DISK_ERR = 0x1F,
+};
+
+static inline void ch375_iodelay(void)
+{
+ // Doesn't look to be required on pocket386
+ //outp(0x80, 0);
+}
+
+static inline uint8_t ch375_read_data(void)
+{
+ return inp(ch375_port + CH375_PORT_DATA);
+}
+
+static inline void ch375_write_data(uint8_t data)
+{
+ outp(ch375_port + CH375_PORT_DATA, data);
+ ch375_iodelay();
+}
+
+static inline void ch375_send_command(enum ch375_command cmd)
+{
+ outp(ch375_port + CH375_PORT_CMD, cmd);
+ ch375_iodelay();
+}
+
+/// True if interrupt line is currently active (i.e. low)
+static inline bool ch375_poll_interrupt(void)
+{
+ return !(inp(ch375_port + CH375_PORT_CMD) & 0x80);
+}
+
+enum CH375_REG {
+ /** (Host mode) Get data rate of current USB device.
+ * Bit 4: if 1= Low Speed. if 0 = Full Speed.*/
+ CH375_REG_DEV_RATE = 0x0A,
+ /** (Device mode): how to check USB bus suspend status.
+ * 0: Do not check
+ * 4: Check at 50ms intervals
+ * 5: Check at 10ms intervals */
+ CH375_REG_CHK_SUSPEND = 0x10,
+ CH375_REG_TOGGLE = 0x1A,
+
+ /** ???
+ * Bit 5 : set after disk is online? but never unset... */
+ CH375_REG_DISK_STATUS = 0x20,
+ /** (Host mode) Set the number of retries for USB transactions.
+ * If bit 7 is 0, no retry is performed when NAK is received.
+ * If bit 7 is 1 and bit 6 is 0, unlimited retries are performed when NAK is received.
+ * If bit 7 is 1 and bit 6 is 1, the maximum retry is 2 seconds when NAK is received.
+ * Bits 5-0 are the number of retries after timeout. */
+ CH375_REG_RETRY = 0x25,
+ /** (Host disk mode) Set the current LUN. */
+ CH375_REG_DISK_LUN = 0x34,
+ CH375_REG_DISK_MAX_LUN = 0x38,
+ /** (Host disk mode) Set the total number of 64-byte packets per sector.
+ * E.g. 8 = 512 byte sectors. */
+ CH375_REG_DISK_PKT_P_SEC = 0x39,
+ /// Drive letter (in ASCII) currently assigned. This is set by software.
+ CH375_REG_DISK_NAME = 0x3E
+
+ // 0x1B what's on this register ? 0x7F , 0x80 values seen
+ // 0x20 ?? some type of status ?
+ // 0x31, 0x32, 0x33 ??
+
+};
+
+static uint8_t ch375_read_reg(enum CH375_REG reg)
+{
+ ch375_send_command(CH375_READ_REG);
+ ch375_write_data(reg);
+ return ch375_read_data();
+}
+
+static void ch375_write_reg(enum CH375_REG reg, uint8_t value)
+{
+ ch375_send_command(CH375_WRITE_REG);
+ ch375_write_data(reg);
+ ch375_write_data(value);
+}
+
+/** Reads one transfer block (64 bytes max). */
+#if _M_IX86 >= 200
+// Watcom does not seem able to synthesize rep ins/outs. Do it in 286+ mode.
+static uint8_t ch375_read_data_block(uint8_t far *buffer);
+#pragma aux ch375_read_data_block = \
+ "mov dx, [ch375_port]" \
+ "in al, dx" \
+ "movzx cx, al" \
+ "rep ins byte ptr es:[di], dx" \
+ __parm [es di] \
+ __value [al] \
+ __modify exact [cx dx di]
+#else
+static uint8_t ch375_read_data_block(uint8_t far *buffer)
+{
+ uint8_t i, n = ch375_read_data();
+
+ for (i = 0; i < n; i++) {
+ buffer[i] = ch375_read_data();
+ }
+
+ return n;
+}
+#endif
+
+#if _M_IX86 >= 200
+static void ch375_write_data_block(uint8_t far * buffer, uint8_t n);
+#pragma aux ch375_write_data_block = \
+ "mov dx, [ch375_port]" \
+ "out dx, al" \
+ "movzx cx, al" \
+ "rep outs dx, byte ptr es:[si]" \
+ __parm [es si] [al] \
+ __modify exact [cx dx si]
+#else
+static void ch375_write_data_block(uint8_t far * buffer, uint8_t n)
+{
+ uint8_t i;
+
+ ch375_write_data(n);
+
+ for (i = 0; i < n; i++) {
+ ch375_write_data(buffer[i]);
+ }
+}
+#endif
+
+// GLOBAL COMMANDS
+
+static inline uint8_t ch375_get_version(void)
+{
+ ch375_send_command(CH375_GET_IC_VERSION);
+ return ch375_read_data() & 0x3F;
+}
+
+static inline void ch375_enter_sleep(void)
+{
+ ch375_send_command(CH375_ENTER_SLEEP);
+}
+
+static inline void ch375_reset(void)
+{
+ ch375_send_command(CH375_RESET);
+}
+
+static inline uint8_t ch375_check_exists(uint8_t val)
+{
+ ch375_send_command(CH375_CHECK_EXIST);
+ ch375_write_data(val);
+ return ch375_read_data();
+}
+
+static inline uint8_t ch375_get_int_status(void)
+{
+ ch375_send_command(CH375_GET_INT_STATUS);
+ return ch375_read_data();
+}
+
+enum ch375_usb_mode {
+ CH375_USB_MODE_DEVICE_DISABLED = 0,
+ CH375_USB_MODE_DEVICE_EXTERNAL_FIRMWARE = 1,
+ CH375_USB_MODE_DEVICE_INTERNAL_FIRMWARE = 2,
+ CH375_USB_MODE_HOST_DISABLED = 4,
+ CH375_USB_MODE_HOST = 5,
+ CH375_USB_MODE_HOST_AUTO_SOF = 6,
+ CH375_USB_MODE_HOST_RESET = 7
+};
+
+static void ch375_set_usb_mode(enum ch375_usb_mode mode) {
+ ch375_send_command(CH375_SET_USB_MODE);
+ ch375_write_data(mode);
+}
+
+enum ch375_usb_speed {
+ CH375_USB_SPEED_FULL = 0,
+ CH375_USB_SPEED_LOW = 2
+};
+
+// Seems to be for CH375B only. See below for CH375A alternative.
+static void ch375_set_usb_speed(enum ch375_usb_speed speed) {
+ ch375_send_command(CH375_SET_USB_SPEED);
+ ch375_write_data(speed);
+}
+
+static void ch375a_set_low_speed(void) {
+ // Undocumented.
+ ch375_write_reg(0x17, 0xd8);
+}
+
+// HOST COMMANDS
+
+static void ch375_test_connect(void) {
+ ch375_send_command(CH375_TEST_CONNECT);
+}
+
+static void ch375_auto_setup(void) {
+ ch375_send_command(CH375_AUTO_SETUP);
+}
+
+enum ch375_retry_mode {
+ CH375_RETRY_NAK_INFINITE = 0x80,
+ CH375_RETRY_NAK_2S = 0xC0,
+ CH375_RETRY_NAK_NONE = 0x00,
+
+ CH375_RETRY_TIMEOUT_MAX = 0x1F
+};
+
+static void ch375_set_retry(enum ch375_retry_mode mode) {
+ ch375_write_reg(CH375_REG_RETRY, mode);
+}
+
+static void ch375_get_descriptor(usb_descriptor_type type) {
+ ch375_send_command(CH375_GET_DESCR);
+ ch375_write_data(type);
+}
+
+// DISK COMMANDS
+
+enum ch375_reg_disk_status {
+ CH375_REG_DISK_STATUS_READY = 0x20
+};
+
+static inline bool ch375_is_disk_ready(void)
+{
+ return ch375_read_reg(CH375_REG_DISK_STATUS) & CH375_REG_DISK_STATUS_READY;
+}
+
+static void ch375_disk_init()
+{
+ ch375_send_command(CH375_DISK_INIT);
+}
+
+static void ch375_disk_read(uint32_t lba, uint8_t nsectors)
+{
+ ch375_send_command(CH375_DISK_READ);
+ ch375_write_data((lba >> 0) & 0xFF);
+ ch375_write_data((lba >> 8) & 0xFF);
+ ch375_write_data((lba >> 16) & 0xFF);
+ ch375_write_data((lba >> 24) & 0xFF);
+ ch375_write_data(nsectors);
+}
+
+static void ch375_disk_write(uint32_t lba, uint8_t nsectors)
+{
+ ch375_send_command(CH375_DISK_WRITE);
+ ch375_write_data((lba >> 0) & 0xFF);
+ ch375_write_data((lba >> 8) & 0xFF);
+ ch375_write_data((lba >> 16) & 0xFF);
+ ch375_write_data((lba >> 24) & 0xFF);
+ ch375_write_data(nsectors);
+}
+
+static void ch375_test_disk_ready(void)
+{
+ ch375_send_command(CH375_DISK_READY);
+}
+
+#endif