diff --git a/pkgs/by-name/eg25-control/eg25-control b/pkgs/by-name/eg25-control/eg25-control index f055cca78..38c0a3c61 100755 --- a/pkgs/by-name/eg25-control/eg25-control +++ b/pkgs/by-name/eg25-control/eg25-control @@ -325,58 +325,81 @@ class GpioPhy: # GPIO indices ("lines") # DTR = "Data Terminal Ready", controls sleep/wakeup. high means modem is allowed to sleep when requested. high -> low forces the modem awake. internal pull-up. # WAKEUP = 6 - DTR = 34 # PB2 - PWRKEY = 35 # PB3 - RESET = 68 # PC4 - APREADY = 231 # PH7, a.k.a. "host ready" - DISABLE = 232 # PH8, a.k.a. "enable"; active-low + # these are supplied by the subclass: + DTR = None, None + PWRKEY = None, None + RESET = None, None + APREADY = None, None + DISABLE = None, None # STATUS line: HIGH means powered off - STATUS = 233 # PH9 + STATUS = None, None def __init__(self): - self.lines = gpiod.request_lines( - "/dev/gpiochip1", - consumer="eg25-control", - config={ - GpioPhy.DTR: gpiod.LineSettings(direction=gpiod.line.Direction.OUTPUT), - GpioPhy.PWRKEY: gpiod.LineSettings(direction=gpiod.line.Direction.OUTPUT), - GpioPhy.RESET: gpiod.LineSettings(direction=gpiod.line.Direction.OUTPUT), - GpioPhy.APREADY: gpiod.LineSettings(direction=gpiod.line.Direction.OUTPUT), - GpioPhy.DISABLE: gpiod.LineSettings(direction=gpiod.line.Direction.OUTPUT), - GpioPhy.STATUS: gpiod.LineSettings(direction=gpiod.line.Direction.INPUT, bias=gpiod.line.Bias.PULL_UP), - }, + gpios = dict( + DTR=self.DTR, + PWRKEY=self.PWRKEY, + RESET=self.RESET, + APREADY=self.APREADY, + DISABLE=self.DISABLE, + STATUS=self.STATUS, ) + num_lines = max(chip for (chip, _) in gpios.values()) + line_configs = [dict() for _ in range(num_lines)] + + line_configs[self.DTR[0]][self.DTR[1]] = gpiod.LineSettings(direction=gpiod.line.Direction.OUTPUT) + line_configs[self.PWRKEY[0]][self.PWRKEY[1]] = gpiod.LineSettings(direction=gpiod.line.Direction.OUTPUT) + line_configs[self.RESET[0]][self.RESET[1]] = gpiod.LineSettings(direction=gpiod.line.Direction.OUTPUT) + line_configs[self.APREADY[0]][self.APREADY[1]] = gpiod.LineSettings(direction=gpiod.line.Direction.OUTPUT) + line_configs[self.DISABLE[0]][self.DISABLE[1]] = gpiod.LineSettings(direction=gpiod.line.Direction.OUTPUT) + line_configs[self.STATUS[0]][self.STATUS[1]] = gpiod.LineSettings(direction=gpiod.line.Direction.INPUT, bias=gpiod.line.Bias.PULL_UP) + + self.lines = [None] * num_lines + for line, config in enumerate(line_configs): + if config: + self.lines[line] = gpiod.request_line( + f"/dev/gpiochip{line}", + consumer="eg25-control", + config=config, + ) + + def set_gpio(self, gpio, value) -> None: + line, index = gpio + self.lines[line].set_value(index, value) + + def get_gpio(self, gpio): + line, index = gpio + return self.lines[line].get_value(index) def power_toggle(self, disable = gpiod.line.Value.INACTIVE) -> None: # power-on is signalled by toggling the PWRKEY, and the modem interprets that as either a power-up OR a power-down request. self.dump_debug_info() - if self.lines.get_value(self.STATUS) == disable: + if self.get_gpio(self.STATUS) == disable: # i wouldn't be surprised if the modem can get "stuck" in some state such that this early return always hits, # if that's the case add some `--force` flag or verify against `mmcli -m any` report, etc. logger.info("modem appears physically to already be in the desired state: not changing") return - self.lines.set_value(self.APREADY, gpiod.line.Value.ACTIVE) - self.lines.set_value(self.DISABLE, gpiod.line.Value.INACTIVE) - self.lines.set_value(self.RESET, gpiod.line.Value.INACTIVE) - self.lines.set_value(self.PWRKEY, gpiod.line.Value.INACTIVE) - self.lines.set_value(self.DTR, gpiod.line.Value.INACTIVE) + self.set_gpio(self.APREADY, gpiod.line.Value.ACTIVE) + self.set_gpio(self.DISABLE, gpiod.line.Value.INACTIVE) + self.set_gpio(self.RESET, gpiod.line.Value.INACTIVE) + self.set_gpio(self.PWRKEY, gpiod.line.Value.INACTIVE) + self.set_gpio(self.DTR, gpiod.line.Value.INACTIVE) # Megi's modem-power sleeps 50ms, cites datasheet claim to 30ms power-on time.sleep(0.050) self.dump_debug_info() - self.lines.set_value(self.PWRKEY, gpiod.line.Value.ACTIVE) + self.set_gpio(self.PWRKEY, gpiod.line.Value.ACTIVE) # Megi's modem-power sleeps 200ms; eg25-manager sleeps 1.0s; EG25-HW 3.7 says 500ms+ for power-on, 650ms+ for power-off time.sleep(1.0) - self.lines.set_value(self.PWRKEY, gpiod.line.Value.INACTIVE) + self.set_gpio(self.PWRKEY, gpiod.line.Value.INACTIVE) # TODO: switch 'status' key to input (megi's modem-power claims it can be multiplexed with other stuff, so shouldn't be actively driven when possible) # power-up takes 3-5s; power-down takes 12-15s for i in range(20): self.dump_debug_info() - if self.lines.get_value(self.STATUS) == disable: + if self.get_gpio(self.STATUS) == disable: break else: logger.info("modem hasn't pulled STATUS: sleeping for 1s") @@ -391,18 +414,46 @@ class GpioPhy: self.power_toggle(disable=gpiod.line.Value.ACTIVE) def dump_debug_info(self) -> None: - vals = self.lines.get_values() - dtr, pwrkey, reset, apready, disable, status = vals + DTR = self.get_gpio(self.DTR) + PWRKEY = self.get_gpio(self.PWRKEY) + RESET = self.get_gpio(self.RESET) + APREADY = self.get_gpio(self.APREADY) + DISABLE = self.get_gpio(self.DISABLE) + STATUS = self.get_gpio(self.STATUS) logger.debug( "gpio states:\n" - f" DTR: {dtr}\n" - f" PWRKEY: {pwrkey}\n" - f" RESET: {reset}\n" - f" APREADY: {apready}\n" - f" DISABLE: {disable}\n" - f" STATUS: {status}" + f" DTR: {DTR}\n" + f" PWRKEY: {PWRKEY}\n" + f" RESET: {RESET}\n" + f" APREADY: {APREADY}\n" + f" DISABLE: {DISABLE}\n" + f" STATUS: {STATUS}" ) +class PinePhoneGpioPhy(GpioPhy): + # PLEASE NOTE THAT THIS REQUIRES THE AXP REGULATOR TO BE POWERING VBAT-BB. + # as of 2024-09-19, that's not mainline; it still requires a handful of patches. + # see postmarketOS' or mobian's kernel to cherry-pick the necessary patches. + + # GPIO indices ("lines") + # DTR = "Data Terminal Ready", controls sleep/wakeup. high means modem is allowed to sleep when requested. high -> low forces the modem awake. internal pull-up. + # WAKEUP = 6 + DTR = 1, 34 # PB2 + PWRKEY = 1, 35 # PB3 + RESET = 1, 68 # PC4 + APREADY = 1, 231 # PH7, a.k.a. "host ready" + DISABLE = 1, 232 # PH8, a.k.a. "enable"; active-low + # STATUS line: HIGH means powered off + STATUS = 1, 233 # PH9 + +class PinePhoneProGpioPhy(GpioPhy): + DTR = 0, 3 # gpio0, RK_PA3 + PWRKEY = 0, 13 # gpio0, RK_PB5 + RESET = 3, 8 # gpio3, RK_PB0 + APREADY = 0, 12 # gio0, RK_RB4 ('host-ready-gpios') + DISABLE = 0, 8 # gpio0, RK_PB0 (TODO: megi has this marked active HIGH, not LOW) + STATUS = 3, 6 # gpio3, RK_PA6 + class Sequencer: def __init__(self, executor: Executor, modem: str, modem_phy: Phy, state_fs: Filesystem): self.executor = executor @@ -720,7 +771,7 @@ def main(): modem_phy = MegiPhy(executor, args.power_endpoint) elif args.power_on or args.power_off: # don't initialize the Gpio PHY unless absolutely necessary, since it has to (re-)configure GPIOs just to show debug info - modem_phy = GpioPhy() + modem_phy = PinePhoneGpioPhy() sequencer = Sequencer(executor, modem=args.modem, modem_phy=modem_phy, state_fs=state_fs)