#
# Copyright 2017 Ettus Research (National Instruments)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see .
#
"""
Access to GPIOs mapped into the PS via sysfs
"""
import os
from builtins import object
import pyudev
from .mpmlog import get_logger
GPIO_SYSFS_BASE_DIR = '/sys/class/gpio'
GPIO_SYSFS_LABELFILE = 'label'
GPIO_SYSFS_VALUEFILE = 'value'
def get_all_gpio_devs():
    """
    Returns a list of all GPIO chips available through sysfs. Will look
    something like ['gpiochip882', 'gpiochip123', ...]
    """
    try:
        context = pyudev.Context()
        gpios = [device.sys_name
                 for device in context.list_devices(subsystem="gpio")
                 if os.path.exists(os.path.join( # udev probably has better ways to do this
                     GPIO_SYSFS_BASE_DIR,
                     device.sys_name,
                     GPIO_SYSFS_LABELFILE
                 ))
                ]
        return gpios
    except OSError:
        # Typically means GPIO not available, maybe no overlay
        return []
def get_gpio_map_info(gpio_dev):
    """
    Returns all the map info for a given GPIO device.
    Example: If pio_dev is 'gpio882', it will list all files
    in /sys/class/gpio/gpio882/ and create a dictionary with filenames
    as keys and content as value. Subdirs are skipped.
    Numbers are casted to numbers automatically. Strings remain strings.
    """
    map_info = {}
    map_info_path = os.path.join(
        GPIO_SYSFS_BASE_DIR, gpio_dev,
    )
    for info_file in os.listdir(map_info_path):
        if not os.path.isfile(os.path.join(map_info_path, info_file)):
            continue
        map_info_value = open(os.path.join(map_info_path, info_file), 'r').read().strip()
        try:
            map_info[info_file] = int(map_info_value, 0)
        except ValueError:
            map_info[info_file] = map_info_value
    # Manually add GPIO number
    context = pyudev.Context()
    map_info['sys_number'] = int(
        pyudev.Devices.from_name(context, subsystem="gpio", sys_name=gpio_dev).sys_number
    )
    return map_info
def find_gpio_device(label, logger=None):
    """
    Given a label, returns a tuple (uio_device, map_info).
    uio_device is something like 'gpio882'. map_info is a dictionary with
    information regarding the GPIO device read from the map info sysfs dir.
    """
    gpio_devices = get_all_gpio_devs()
    if logger:
        logger.trace("Found the following UIO devices: `{0}'".format(','.join(gpio_devices)))
    for gpio_device in gpio_devices:
        map_info = get_gpio_map_info(gpio_device)
        if logger:
            logger.trace("{0} has map info: {1}".format(gpio_device, map_info))
        if map_info.get('label') == label:
            if logger:
                logger.trace("Device matches label: `{0}'".format(gpio_device))
            return gpio_device, map_info
    if logger:
        logger.warning("Found no matching gpio device for label `{0}'".format(label))
    return None, None
class SysFSGPIO(object):
    """
    API for accessing GPIOs mapped into userland via sysfs
    """
    def __init__(self, label, use_mask, ddr):
        assert (use_mask & ddr) == ddr
        self.log = get_logger("SysFSGPIO")
        self._label = label
        self._use_mask = use_mask
        self._ddr = ddr
        self.log.trace("Generating SysFSGPIO object for label `{}'...".format(label))
        self._gpio_dev, self._map_info = find_gpio_device(label, self.log)
        if self._gpio_dev is None:
            self.log.error("Could not find GPIO device with label `{}'.".format(label))
        self.log.trace("GPIO base number is {}".format(self._map_info.get("sys_number")))
        self._base_gpio = self._map_info.get("sys_number")
        self.init(self._map_info['ngpio'], self._base_gpio, self._use_mask, self._ddr)
    def init(self, n_gpio, base, use_mask, ddr):
        """
        Guarantees that all the devices are created accordingly
        E.g., if use_mask & 0x1 is True, it makes sure that 'gpioXXX' is exported.
        Also sets the DDRs.
        """
        gpio_list = [x for x in range(n_gpio) if (1<