| # Copyright 2011 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Base class for servo drivers.""" |
| |
| import logging |
| import re |
| import weakref |
| |
| |
| VALID_IO_TYPES = ["PU", "PP"] |
| |
| |
| class HwDriverError(Exception): |
| """Exception class for HwDriver.""" |
| |
| |
| def _get_io_type(params): |
| """Retrieve IO driver type. |
| |
| Valid driver types are: |
| PU == Pull-up |
| PP == Push-pull |
| |
| Returns: string either 'PP' or 'PU' |
| |
| Raises: HwDriverError if: |
| io type is invalid |
| controls width > 1 |
| """ |
| if "od" in params: |
| width = params.get("width", 1) |
| if width > 1: |
| raise HwDriverError("Open drain not implemented for widths != 1") |
| io_type = params["od"].upper() |
| if io_type not in VALID_IO_TYPES: |
| raise HwDriverError("Unrecognized io type (param 'od') %s" % io_type) |
| return io_type |
| return False |
| |
| |
| class HwDriver: |
| """Base class for all hardware drivers.""" |
| |
| # The list of params that an overlay needs to specific for the drv to be |
| # effective. By default, that list is empty, though a drv implementation |
| # can simply overwrite them. |
| # Should a drv require different params for set and get, make sure that |
| # 1. the drv uses the type specific |REQUIRED_GET_PARAMS| and |
| # |REQUIRED_SET_PARAMS| and |
| # 2. the overlay contains the right 'cmd' for each of them, or follows the cmd |
| # convention for single-params controls. |
| # If both set and get use the same required params, simplify define one and |
| # set the other to that definition. |
| REQUIRED_GET_PARAMS = [] |
| REQUIRED_SET_PARAMS = [] |
| |
| def __init__(self, interface, params, servod=None): |
| """Driver constructor. |
| |
| Args: |
| interface: FTDI interface object to handle low-level communication to |
| control |
| params: dictionary of params needed to perform operations on gpio driver |
| |
| params dictionary can have k=v pairs necessary to communicate with the |
| underlying hardware. Notables are: |
| width: integer of width of the control in bits. |
| Default: 1 |
| offset: integer of position of bit(s) with respect to lsb. |
| Default: 0 |
| map: name string of map dictionary to use for unmapping / remapping |
| fmt: function name string to call to format the result. |
| |
| Additional param keys will be described in sub-class drivers. |
| servod: Servod object to handle cross-servo-device communication |
| |
| Attributes: |
| _logger: logger object. May be accessed via sub-class |
| _complement: a weak reference to the complement drv e.g. the controls' |
| set implementation if this is a get implementation. |
| _interface: interface object. May be accessed via sub-class |
| _params: parameter dictionary. May be accessed via sub-class |
| _io_type: String of io type or False if not explicitly assigned. |
| """ |
| self._logger = logging.getLogger(type(self).__name__) |
| self._logger.debug("") |
| self._complement = None |
| self._interface = interface |
| self._servod = servod |
| self._params = params |
| # Check whether all required params are provided. if 'cmd' is in params, |
| # use a type-specific |REQUIRED_PARAMS| e.g. set or get. If not, use the |
| # generic one. |
| cmd = self._params["cmd"] |
| if cmd == "get": |
| req_params = self.REQUIRED_GET_PARAMS |
| elif cmd == "set": |
| req_params = self.REQUIRED_SET_PARAMS |
| # The cmd is invalid here, raise an error. While this doesn't necessarily |
| # cause an issue, we do not want config files laying around that misuse |
| # cmd. |
| else: |
| raise HwDriverError("'cmd' param %r unknown. Use 'set' or 'get'" % (cmd,)) |
| for a in req_params: |
| # Empty |req_params| leads to no checks being performed. |
| if a not in self._params: |
| raise HwDriverError("Required param %r not in params" % (a,)) |
| # |set| cmd might define choices that show what are valid choices to be |
| # set. |
| self._choices = None |
| if "choices" in self._params: |
| self._choices = re.compile(self._params["choices"]) |
| self._logger.debug("Valid input choices: %s", self._choices) |
| self._io_type = _get_io_type(params) |
| self._prefix = self._params.get("interface_prefix") |
| self._drv_init() |
| |
| def _drv_init(self): |
| """Subclasses may override this to perform custom initialization. |
| |
| The attributes documented in __init__() will be available when this is |
| called. |
| |
| **Do** use super() to call this base class implementation, in case it |
| gains functionality in the future. |
| """ |
| |
| def __str__(self): |
| """Return a string representation of this drv.""" |
| return "%s[%s](%s)" % ( |
| self._params["control_name"], |
| type(self).__name__, |
| self._mode(), |
| ) |
| |
| def _servod_get(self, control): |
| """Get the value of the given control with proper prefix.""" |
| if not self._servod: |
| raise HwDriverError("No valid servod instance.") |
| return self._servod.get(control) |
| |
| def _servod_set(self, control, value): |
| """Set the value of the given control with proper prefix.""" |
| if not self._servod: |
| raise HwDriverError("No valid servod instance.") |
| return self._servod.set(control, value) |
| |
| def __repr__(self): |
| """Return same as __str__()""" |
| return str(self) |
| |
| def _is_set(self): |
| """Whether the |self| is a driver instance for 'set'. |
| |
| Returns: |
| True if 'cmd' is 'set' |
| """ |
| return self._mode() == "set" |
| |
| def _is_get(self): |
| """Whether the |self| is a driver instance for 'get'. |
| |
| Returns: |
| True if 'cmd' is 'get' |
| """ |
| return self._mode() == "get" |
| |
| def _mode(self): |
| """Mode of this drv, either 'set' or 'get'. |
| |
| Returns: |
| contents of self._params['cmd'] |
| """ |
| return self._params["cmd"] |
| |
| def set_complement(self, complement_drv): |
| """set |complement_drv| to this drv's complement and vice-versa |
| |
| Args: |
| complement_drv: this control drv's complement |
| |
| Raises: |
| HwDriverError: if |complement_drv| and |self| have the same mode (set,get) |
| HwDriverError: if |complement_drv| or |self| already have a complement |
| """ |
| # pylint: disable=protected-access |
| # Access required as the goal is to link both the classes. |
| if complement_drv._mode() == self._mode(): |
| raise HwDriverError( |
| "Cannot set %r as the complement of %r." % (complement_drv, self) |
| ) |
| |
| # Note: this policy can be changed if necessary, but for now it seems safer |
| # to enforce that complements are set once. There is no known reason so far |
| # to have a complement drv dynamically change at runtime after the first |
| # time it is set. If this is changed in the future, consider that we do |
| # not want to have open loops i.e. if a complement is cleaned up, it needs |
| # to be cleaned up from both sides. |
| for d in [self, complement_drv]: |
| if d._complement is not None: |
| # |d._complement| is a weakref. Casting it to a string before passing it |
| # to %r ensures it prints the actual object class and not that it's |
| # a weakref. |
| raise HwDriverError( |
| "drv %r already has a complement: %r" % (d, str(d._complement)) |
| ) |
| |
| self._complement = weakref.proxy(complement_drv) |
| complement_drv._complement = weakref.proxy(self) |
| # |self._complement| is a weakref. Casting it to a string before passing it |
| # to %r ensures it prints the actual object class and not that it's a |
| # weakref. |
| self._logger.debug( |
| "Set drvs %r and %r as each others complements.", |
| self, |
| str(self._complement), |
| ) |
| |
| def _get_complement(self): |
| """Return the complement drv. |
| |
| Returns: |
| self._complement i.e. the complement drv. Can be None if not set yet. |
| """ |
| return self._complement |
| |
| def _check_input(self, value): |
| """Check whether |value| is a valid input. |
| |
| If |self._choices| is defined, then |value| has to match. |
| |
| Args: |
| value: value to check |
| |
| Raises: |
| HwDriverError: if |self._choices| is defined and value does not match. |
| """ |
| if self._choices and self._choices.match(str(value)) is None: |
| raise HwDriverError( |
| "%r not a valid input choice (%r)" % (value, self._choices.pattern) |
| ) |
| |
| def set(self, logical_value): |
| """Set hardware control to a particular value. |
| |
| This function first check that |logical_value| is a valid choice before |
| passing it down. Then, it tries to set the value: |
| 1. If `subtype` is specified in the self._params, run _Set_[subtype]() |
| 2. Otherwise, run _set() |
| |
| DO NOT OVERRIDE THIS METHOD in the subclass. Instead, override _set(). |
| |
| Args: |
| logical_value: Integer value to write to hardware. |
| |
| Returns: |
| Value from _Set_[subtype]() or _set() |
| |
| Raises: |
| HwDriverError: If unable to locate _Set_[subtype]() method. |
| """ |
| self._logger.debug("logical_value = %s", logical_value) |
| self._check_input(logical_value) |
| if "subtype" in self._params: |
| if self._params["subtype"] == "": |
| raise HwDriverError( |
| "Subtype should not be empty. \ |
| Please remove subtype param if it is unnecessary." |
| ) |
| fn_name = "_Set_%s" % self._params["subtype"] |
| if hasattr(self, fn_name): |
| return getattr(self, fn_name)(logical_value) |
| self._logger.warning( |
| "Cannot find set subtype function %s. \ |
| Fall back to general set function." |
| % (fn_name,) |
| ) |
| return self._set(logical_value) |
| |
| def _set(self, logical_value): |
| """Set the control to |logical_value|. |
| |
| Args: |
| logical_value: Integer value to write to hardware. |
| |
| Returns: |
| Value from subclass method. |
| |
| Raises: |
| NotImplementedError: _set() unimplemented in the subclass. |
| """ |
| raise NotImplementedError("_set should be implemented in subclass.") |
| |
| def get(self): |
| """Get hardware control to a particular value. |
| |
| This function tries to get the value: |
| 1. If `subtype` is specified in the self._params, run _Get_[subtype]() |
| 2. Otherwise, run _get() |
| |
| DO NOT OVERRIDE THIS METHOD in the subclass. Instead, override _get(). |
| |
| Returns: |
| Value from _Get_[subtype]() or _get() |
| |
| Raises: |
| HwDriverError: If unable to locate _Get_[subtype]() method. |
| """ |
| if "subtype" in self._params: |
| if self._params["subtype"] == "": |
| raise HwDriverError( |
| "Subtype should not be empty. \ |
| Please remove subtype param if it is unnecessary." |
| ) |
| fn_name = "_Get_%s" % self._params["subtype"] |
| if hasattr(self, fn_name): |
| return getattr(self, fn_name)() |
| self._logger.warning( |
| "Cannot find get subtype function %s. \ |
| Fall back to general get function." |
| % fn_name |
| ) |
| return self._get() |
| |
| def _get(self): |
| """Get the control. |
| |
| Returns: |
| Value from subclass method. |
| |
| Raises: |
| NotImplementedError: _get() unimplemented in the subclass. |
| """ |
| raise NotImplementedError("_get method should be implemented in subclass.") |
| |
| def _create_logical_value(self, hw_value): |
| """Create logical value using mask & offset. |
| |
| logical_value = (hw_value & mask) >> offset |
| |
| In MOST cases, subclass drivers should use this for data coming back from |
| get method. |
| |
| Args: |
| hw_value: value to convert to logical value |
| |
| Returns: |
| integer in logical representation |
| """ |
| (offset, mask) = self._get_offset_mask() |
| if offset is not None: |
| fmt_value = (hw_value & mask) >> offset |
| else: |
| fmt_value = hw_value |
| return fmt_value |
| |
| def _create_hw_value(self, logical_value): |
| """Create hardware value using mask & offset. |
| |
| hw_value = (logical_value << offset) & mask |
| |
| In MOST cases, subclass drivers should use this for data going to value |
| argument in set method. |
| |
| Args: |
| logical_value: value to convert to hardware value |
| |
| Returns: |
| integer in hardware representation |
| |
| Raises: |
| HwDriverError: when logical_value would assert bits outside the mask |
| """ |
| (offset, mask) = self._get_offset_mask() |
| if offset is not None: |
| hw_value = logical_value << offset |
| if hw_value != (hw_value & mask): |
| raise HwDriverError("format value asserts bits outside mask") |
| else: |
| hw_value = logical_value |
| return hw_value |
| |
| def _get_offset_mask(self): |
| """Retrieve offset and mask. |
| |
| Subclasses should use to obtain &| create these ints from param dict |
| |
| Returns: |
| tuple (offset, mask) where: |
| offset: integer offset or None if not defined in param dict |
| mask: integer mask or None if offset not defined in param dict |
| For example if width=2 and offset=4 then mask=0x30 |
| |
| """ |
| if "offset" in self._params: |
| offset = int(self._params["offset"]) |
| if "width" not in self._params: |
| mask = 1 << offset |
| else: |
| mask = (pow(2, int(self._params["width"])) - 1) << offset |
| else: |
| offset = None |
| mask = None |
| return (offset, mask) |