Public/Set-PsGadgetFt232rCbusMode.ps1

# Set-PsGadgetFt232rCbusMode.ps1
# Public function to program FT232R CBUS pin functions via EEPROM.

#Requires -Version 5.1

function Set-PsGadgetFt232rCbusMode {
    <#
    .SYNOPSIS
    Programs the FT232R EEPROM to configure CBUS pins for GPIO bit-bang mode.
 
    .DESCRIPTION
    Writes the FT232R device EEPROM to assign the specified CBUS pins to
    FT_CBUS_IOMODE (or any other supported FT_CBUS_OPTIONS function).
 
    This is a one-time setup step that replaces the need for FTDI's FT_PROG tool.
    After writing, the function will prompt you to either cycle the USB port
    automatically (no cable unplug required) or replug the cable manually.
 
    Once CBUS pins are set to FT_CBUS_IOMODE, Set-PsGadgetGpio can control them
    directly without any additional EEPROM changes.
 
    Workflow:
        1. Run Set-PsGadgetFt232rCbusMode once per device to enable GPIO on CBUS pins.
        2. Accept the prompt to cycle the port, or unplug and replug the USB cable.
        3. Use Set-PsGadgetGpio -Index N -Pins @(0..3) -State HIGH/LOW freely.
 
    Available -Mode values:
        FT_CBUS_IOMODE GPIO / bit-bang [DEFAULT - enables Set-PsGadgetGpio]
        FT_CBUS_TXLED Pulses on Tx data
        FT_CBUS_RXLED Pulses on Rx data
        FT_CBUS_TXRXLED Pulses on Tx or Rx data
        FT_CBUS_PWREN Power-on signal (PWREN#, active low)
        FT_CBUS_SLEEP Sleep indicator
        FT_CBUS_CLK48 48 MHz clock output
        FT_CBUS_CLK24 24 MHz clock output
        FT_CBUS_CLK12 12 MHz clock output
        FT_CBUS_CLK6 6 MHz clock output
        FT_CBUS_TXDEN Tx Data Enable
        FT_CBUS_BITBANG_WR Bit-bang write strobe
        FT_CBUS_BITBANG_RD Bit-bang read strobe
 
    .PARAMETER Index
    Zero-based device index (from Get-FTDevice).
 
    .PARAMETER SerialNumber
    Alternative to Index: specify the target device by serial number string.
 
    .PARAMETER Pins
    The CBUS pin numbers to reconfigure (0-3). Defaults to @(0,1,2,3) so all four
    CBUS bit-bang pins are enabled in one call.
 
    .PARAMETER Mode
    The FT_CBUS_OPTIONS mode name to write. Defaults to FT_CBUS_IOMODE (GPIO).
 
    .PARAMETER WhatIf
    Shows what EEPROM change would be made without writing anything.
 
    .EXAMPLE
    # Configure all four CBUS pins as GPIO on device 0 (most common usage):
    Set-PsGadgetFt232rCbusMode -Index 0
 
    .EXAMPLE
    # Configure only CBUS0 and CBUS1 as GPIO; leave CBUS2/3 unchanged:
    Set-PsGadgetFt232rCbusMode -Index 0 -Pins @(0, 1)
 
    .EXAMPLE
    # Set CBUS0 to Rx LED instead of GPIO:
    Set-PsGadgetFt232rCbusMode -Index 0 -Pins @(0) -Mode FT_CBUS_RXLED
 
    .EXAMPLE
    # Preview what would change without writing:
    Set-PsGadgetFt232rCbusMode -Index 0 -WhatIf
 
    .PARAMETER HighDriveIOs
    Override the ftdi.highDriveIOs config setting for this call.
    When omitted, the value from Get-PsGadgetConfig is used (default: $false).
    $true doubles CBUS drive strength from 4 mA to 8 mA.
 
    .PARAMETER PullDownEnable
    Override the ftdi.pullDownEnable config setting for this call.
    When omitted, the value from Get-PsGadgetConfig is used (default: $false).
    $true adds weak pull-downs on all I/O pins during USB suspend.
 
    .PARAMETER RIsD2XX
    Override the ftdi.rIsD2XX config setting for this call.
    When omitted, the value from Get-PsGadgetConfig is used (default: $false).
    $true makes the device enumerate as D2XX-only (no duplicate COM port).
 
    .NOTES
    - Only CBUS pins 0-3 are configurable. CBUS4 is a special-purpose pin.
    - After a successful write, the function prompts to cycle the USB port
      automatically. Accepting is equivalent to physically unplugging and replugging.
    - The result object includes a PortCycled property indicating whether the port
      was cycled automatically (True) or left for manual replug (False).
    - HighDriveIOs, PullDownEnable, and RIsD2XX default to the values in
      ~/.psgadget/config.json (see Get-Help about_PsGadgetConfig).
    - To verify the EEPROM after cycling/replugging, use Get-PsGadgetFtdiEeprom.
    - This function requires Windows with the D2XX driver loaded.
      On Linux, use an FT232H device instead -- it has MPSSE and is fully supported
      via the IoT backend without any EEPROM pre-programming step.
    #>


    [CmdletBinding(
        DefaultParameterSetName = 'ByIndex',
        SupportsShouldProcess,
        ConfirmImpact = 'High'
    )]
    [OutputType([System.Object])]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'ByIndex', Position = 0)]
        [int]$Index,

        [Parameter(Mandatory = $true, ParameterSetName = 'BySerial')]
        [string]$SerialNumber,

        [Parameter(Mandatory = $true, ParameterSetName = 'PsGadget', Position = 0)]
        [ValidateNotNull()]
        [PsGadgetFtdi]$PsGadget,

        [Parameter(Mandatory = $false, Position = 1)]
        [ValidateRange(0, 4)]
        [int[]]$Pins = @(0, 1, 2, 3),

        [Parameter(Mandatory = $false, Position = 2)]
        [ValidateSet(
            'FT_CBUS_TXDEN','FT_CBUS_PWREN','FT_CBUS_RXLED','FT_CBUS_TXLED',
            'FT_CBUS_TXRXLED','FT_CBUS_SLEEP','FT_CBUS_CLK48','FT_CBUS_CLK24',
            'FT_CBUS_CLK12','FT_CBUS_CLK6','FT_CBUS_IOMODE',
            'FT_CBUS_BITBANG_WR','FT_CBUS_BITBANG_RD'
        )]
        [string]$Mode = 'FT_CBUS_IOMODE',

        # EEPROM flag overrides -- when omitted the value from config.json is used.
        [Parameter(Mandatory = $false)]
        [System.Nullable[bool]]$HighDriveIOs,

        [Parameter(Mandatory = $false)]
        [System.Nullable[bool]]$PullDownEnable,

        [Parameter(Mandatory = $false)]
        [System.Nullable[bool]]$RIsD2XX
    )

    try {
        # Resolve device index
        $targetIndex = $Index
        if ($PSCmdlet.ParameterSetName -eq 'BySerial') {
            $devices = Get-FtdiDeviceList
            $match   = $devices | Where-Object { $_.SerialNumber -eq $SerialNumber }
            if (-not $match) {
                throw "No FTDI device found with serial number '$SerialNumber'"
            }
            $targetIndex = $match.Index
        } elseif ($PSCmdlet.ParameterSetName -eq 'PsGadget') {
            $targetIndex = $PsGadget.Index
        }

        # Validate that the target is an FT232R family device
        $deviceList = Get-FtdiDeviceList
        $targetDev  = $null
        foreach ($d in @($deviceList)) {
            if ($d.Index -eq $targetIndex) {
                $targetDev = $d
                break
            }
        }

        if (-not $targetDev) {
            throw "Device at index $targetIndex not found. Run Get-PsGadgetFtdi to check available devices."
        }

        if ($targetDev.Type -notmatch '^FT232R(L|NL)?$') {
            throw (
                "Device '$($targetDev.Type)' ($($targetDev.SerialNumber)) is not an FT232R family device. " +
                "Set-PsGadgetFt232rCbusMode only supports FT232R / FT232RL / FT232RNL."
            )
        }

        # Show current EEPROM state for context
        Write-Verbose "Reading current EEPROM for $($targetDev.Description) ($($targetDev.SerialNumber))..."
        $current = Get-FtdiFt232rEeprom -Index $targetIndex -SerialNumber $targetDev.SerialNumber
        if ($current) {
            $pinLines = $Pins | ForEach-Object {
                $cur = $current."Cbus$_"
                " CBUS$_ : $cur -> $Mode"
            }
            Write-Verbose "EEPROM changes planned:`n$($pinLines -join "`n")"
        }

        # Confirm and write
        $pinNames  = ($Pins | ForEach-Object { "CBUS$_" }) -join ', '
        $operation = "Write FT232R EEPROM: set $pinNames to $Mode"

        if (-not $PSCmdlet.ShouldProcess("$($targetDev.Description) ($($targetDev.SerialNumber))", $operation)) {
            return $null
        }

        # Resolve EEPROM flags: explicit param wins over config default
        $cfg = $script:PsGadgetConfig
        $resolvedHighDriveIOs   = if ($null -ne $HighDriveIOs)   { [bool]$HighDriveIOs }   else { $cfg.ftdi.highDriveIOs }
        $resolvedPullDownEnable = if ($null -ne $PullDownEnable)  { [bool]$PullDownEnable }  else { $cfg.ftdi.pullDownEnable }
        $resolvedRIsD2XX        = if ($null -ne $RIsD2XX)         { [bool]$RIsD2XX }         else { $cfg.ftdi.rIsD2XX }

        $result = Set-FtdiFt232rCbusPinMode -Index $targetIndex -Pins $Pins -Mode $Mode `
            -SerialNumber $targetDev.SerialNumber `
            -HighDriveIOs $resolvedHighDriveIOs `
            -PullDownEnable $resolvedPullDownEnable `
            -RIsD2XX $resolvedRIsD2XX

        if ($result.Success) {
            Write-Verbose "FT232R EEPROM updated: $pinNames set to $Mode."

            # Inform the user and offer automatic port cycling.
            Write-Host ""
            Write-Host "EEPROM written successfully."
            Write-Host "The new CBUS pin settings will not take effect until the device re-enumerates on the USB bus."
            Write-Host ""
            Write-Host "You have two options:"
            Write-Host " [Y] Cycle the USB port automatically right now (no cable unplug needed)"
            Write-Host " [N] Unplug and replug the USB cable manually, then continue"
            Write-Host ""

            $choices = [System.Management.Automation.Host.ChoiceDescription[]]@(
                [System.Management.Automation.Host.ChoiceDescription]::new(
                    '&Yes',
                    'Cycle the USB port now. The device will briefly disconnect and reconnect automatically without needing to physically unplug the cable.'
                )
                [System.Management.Automation.Host.ChoiceDescription]::new(
                    '&No',
                    'Skip automatic cycling. Unplug and replug the USB cable manually, then continue.'
                )
            )

            $choice = $Host.UI.PromptForChoice(
                'Apply EEPROM Changes',
                'Cycle the USB port now to apply the new settings?',
                $choices,
                0   # default = Yes
            )

            if ($choice -eq 0) {
                Write-Host ""
                Write-Host "Cycling USB port on $($targetDev.Description) ($($targetDev.SerialNumber))..."
                try {
                    $cycleDevice = [PsGadgetFtdi]::new([int]$targetIndex)
                    $cycleDevice.Connect()
                    $cycleDevice.CyclePort()

                    Write-Host ""
                    Write-Host "Port cycled successfully. The device has re-enumerated with the new EEPROM settings."
                    Write-Host "You can now use Set-PsGadgetGpio or Connect-PsGadgetFtdi immediately."
                    Write-Host ""
                    Write-Host "To verify the new settings:"
                    Write-Host " Get-PsGadgetFtdiEeprom -Index $targetIndex | Select-Object Cbus0, Cbus1, Cbus2, Cbus3"

                    $result | Add-Member -MemberType NoteProperty -Name 'PortCycled' -Value $true -Force
                } catch {
                    Write-Warning "CyclePort failed: $_"
                    Write-Warning "Please unplug and replug the USB cable manually to apply the new EEPROM settings."
                    $result | Add-Member -MemberType NoteProperty -Name 'PortCycled' -Value $false -Force
                }
            } else {
                Write-Host ""
                Write-Host "ACTION REQUIRED: Unplug and replug the USB cable to activate the new EEPROM settings."
                Write-Host ""
                Write-Host "After replugging, verify the change with:"
                Write-Host " Get-PsGadgetFtdiEeprom -Index $targetIndex | Select-Object Cbus0, Cbus1, Cbus2, Cbus3"
                Write-Host ""
                $result | Add-Member -MemberType NoteProperty -Name 'PortCycled' -Value $false -Force
            }
        }

        return $result

    } catch {
        Write-Error "Set-PsGadgetFt232rCbusMode failed: $_"
        return $null
    }
}