Message ID | 20180627232951.14725-9-laurent@vivier.eu |
---|---|
State | New |
Headers | show |
Series | hw/m68k: add Apple Machintosh Quadra 800 machine | expand |
Le 28/06/2018 à 01:29, Laurent Vivier a écrit : > Co-developed-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk> > Signed-off-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk> > Signed-off-by: Laurent Vivier <laurent@vivier.eu> > --- > hw/block/Makefile.objs | 1 + > hw/block/swim.c | 284 ++++++++++++++++++++++++++++++++++++++++++++++++ > include/hw/block/swim.h | 53 +++++++++ > 3 files changed, 338 insertions(+) > create mode 100644 hw/block/swim.c > create mode 100644 include/hw/block/swim.h > > diff --git a/hw/block/Makefile.objs b/hw/block/Makefile.objs > index 53ce5751ae..068de3f0c9 100644 > --- a/hw/block/Makefile.objs > +++ b/hw/block/Makefile.objs > @@ -8,6 +8,7 @@ common-obj-$(CONFIG_XEN) += xen_disk.o > common-obj-$(CONFIG_ECC) += ecc.o > common-obj-$(CONFIG_ONENAND) += onenand.o > common-obj-$(CONFIG_NVME_PCI) += nvme.o > +common-obj-$(CONFIG_SWIM) += swim.o > > obj-$(CONFIG_SH4) += tc58128.o > > diff --git a/hw/block/swim.c b/hw/block/swim.c > new file mode 100644 > index 0000000000..6b1d0371e8 > --- /dev/null > +++ b/hw/block/swim.c > @@ -0,0 +1,284 @@ > +/* > + * QEMU Macintosh floppy disk controller emulator (SWIM) > + * > + * Copyright (c) 2014-2018 Laurent Vivier <laurent@vivier.eu> > + * > + * This work is licensed under the terms of the GNU GPL, version 2. See > + * the COPYING file in the top-level directory. > + * > + */ > + > +#include "qemu/osdep.h" > +#include "hw/sysbus.h" > +#include "hw/block/swim.h" > + > +/* IWM registers */ > + > +#define IWM_PH0L 0 > +#define IWM_PH0H 1 > +#define IWM_PH1L 2 > +#define IWM_PH1H 3 > +#define IWM_PH2L 4 > +#define IWM_PH2H 5 > +#define IWM_PH3L 6 > +#define IWM_PH3H 7 > +#define IWM_MTROFF 8 > +#define IWM_MTRON 9 > +#define IWM_INTDRIVE 10 > +#define IWM_EXTDRIVE 11 > +#define IWM_Q6L 12 > +#define IWM_Q6H 13 > +#define IWM_Q7L 14 > +#define IWM_Q7H 15 > + > +/* SWIM registers */ > + > +#define SWIM_WRITE_DATA 0 > +#define SWIM_WRITE_MARK 1 > +#define SWIM_WRITE_CRC 2 > +#define SWIM_WRITE_PARAMETER 3 > +#define SWIM_WRITE_PHASE 4 > +#define SWIM_WRITE_SETUP 5 > +#define SWIM_WRITE_MODE0 6 > +#define SWIM_WRITE_MODE1 7 > + > +#define SWIM_READ_DATA 8 > +#define SWIM_READ_MARK 9 > +#define SWIM_READ_ERROR 10 > +#define SWIM_READ_PARAMETER 11 > +#define SWIM_READ_PHASE 12 > +#define SWIM_READ_SETUP 13 > +#define SWIM_READ_STATUS 14 > +#define SWIM_READ_HANDSHAKE 15 > + > +#define REG_SHIFT 9 > + > +#define SWIM_MODE_IWM 0 > +#define SWIM_MODE_SWIM 1 > + > +/* bits in phase register */ > + > +#define SWIM_SEEK_NEGATIVE 0x074 > +#define SWIM_STEP 0x071 > +#define SWIM_MOTOR_ON 0x072 > +#define SWIM_MOTOR_OFF 0x076 > +#define SWIM_INDEX 0x073 > +#define SWIM_EJECT 0x077 > +#define SWIM_SETMFM 0x171 > +#define SWIM_SETGCR 0x175 > +#define SWIM_RELAX 0x033 > +#define SWIM_LSTRB 0x008 > +#define SWIM_CA_MASK 0x077 > + > +/* Select values for swim_select and swim_readbit */ > + > +#define SWIM_READ_DATA_0 0x074 > +#define SWIM_TWOMEG_DRIVE 0x075 > +#define SWIM_SINGLE_SIDED 0x076 > +#define SWIM_DRIVE_PRESENT 0x077 > +#define SWIM_DISK_IN 0x170 > +#define SWIM_WRITE_PROT 0x171 > +#define SWIM_TRACK_ZERO 0x172 > +#define SWIM_TACHO 0x173 > +#define SWIM_READ_DATA_1 0x174 > +#define SWIM_MFM_MODE 0x175 > +#define SWIM_SEEK_COMPLETE 0x176 > +#define SWIM_ONEMEG_MEDIA 0x177 > + > +/* Bits in handshake register */ > + > +#define SWIM_MARK_BYTE 0x01 > +#define SWIM_CRC_ZERO 0x02 > +#define SWIM_RDDATA 0x04 > +#define SWIM_SENSE 0x08 > +#define SWIM_MOTEN 0x10 > +#define SWIM_ERROR 0x20 > +#define SWIM_DAT2BYTE 0x40 > +#define SWIM_DAT1BYTE 0x80 > + > +/* bits in setup register */ > + > +#define SWIM_S_INV_WDATA 0x01 > +#define SWIM_S_3_5_SELECT 0x02 > +#define SWIM_S_GCR 0x04 > +#define SWIM_S_FCLK_DIV2 0x08 > +#define SWIM_S_ERROR_CORR 0x10 > +#define SWIM_S_IBM_DRIVE 0x20 > +#define SWIM_S_GCR_WRITE 0x40 > +#define SWIM_S_TIMEOUT 0x80 > + > +/* bits in mode register */ > + > +#define SWIM_CLFIFO 0x01 > +#define SWIM_ENBL1 0x02 > +#define SWIM_ENBL2 0x04 > +#define SWIM_ACTION 0x08 > +#define SWIM_WRITE_MODE 0x10 > +#define SWIM_HEDSEL 0x20 > +#define SWIM_MOTON 0x80 > + > + > +static void iwmctrl_write(void *opaque, hwaddr reg, uint64_t value, > + unsigned size) > +{ > + SWIMCtrl *swimctrl = opaque; > + > + reg >>= REG_SHIFT; > + > + swimctrl->regs[reg >> 1] = reg & 1; > + > + if (swimctrl->regs[IWM_Q6] && > + swimctrl->regs[IWM_Q7]) { > + if (swimctrl->regs[IWM_MTR]) { > + /* data register */ > + swimctrl->iwm_data = value; > + } else { > + /* mode register */ > + swimctrl->iwm_mode = value; > + /* detect sequence to switch from IWM mode to SWIM mode */ > + switch (swimctrl->iwm_switch) { > + case 0: > + if (value == 0x57) { > + swimctrl->iwm_switch++; > + } > + break; > + case 1: > + if (value == 0x17) { > + swimctrl->iwm_switch++; > + } > + break; > + case 2: > + if (value == 0x57) { > + swimctrl->iwm_switch++; > + } > + break; > + case 3: > + if (value == 0x57) { > + swimctrl->mode = SWIM_MODE_SWIM; > + swimctrl->iwm_switch = 0; > + } > + break; > + } I assume that the values of the sequence must be contiguous. If you don't follow it, you have to restart from scratch. In that case, you're missing the "else { swimctrl->iwm_switch = 0; }" in the cases 1 to 3. You may also use the following pattern: static const uint8_t iwm_switch_sequence[] = { 0x57, 0x17, 0x57, 0x57 }; if (value == iwm_switch_sequence[swimctrl->iwm_switch]) { swimctrl->iwm_switch++; if (swimctrl->iwm_switch == ARRAY_SIZE(iwm_switch_sequence)) { swimctrl->mode = SWIM_MODE_SWIM; swimctrl->iwm_switch = 0; } } else { swimctrl->iwm_switch = 0; } Hervé > + } > + } > +} > + > +static uint64_t iwmctrl_read(void *opaque, hwaddr reg, unsigned size) > +{ > + SWIMCtrl *swimctrl = opaque; > + > + reg >>= REG_SHIFT; > + > + swimctrl->regs[reg >> 1] = reg & 1; > + > + return 0; > +} > + > +static void swimctrl_write(void *opaque, hwaddr reg, uint64_t value, > + unsigned size) > +{ > + SWIMCtrl *swimctrl = opaque; > + > + if (swimctrl->mode == SWIM_MODE_IWM) { > + iwmctrl_write(opaque, reg, value, size); > + return; > + } > + > + reg >>= REG_SHIFT; > + > + switch (reg) { > + case SWIM_WRITE_PHASE: > + swimctrl->swim_phase = value; > + break; > + case SWIM_WRITE_MODE0: > + swimctrl->swim_mode &= ~value; > + break; > + case SWIM_WRITE_MODE1: > + swimctrl->swim_mode |= value; > + break; > + case SWIM_WRITE_DATA: > + case SWIM_WRITE_MARK: > + case SWIM_WRITE_CRC: > + case SWIM_WRITE_PARAMETER: > + case SWIM_WRITE_SETUP: > + break; > + } > +} > + > +static uint64_t swimctrl_read(void *opaque, hwaddr reg, unsigned size) > +{ > + SWIMCtrl *swimctrl = opaque; > + uint32_t value = 0; > + > + if (swimctrl->mode == SWIM_MODE_IWM) { > + return iwmctrl_read(opaque, reg, size); > + } > + > + reg >>= REG_SHIFT; > + > + switch (reg) { > + case SWIM_READ_PHASE: > + value = swimctrl->swim_phase; > + break; > + case SWIM_READ_HANDSHAKE: > + if (swimctrl->swim_phase == SWIM_DRIVE_PRESENT) { > + /* always answer "no drive present" */ > + value = SWIM_SENSE; > + } > + break; > + case SWIM_READ_DATA: > + case SWIM_READ_MARK: > + case SWIM_READ_ERROR: > + case SWIM_READ_PARAMETER: > + case SWIM_READ_SETUP: > + case SWIM_READ_STATUS: > + break; > + } > + > + return value; > +} > + > +static const MemoryRegionOps swimctrl_mem_ops = { > + .write = swimctrl_write, > + .read = swimctrl_read, > + .endianness = DEVICE_NATIVE_ENDIAN, > +}; > + > +static void swim_init(Object *obj) > +{ > + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); > + SWIMCtrl *swimctrl = SWIM(obj); > + > + memory_region_init_io(&swimctrl->iomem, obj, &swimctrl_mem_ops, swimctrl, > + "swim", 0x2000); > + sysbus_init_mmio(sbd, &swimctrl->iomem); > +} > + > +static Property swim_properties[] = { > + DEFINE_PROP_DRIVE("driveA", SWIMCtrl, drives[0].blk), > + DEFINE_PROP_DRIVE("driveB", SWIMCtrl, drives[1].blk), > + DEFINE_PROP_END_OF_LIST(), > +}; > + > +static void swim_class_init(ObjectClass *oc, void *data) > +{ > + DeviceClass *dc = DEVICE_CLASS(oc); > + > + dc->props = swim_properties; > + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); > +} > + > +static const TypeInfo swim_info = { > + .name = TYPE_SWIM, > + .parent = TYPE_SYS_BUS_DEVICE, > + .instance_size = sizeof(SWIMCtrl), > + .instance_init = swim_init, > + .class_init = swim_class_init, > +}; > + > +static void swim_register_types(void) > +{ > + type_register_static(&swim_info); > +} > + > +type_init(swim_register_types) > diff --git a/include/hw/block/swim.h b/include/hw/block/swim.h > new file mode 100644 > index 0000000000..2c0258967c > --- /dev/null > +++ b/include/hw/block/swim.h > @@ -0,0 +1,53 @@ > +/* > + * QEMU Macintosh floppy disk controller emulator (SWIM) > + * > + * Copyright (c) 2014-2018 Laurent Vivier <laurent@vivier.eu> > + * > + * This work is licensed under the terms of the GNU GPL, version 2. See > + * the COPYING file in the top-level directory. > + * > + */ > + > +#ifndef SWIM_H > +#define SWIM_H > + > +#include "qemu/osdep.h" > +#include "hw/sysbus.h" > + > +#define MAX_FD 2 > + > +typedef struct SWIMCtrl SWIMCtrl; > + > +typedef struct FDrive { > + SWIMCtrl *swimctrl; > + BlockBackend *blk; > +} FDrive; > + > +#define TYPE_SWIM "swim" > +#define SWIM(obj) OBJECT_CHECK(SWIMCtrl, (obj), TYPE_SWIM) > + > +typedef struct SWIMCtrl { > + SysBusDevice parent_obj; > + > + MemoryRegion iomem; > + FDrive drives[MAX_FD]; > + int mode; > + /* IWM mode */ > + int iwm_switch; > + int regs[8]; > +#define IWM_PH0 0 > +#define IWM_PH1 1 > +#define IWM_PH2 2 > +#define IWM_PH3 3 > +#define IWM_MTR 4 > +#define IWM_DRIVE 5 > +#define IWM_Q6 6 > +#define IWM_Q7 7 > + uint8_t iwm_data; > + uint8_t iwm_mode; > + /* SWIM mode */ > + uint8_t swim_phase; > + uint8_t swim_mode; > +} SWIMCtrl; > + > +#endif >
diff --git a/hw/block/Makefile.objs b/hw/block/Makefile.objs index 53ce5751ae..068de3f0c9 100644 --- a/hw/block/Makefile.objs +++ b/hw/block/Makefile.objs @@ -8,6 +8,7 @@ common-obj-$(CONFIG_XEN) += xen_disk.o common-obj-$(CONFIG_ECC) += ecc.o common-obj-$(CONFIG_ONENAND) += onenand.o common-obj-$(CONFIG_NVME_PCI) += nvme.o +common-obj-$(CONFIG_SWIM) += swim.o obj-$(CONFIG_SH4) += tc58128.o diff --git a/hw/block/swim.c b/hw/block/swim.c new file mode 100644 index 0000000000..6b1d0371e8 --- /dev/null +++ b/hw/block/swim.c @@ -0,0 +1,284 @@ +/* + * QEMU Macintosh floppy disk controller emulator (SWIM) + * + * Copyright (c) 2014-2018 Laurent Vivier <laurent@vivier.eu> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include "hw/sysbus.h" +#include "hw/block/swim.h" + +/* IWM registers */ + +#define IWM_PH0L 0 +#define IWM_PH0H 1 +#define IWM_PH1L 2 +#define IWM_PH1H 3 +#define IWM_PH2L 4 +#define IWM_PH2H 5 +#define IWM_PH3L 6 +#define IWM_PH3H 7 +#define IWM_MTROFF 8 +#define IWM_MTRON 9 +#define IWM_INTDRIVE 10 +#define IWM_EXTDRIVE 11 +#define IWM_Q6L 12 +#define IWM_Q6H 13 +#define IWM_Q7L 14 +#define IWM_Q7H 15 + +/* SWIM registers */ + +#define SWIM_WRITE_DATA 0 +#define SWIM_WRITE_MARK 1 +#define SWIM_WRITE_CRC 2 +#define SWIM_WRITE_PARAMETER 3 +#define SWIM_WRITE_PHASE 4 +#define SWIM_WRITE_SETUP 5 +#define SWIM_WRITE_MODE0 6 +#define SWIM_WRITE_MODE1 7 + +#define SWIM_READ_DATA 8 +#define SWIM_READ_MARK 9 +#define SWIM_READ_ERROR 10 +#define SWIM_READ_PARAMETER 11 +#define SWIM_READ_PHASE 12 +#define SWIM_READ_SETUP 13 +#define SWIM_READ_STATUS 14 +#define SWIM_READ_HANDSHAKE 15 + +#define REG_SHIFT 9 + +#define SWIM_MODE_IWM 0 +#define SWIM_MODE_SWIM 1 + +/* bits in phase register */ + +#define SWIM_SEEK_NEGATIVE 0x074 +#define SWIM_STEP 0x071 +#define SWIM_MOTOR_ON 0x072 +#define SWIM_MOTOR_OFF 0x076 +#define SWIM_INDEX 0x073 +#define SWIM_EJECT 0x077 +#define SWIM_SETMFM 0x171 +#define SWIM_SETGCR 0x175 +#define SWIM_RELAX 0x033 +#define SWIM_LSTRB 0x008 +#define SWIM_CA_MASK 0x077 + +/* Select values for swim_select and swim_readbit */ + +#define SWIM_READ_DATA_0 0x074 +#define SWIM_TWOMEG_DRIVE 0x075 +#define SWIM_SINGLE_SIDED 0x076 +#define SWIM_DRIVE_PRESENT 0x077 +#define SWIM_DISK_IN 0x170 +#define SWIM_WRITE_PROT 0x171 +#define SWIM_TRACK_ZERO 0x172 +#define SWIM_TACHO 0x173 +#define SWIM_READ_DATA_1 0x174 +#define SWIM_MFM_MODE 0x175 +#define SWIM_SEEK_COMPLETE 0x176 +#define SWIM_ONEMEG_MEDIA 0x177 + +/* Bits in handshake register */ + +#define SWIM_MARK_BYTE 0x01 +#define SWIM_CRC_ZERO 0x02 +#define SWIM_RDDATA 0x04 +#define SWIM_SENSE 0x08 +#define SWIM_MOTEN 0x10 +#define SWIM_ERROR 0x20 +#define SWIM_DAT2BYTE 0x40 +#define SWIM_DAT1BYTE 0x80 + +/* bits in setup register */ + +#define SWIM_S_INV_WDATA 0x01 +#define SWIM_S_3_5_SELECT 0x02 +#define SWIM_S_GCR 0x04 +#define SWIM_S_FCLK_DIV2 0x08 +#define SWIM_S_ERROR_CORR 0x10 +#define SWIM_S_IBM_DRIVE 0x20 +#define SWIM_S_GCR_WRITE 0x40 +#define SWIM_S_TIMEOUT 0x80 + +/* bits in mode register */ + +#define SWIM_CLFIFO 0x01 +#define SWIM_ENBL1 0x02 +#define SWIM_ENBL2 0x04 +#define SWIM_ACTION 0x08 +#define SWIM_WRITE_MODE 0x10 +#define SWIM_HEDSEL 0x20 +#define SWIM_MOTON 0x80 + + +static void iwmctrl_write(void *opaque, hwaddr reg, uint64_t value, + unsigned size) +{ + SWIMCtrl *swimctrl = opaque; + + reg >>= REG_SHIFT; + + swimctrl->regs[reg >> 1] = reg & 1; + + if (swimctrl->regs[IWM_Q6] && + swimctrl->regs[IWM_Q7]) { + if (swimctrl->regs[IWM_MTR]) { + /* data register */ + swimctrl->iwm_data = value; + } else { + /* mode register */ + swimctrl->iwm_mode = value; + /* detect sequence to switch from IWM mode to SWIM mode */ + switch (swimctrl->iwm_switch) { + case 0: + if (value == 0x57) { + swimctrl->iwm_switch++; + } + break; + case 1: + if (value == 0x17) { + swimctrl->iwm_switch++; + } + break; + case 2: + if (value == 0x57) { + swimctrl->iwm_switch++; + } + break; + case 3: + if (value == 0x57) { + swimctrl->mode = SWIM_MODE_SWIM; + swimctrl->iwm_switch = 0; + } + break; + } + } + } +} + +static uint64_t iwmctrl_read(void *opaque, hwaddr reg, unsigned size) +{ + SWIMCtrl *swimctrl = opaque; + + reg >>= REG_SHIFT; + + swimctrl->regs[reg >> 1] = reg & 1; + + return 0; +} + +static void swimctrl_write(void *opaque, hwaddr reg, uint64_t value, + unsigned size) +{ + SWIMCtrl *swimctrl = opaque; + + if (swimctrl->mode == SWIM_MODE_IWM) { + iwmctrl_write(opaque, reg, value, size); + return; + } + + reg >>= REG_SHIFT; + + switch (reg) { + case SWIM_WRITE_PHASE: + swimctrl->swim_phase = value; + break; + case SWIM_WRITE_MODE0: + swimctrl->swim_mode &= ~value; + break; + case SWIM_WRITE_MODE1: + swimctrl->swim_mode |= value; + break; + case SWIM_WRITE_DATA: + case SWIM_WRITE_MARK: + case SWIM_WRITE_CRC: + case SWIM_WRITE_PARAMETER: + case SWIM_WRITE_SETUP: + break; + } +} + +static uint64_t swimctrl_read(void *opaque, hwaddr reg, unsigned size) +{ + SWIMCtrl *swimctrl = opaque; + uint32_t value = 0; + + if (swimctrl->mode == SWIM_MODE_IWM) { + return iwmctrl_read(opaque, reg, size); + } + + reg >>= REG_SHIFT; + + switch (reg) { + case SWIM_READ_PHASE: + value = swimctrl->swim_phase; + break; + case SWIM_READ_HANDSHAKE: + if (swimctrl->swim_phase == SWIM_DRIVE_PRESENT) { + /* always answer "no drive present" */ + value = SWIM_SENSE; + } + break; + case SWIM_READ_DATA: + case SWIM_READ_MARK: + case SWIM_READ_ERROR: + case SWIM_READ_PARAMETER: + case SWIM_READ_SETUP: + case SWIM_READ_STATUS: + break; + } + + return value; +} + +static const MemoryRegionOps swimctrl_mem_ops = { + .write = swimctrl_write, + .read = swimctrl_read, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void swim_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + SWIMCtrl *swimctrl = SWIM(obj); + + memory_region_init_io(&swimctrl->iomem, obj, &swimctrl_mem_ops, swimctrl, + "swim", 0x2000); + sysbus_init_mmio(sbd, &swimctrl->iomem); +} + +static Property swim_properties[] = { + DEFINE_PROP_DRIVE("driveA", SWIMCtrl, drives[0].blk), + DEFINE_PROP_DRIVE("driveB", SWIMCtrl, drives[1].blk), + DEFINE_PROP_END_OF_LIST(), +}; + +static void swim_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->props = swim_properties; + set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); +} + +static const TypeInfo swim_info = { + .name = TYPE_SWIM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SWIMCtrl), + .instance_init = swim_init, + .class_init = swim_class_init, +}; + +static void swim_register_types(void) +{ + type_register_static(&swim_info); +} + +type_init(swim_register_types) diff --git a/include/hw/block/swim.h b/include/hw/block/swim.h new file mode 100644 index 0000000000..2c0258967c --- /dev/null +++ b/include/hw/block/swim.h @@ -0,0 +1,53 @@ +/* + * QEMU Macintosh floppy disk controller emulator (SWIM) + * + * Copyright (c) 2014-2018 Laurent Vivier <laurent@vivier.eu> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#ifndef SWIM_H +#define SWIM_H + +#include "qemu/osdep.h" +#include "hw/sysbus.h" + +#define MAX_FD 2 + +typedef struct SWIMCtrl SWIMCtrl; + +typedef struct FDrive { + SWIMCtrl *swimctrl; + BlockBackend *blk; +} FDrive; + +#define TYPE_SWIM "swim" +#define SWIM(obj) OBJECT_CHECK(SWIMCtrl, (obj), TYPE_SWIM) + +typedef struct SWIMCtrl { + SysBusDevice parent_obj; + + MemoryRegion iomem; + FDrive drives[MAX_FD]; + int mode; + /* IWM mode */ + int iwm_switch; + int regs[8]; +#define IWM_PH0 0 +#define IWM_PH1 1 +#define IWM_PH2 2 +#define IWM_PH3 3 +#define IWM_MTR 4 +#define IWM_DRIVE 5 +#define IWM_Q6 6 +#define IWM_Q7 7 + uint8_t iwm_data; + uint8_t iwm_mode; + /* SWIM mode */ + uint8_t swim_phase; + uint8_t swim_mode; +} SWIMCtrl; + +#endif