/* * 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 . */ #ifndef CH375_H #define CH375_H #include #include #include #include #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