| #!/usr/bin/python |
| |
| # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """A script to create symlinks to platform specific GPIO pins. |
| |
| This script creates a set of symlinks pointing at the sys fs files returning |
| the appropriate GPIO pin values. Each symlink is named to represent the actual |
| GPIO pin function. |
| |
| The location of the symlinks generated by this script can be specified using |
| the --symlink_root command line option. By default /home/gpio directory is |
| used. The symlink directory must exist before this script is run. |
| |
| The GPIO pins' values are available through a GPIO device present in sys fs. |
| The device is identified by its PCI bus address. The default PCI address of |
| the GPIO device (set to 0:0:1f.0), can be changed using the --pci_address |
| command line option. |
| |
| The platform specific bit usage of the GPIO device is derived from the ACPI, |
| also using files found in a fixed location in sys fs. The default location of |
| /sys/bus/platform/devices/chromeos_acpi could be changed using the --acpi_root |
| command line option. |
| |
| Each GPIO pin is represented through ACPI as a subdirectory with several files |
| in it. A typical name of the GPIO pin file looks as follows: |
| |
| <acpi_root>/GPIO.<instance>/GPIO.[0-3] |
| |
| where <instance> is a zero based number assigned to this GPIO pin by the BIOS. |
| |
| In particular, file GPIO.0 represents encoded pin signal type (from which the |
| symlink name is derived), and file GPIO.2 represents the actual zero based |
| GPIO pin number within this GPIO device range. |
| |
| This script reads the ACPI provided mapping, enables the appropriate GPIO pins |
| and creates symlinks mapping these GPIOs' values. |
| """ |
| |
| __author__ = 'The Chromium OS Authors' |
| |
| import glob |
| import os |
| import optparse |
| import sys |
| |
| GPIO_ROOT = '/sys/class/gpio' |
| GPIO_DEVICE_ROOT = GPIO_ROOT + '/gpiochip' |
| GPIO_ENABLE_FILE = '/sys/class/gpio/' + 'export' |
| |
| # Can be changed using --pci_address command line option. |
| GPIO_DEVICE_PCI_ADDRESS = '0000:00:1f.0' |
| |
| # Can be changed using --acpi_root command line option. |
| ACPI_ROOT = '/sys/bus/platform/devices/chromeos_acpi' |
| GPIO_SIGNAL_TYPE_EXTENSION = '0' |
| GPIO_PIN_NUMBER_EXTENSION = '2' |
| |
| # can be changed using --symlink_root command line option. |
| DEFAULT_SYMLINK_ROOT = '/home/gpio' |
| |
| # This dictionary maps GPIO signal types codes into their actual names. |
| GPIO_SIGNAL_TYPES = { |
| 1: 'recovery_button', |
| 2: 'developer_switch', |
| 3: 'write_protect' |
| } |
| |
| # Debug header signal type codes are offset by 0x100, the tuple below |
| # represents the range of valid codes for the debug header. The range starts |
| # at 0x100 and is 0x100 wide. |
| GPIO_DEBUG_HEADER_RANGE = (0x100, 0x100) |
| |
| # This is used to prepare the option parser: each element is a tuple of |
| # strings, including the option name and the option default value. |
| option_list = (('symlink_root', DEFAULT_SYMLINK_ROOT), |
| ('pci_address', GPIO_DEVICE_PCI_ADDRESS), |
| ('acpi_root', ACPI_ROOT)) |
| |
| # This becomes the option object after the command line is parsed. |
| cmd_line_options = None |
| |
| class GpioSetupError(Exception): |
| pass |
| |
| |
| class GpioChip(object): |
| """Represent GPIO chip available through sys fs. |
| |
| Attributes: |
| pci_address: a string, PCI address of this GPIO device |
| base: a number, base global GPIO number of this device (mapped to pin zero |
| in the device range) |
| capacity: a number, shows the number of GPIO pins of this device. |
| description: a multiline string description of this device, initialized |
| after the device is attached. Can be used to dump device |
| information. |
| """ |
| |
| def __init__(self, pci_address): |
| self.pci_address = pci_address |
| self.base = 0 |
| self.capacity = 0 |
| self.description = 'not attached' |
| |
| def Attach(self): |
| for f in glob.glob(GPIO_DEVICE_ROOT + '*/label'): |
| label = open(f).read().strip() |
| if label == self.pci_address: |
| break |
| else: |
| raise GpioSetupError( |
| 'could not find GPIO PCI device %s' % self.pci_address) |
| directory = os.path.dirname(f) |
| self.base = int(open(directory + '/base').read()) |
| self.capacity = int(open(directory + '/ngpio').read()) |
| self.description = '\n'.join(['GPIO device at PCI address %s' % |
| self.pci_address, |
| 'Base gpio pin %d' % self.base, |
| 'Capacity %d' % self.capacity]) |
| |
| def EnablePin(self, pin): |
| """Enable a certain GPIO pin. |
| |
| To enable the pin one needs to write its global GPIO number into |
| /sys/class/gpio/export, if this pin has not been enabled yet. |
| |
| Inputs: |
| pin: a number, zero based pin number within this device's range. |
| """ |
| |
| if pin >= self.capacity: |
| raise GpioSetupError('pin %d exceeds capacity of %d' % ( |
| pin, self.capacity)) |
| global_gpio_number = self.base + pin |
| if not os.path.exists('%s/gpio%d' % (GPIO_ROOT, global_gpio_number)): |
| open(GPIO_ENABLE_FILE, 'w').write('%d' % (global_gpio_number)) |
| |
| def __str__(self): |
| return self.description |
| |
| |
| def ParseAcpiMappings(): |
| """Scan ACPI information about GPIO and generate a mapping. |
| |
| Returns: a list of tuples, each tuple consisting of a string representing |
| the GPIO pin name and a number, representing the GPIO pin within |
| the GPIO device space. |
| """ |
| acpi_gpio_mapping = [] |
| for d in glob.glob('%s/GPIO.[0-9]*' % cmd_line_options.acpi_root): |
| signal_type = int(open('%s/GPIO.%s' % ( |
| d, GPIO_SIGNAL_TYPE_EXTENSION)).read()) |
| |
| pin_number = int(open('%s/GPIO.%s' % ( |
| d, GPIO_PIN_NUMBER_EXTENSION)).read()) |
| |
| if signal_type in GPIO_SIGNAL_TYPES: |
| acpi_gpio_mapping.append((GPIO_SIGNAL_TYPES[signal_type], pin_number)) |
| continue |
| |
| # This is not a specific signal, could be a debug header pin. |
| debug_header = signal_type - GPIO_DEBUG_HEADER_RANGE[0] |
| if debug_header >= 0 and debug_header < GPIO_DEBUG_HEADER_RANGE[1]: |
| acpi_gpio_mapping.append(('debug_header_%d' % debug_header, pin_number)) |
| continue |
| |
| # Unrecognized mapping, could happen if BIOS version is ahead of this |
| # script. |
| print 'unknown signal type encoding %d in %d' % (signal_type, d) |
| |
| if not acpi_gpio_mapping: |
| raise GpioSetupError('no gpio mapping found. Is ACPI driver installed?') |
| |
| return acpi_gpio_mapping |
| |
| |
| def CreateGpioSymlinks(mappings, gpio, symlink_root): |
| if not os.path.exists(symlink_root): |
| raise GpioSetupError('%s does not exist' % symlink_root) |
| |
| if not os.path.isdir(symlink_root): |
| raise GpioSetupError('%s is not a directory' % symlink_root) |
| |
| if not os.access(symlink_root, os.W_OK): |
| raise GpioSetupError('%s is not writable' % symlink_root) |
| |
| try: |
| os.chdir(symlink_root) |
| except OSError: |
| raise GpioSetupError('failed to change directory to %s' % symlink_root) |
| |
| for (symlink, pin) in mappings: |
| gpio.EnablePin(pin) |
| source_file = '%s/gpio%d/value' % (GPIO_ROOT, pin + gpio.base) |
| if not os.path.exists(symlink): |
| os.symlink(source_file, symlink) |
| continue |
| if not os.path.islink(symlink): |
| raise GpioSetupError( |
| '%s exists but is not a symlink' % os.path.abspath(symlink)) |
| if os.readlink(symlink) != source_file: |
| raise GpioSetupError( |
| '%s points to a wrong file' % os.path.abspath(symlink)) |
| |
| |
| def ProcessOptions(): |
| global cmd_line_options |
| parser = optparse.OptionParser() |
| for (name, default_value) in option_list: |
| parser.add_option('--' + name, dest=name, default=default_value) |
| (cmd_line_options, _) = parser.parse_args() |
| |
| |
| def main(): |
| ProcessOptions() |
| gpioc = GpioChip(cmd_line_options.pci_address) |
| gpioc.Attach() |
| CreateGpioSymlinks(ParseAcpiMappings(), gpioc, cmd_line_options.symlink_root) |
| |
| |
| if __name__ == '__main__': |
| try: |
| main() |
| except GpioSetupError, e: |
| print >> sys.stderr, '%s: %s' % (sys.argv[0].split('/')[-1], e) |
| sys.exit(1) |
| sys.exit(0) |