aboutsummaryrefslogtreecommitdiff
path: root/usb-control/cp210x_controller.py
blob: 2fe9008c591bc1ef96d2f115ad3f6e00eeca8ab7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#!/usr/bin/env python

__copyright__ = 'Copyright (C) 2019 Theobroma Systems Design und Consulting GmbH'
__license__ = 'MIT'

import argparse
import usb.core
import usb.util
import sys
import time

VENDOR_ID               = 0x10c4
PRODUCT_ID              = 0xea60

REQTYPE_HOST_TO_DEVICE  = 0x40
REQTYPE_DEVICE_TO_HOST  = 0xc0

CP210X_VENDOR_SPECIFIC  = 0xFF

CP210X_WRITE_LATCH      = 0x37E1
CP210X_READ_LATCH       = 0x00C2
CP210X_GET_MDMSTS       = 0x8

# Power supply GPIO
GPIO_POWER = 0
VALUE_POWER_RELEASE = 0
VALUE_POWER_PRESS = 1

# BIOS disable GPIO
GPIO_BIOS = 1
VALUE_BIOS_NORMAL = 0
VALUE_BIOS_DISABLE = 1

# Power state
POWERSTATE_ON = 0
POWERSTATE_OFF = 1

def set_gpio(dev, gpio, value):
    # the latch register has 16 bit. the upper 8 bits are the gpio value,
    # the lower 8 bit are the write mask.
    val = (value << (8 + gpio)) | (1 << gpio);
    dev.ctrl_transfer(bmRequestType = REQTYPE_HOST_TO_DEVICE,
                      bRequest = CP210X_VENDOR_SPECIFIC,
                      wValue = CP210X_WRITE_LATCH,
                      wIndex = val)

def get_gpio(dev, gpio):
    # Read 1 byte from latch register
    data = dev.ctrl_transfer(bmRequestType = REQTYPE_DEVICE_TO_HOST,
                             bRequest = CP210X_VENDOR_SPECIFIC,
                             wValue = CP210X_READ_LATCH,
                             data_or_wLength = 1)
    return not not (data[0] & (1 << gpio))

def toggle_power(dev):
    # We emulate a power button press for 300 ms
    set_gpio(dev, GPIO_POWER, VALUE_POWER_PRESS)
    time.sleep(1)
    set_gpio(dev, GPIO_POWER, VALUE_POWER_RELEASE)

def get_powerstate(dev):
    # Reading the latch would be useless, but we have CTS connected to 3V3
    # Get modem status
    data = dev.ctrl_transfer(bmRequestType = REQTYPE_DEVICE_TO_HOST,
                             bRequest = CP210X_GET_MDMSTS,
                             wValue = 0,
                             wIndex = 0,
                             data_or_wLength = 1);
    # Check CTS line
    return ((data[0] >> 4) & 1)

def get_bootmode(dev):
    # We can only check if we pull the line low
    return get_gpio(dev, GPIO_BIOS)

def create_parser():
    parser = argparse.ArgumentParser(description=
            'Controls power and BIOS disable state of the Haikou baseboard')

    # Generic arguments (for identifying the USB device)
    parser.add_argument('--ignore-product-string', '-i',
            action='store_true',
            dest='ignore_product_string',
            help='ignore product string')

    parser.add_argument('--serial', '-s',
            action='store',
            dest='serialnumber',
            type=str,
            help='defines the serialnumber to use')

    # Arguments to set the bootmode
    parser_bootmode_group = parser.add_mutually_exclusive_group()
    parser_bootmode_group.add_argument('--normal-boot', '-n',
            action='store_true',
            dest='normal_boot',
            help='activate Normal Boot')
    parser_bootmode_group.add_argument('--bios-disable', '-b',
            action='store_true',
            dest='bios_disable',
            help='activate BIOS Disable')

    # Commands
    subparsers = parser.add_subparsers(help='command to perform',
            dest='subcommand')
    subparsers.add_parser('list',
            help='lists serial numbers of attached Haikou Baseboards')
    subparsers.add_parser('on',
            help='turn on attached Haikou Baseboards')
    subparsers.add_parser('off',
            help='turn off attached Haikou Baseboards')
    subparsers.add_parser('cycle',
            help='power cycle attached Haikou Baseboards')
    subparsers.add_parser('toggle',
            help='emulate power button press on attached Haikou Baseboards')
    subparsers.add_parser('toggle2',
            help='emulate two power button presses on attached Haikou Baseboards')
    subparsers.add_parser('status',
            help='show status of attached Haikou Baseboards')
    subparsers.add_parser('force-on',
            help='tdb')
    subparsers.add_parser('force-off',
            help='tdb')

    return parser

def find_board_list(product_string, serialnumber):
    try:
        kwargs = dict()
        kwargs['idVendor'] = VENDOR_ID
        kwargs['idProduct'] = PRODUCT_ID
        if product_string != None:
            kwargs['product'] = product_string
        if serialnumber:
            kwargs['serial_number'] = serialnumber
        kwargs['find_all'] = True

        # Find the devices matching the specified requirements
        dev_it = usb.core.find(**kwargs)

        # Convert iterator to list
        devs = list(dev_it)

        return devs

    except ValueError, e:
        if 'langid' in e.message:
            raise usb.core.USBError(e.message + "\n" +
                    "This may be a permission issue. See: \n" +
                    "https://github.com/pyusb/pyusb/issues/139")

def cp210x_controller(product_string):
    # Create the argument parser
    parser = create_parser()

    # Apply the parser
    args = parser.parse_args()

    # Collect list of potential devices
    if args.ignore_product_string == True:
        product_string = None
    devs = find_board_list(product_string, args.serialnumber)

    if args.subcommand == 'list':
        print "Found %d Baseboard%s:" % (len(devs), "s"[len(devs) == 1:])
        for idx, dev in enumerate(devs):
            print "%d: %s %s" % (idx, dev.serial_number, dev.product)
        sys.exit()


    for dev in devs:
        if args.bios_disable:
            set_gpio(dev, GPIO_BIOS, VALUE_BIOS_DISABLE)
        elif args.normal_boot:
            set_gpio(dev, GPIO_BIOS, VALUE_BIOS_NORMAL)

        if args.subcommand == 'on':
            if get_powerstate(dev) == POWERSTATE_OFF:
                toggle_power(dev)
        elif args.subcommand == 'off':
            if get_powerstate(dev) == POWERSTATE_ON:
                toggle_power(dev)
        elif args.subcommand == 'cycle':
            if get_powerstate(dev) == POWERSTATE_ON:
                toggle_power(dev)
                time.sleep(1)
            toggle_power(dev)
        elif args.subcommand == 'toggle':
            toggle_power(dev)
        elif args.subcommand == 'toggle2':
            toggle_power(dev)
            time.sleep(1)
            toggle_power(dev)
        elif args.subcommand == 'status':
            powerstate = get_powerstate(dev)
            bootmode = get_bootmode(dev)
            print "Board is {} ({})".format(
                    ("ON" if powerstate == POWERSTATE_ON else "OFF"),
                    ("BIOS disabled" if bootmode == VALUE_BIOS_DISABLE else "Normal boot (if not overruled by on-board switch)"))
        elif args.subcommand == "force-on":
            set_gpio(dev, GPIO_POWER, VALUE_POWER_PRESS)
        elif args.subcommand == "force-off":
            set_gpio(dev, GPIO_POWER, VALUE_POWER_RELEASE)

if __name__ == '__main__':
    cp210x_controller(None)