107 lines
4.1 KiB
C
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 */
|