comodore-iec-emu/kernel/iec_lines.h
2026-06-18 22:25:42 +02:00

107 lines
4.1 KiB
C

/* SPDX-License-Identifier: GPL-2.0 */
/*
* iec_lines.h - logical IEC line layer (PLAN.md §5.1).
*
* The handshake code reasons in protocol true/false terms (asserted / released)
* and never juggles electrical inversions inline. With the PLAN's non-inverting
* bidirectional level shifter (sd2iec-style single DATA pin, §3.1):
*
* bus asserted (0 V) <=> Pi GPIO reads LOW
* bus released (5 V) <=> Pi GPIO reads HIGH
*
* DATA is open-drain emulated on a single bidirectional pin:
* assert : drive GPIO18 OUTPUT LOW -> shifter pulls bus to 0 V
* release : set GPIO18 INPUT (Hi-Z) -> bus pull-ups float it to 5 V;
* the talker's bits can now be read back through the shifter.
*
* The hot path uses direct BCM register access (ioremap'd), which the kernel
* research found to be ~30-40x faster than the gpiod descriptor API and is what
* ninepin/raspbiec use inside the bit loop.
*
* NOTE: this differs from the 7406 *inverting* scheme analysed in
* _research/pi-kernel-module-rt-gpio-2026-06-18.md §4.3. The PLAN deliberately
* chose the non-inverting bidirectional shifter, which also closes that doc's
* "DATA sensing gap" (reading back through the shifter while the pin is an input
* IS valid). See docs/kernel-notes.md.
*/
#ifndef _IEC_LINES_H
#define _IEC_LINES_H
#include <linux/io.h>
/* --- GPIO assignment (BCM numbering, PLAN.md §3.2) ----------------------- */
#define IEC_GPIO_ATN 2 /* header pin 3 - input */
#define IEC_GPIO_RESET 3 /* header pin 5 - input */
#define IEC_GPIO_CLK 17 /* header pin 11 - input */
#define IEC_GPIO_DATA 18 /* header pin 12 - bidirectional (open-drain) */
/* --- BCM2710A1 / BCM2837 (Pi Zero 2 W) peripheral map ------------------- */
/* Same peripheral base as RPi 3. RPi 1 = 0x20000000, RPi 4 = 0xFE000000. */
#define IEC_BCM_PERI_BASE 0x3F000000UL
#define IEC_GPIO_BASE (IEC_BCM_PERI_BASE + 0x200000UL)
#define IEC_GPIO_LEN 0x1000
/* Register word offsets (multiply by 4 for byte offset) */
#define GPFSEL0 0 /* function select base (3 bits/pin, 10 pins/reg) */
#define GPSET0 7 /* output set (write 1 to set pin high) */
#define GPCLR0 10 /* output clear (write 1 to set pin low) */
#define GPLEV0 13 /* pin level (read) */
/* ioremap'd base of the GPIO block; set in module init. */
extern u32 __iomem *iec_gpio;
/* --- low-level direct register helpers ---------------------------------- */
static inline int iec_gpio_read(unsigned int pin)
{
return (ioread32(iec_gpio + GPLEV0) >> (pin & 31)) & 1;
}
static inline void iec_gpio_set(unsigned int pin) /* drive high */
{
iowrite32(1u << (pin & 31), iec_gpio + GPSET0);
}
static inline void iec_gpio_clr(unsigned int pin) /* drive low */
{
iowrite32(1u << (pin & 31), iec_gpio + GPCLR0);
}
/* Set the 3-bit function field for a pin: 0 = input, 1 = output. */
static inline void iec_gpio_fsel(unsigned int pin, unsigned int fn)
{
u32 __iomem *reg = iec_gpio + GPFSEL0 + (pin / 10);
unsigned int shift = (pin % 10) * 3;
u32 v = ioread32(reg);
v &= ~(0x7u << shift);
v |= (fn & 0x7u) << shift;
iowrite32(v, reg);
}
#define IEC_FSEL_INPUT 0
#define IEC_FSEL_OUTPUT 1
/* --- logical (protocol-level) line operations --------------------------- */
/* sense lines: asserted == Pi reads LOW (non-inverting shifter) */
static inline bool iec_atn_asserted(void) { return iec_gpio_read(IEC_GPIO_ATN) == 0; }
static inline bool iec_clk_asserted(void) { return iec_gpio_read(IEC_GPIO_CLK) == 0; }
static inline bool iec_reset_asserted(void) { return iec_gpio_read(IEC_GPIO_RESET) == 0; }
/* DATA, open-drain emulation on the single bidirectional pin */
static inline void iec_data_assert(void) /* pull bus low */
{
iec_gpio_clr(IEC_GPIO_DATA); /* preload output latch LOW */
iec_gpio_fsel(IEC_GPIO_DATA, IEC_FSEL_OUTPUT);
}
static inline void iec_data_release(void) /* float bus high (Hi-Z) */
{
iec_gpio_fsel(IEC_GPIO_DATA, IEC_FSEL_INPUT);
}
/* read the DATA bus state (valid only after iec_data_release()) */
static inline bool iec_data_asserted(void) { return iec_gpio_read(IEC_GPIO_DATA) == 0; }
#endif /* _IEC_LINES_H */