blob: 2d52b71ed6957a286a89e5ace192240751f7b291 [file] [log] [blame]
#!/usr/bin/env python2
# Copyright 2011 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.
"""Helper module to generate system control files for INA adcs."""
import argparse
import copy
import glob
import imp
import json
import os
import re
import sys
import time
from sweetberry_preprocessor import SweetberryPreprocessor
from servo_config_generator import ServoConfigFileGenerator
from servo_config_generator import ServoControlGenerator
class INAConfigGeneratorError(Exception):
"""Error class for INA control generation errors."""
pass
class INAConfigGenerator(object):
"""Base class for any INA Configuration Generator.
Shared base class that handles file name logic.
Attributes:
_configs_to_generate: a list of configurations that this template produces.
"""
def __init__(self, module_name, ina_pkg):
"""Init all INA Configuration Generators.
This sets up the common logic of deciding how many configurations to produce
based on a given template.
Args:
module_name: name of template module
ina_pkg: template loaded as a module
"""
self._configs_to_generate = []
if hasattr(ina_pkg, 'revs'):
# modules need to be named board_[anything].py
board = re.sub(r'_.*', '', module_name)
for rev in ina_pkg.revs:
try:
self._configs_to_generate.append('%s_rev_%d' % (board, int(rev)))
except ValueError:
raise INAConfigGeneratorError('Rev: %s has to be an integer.'
% str(rev))
else:
# if rev doesn't exist, then it's an old-style INA file, in which case
# this script produces just one control named the same as the module.
self._configs_to_generate.append(module_name)
def ExportConfig(self, outdir):
"""Export the configuration(s) of a template to outdir.
This is required for each Generator class to implement.
"""
raise NotImplementedError()
class PowerlogINAConfigGenerator(INAConfigGenerator):
"""Generator to make sweetberry configurations given a template.
Attributes:
_board_content = content of string for .board Sweetberry file.
_scenario_content = content of string for .scenario Sweetberry file.
"""
def __init__(self, module_name, ina_pkg):
"""Setup Generator by generating file contents as string.
Args:
module_name: name of the template module
ina_pkg: template loaded as a module
"""
super(PowerlogINAConfigGenerator, self).__init__(module_name, ina_pkg)
self._board_content, self._scenario_content = self.DumpADCs(ina_pkg.inas)
def DumpADCs(self, adcs):
"""Dump json formatted INA231 configurations for powerlog configuration.
This uses the same adcs template formate as servod (for compatability)
but sweetberry configuration only needs slv, name, sense, and is_calib.
Args:
adcs: array of adc elements. Each array element is a tuple consisting of:
drvname: string name of adc driver to enumerate to control the adc
slv: string formatted '0xAA:B': AA is i2c slv addr and B is i2c port
name: string name of the power rail
nom: float of nominal voltage of power rail
sense: float of sense resitor size in ohms
mux: string name of bank on sweetberry these ADC's live on: j2, j3, j4
is_calib: boolean to indicate if calibration is possible for this rail
if false, no config will be exported
The adcs list above is in order, meaning this function looks for name at
adc[2], where adc is the tuple for a particular adc.
Returns:
Tuple of (board_content, scenario_content):
board_content: json list of dictionaries describing INAs used
scenario_content: json list of INA names in board_content
"""
adc_list = []
rails = []
for (drvname, slv, name, nom, sense, mux, is_calib) in adcs:
if is_calib:
addr, port = [int(entry, 0) for entry in slv.split(':')]
adc_list.append(' %s' % json.dumps({'name': name,
'rs': float(sense),
'sweetberry': 'A',
'addr': addr,
'port': port}))
rails.append(name)
adc_lines = ',\n'.join(adc_list)
return ('[\n%s\n]' % adc_lines,
json.dumps(rails, indent=2))
def ExportConfig(self, outdir):
"""Write the configuration files in the outdir.
Dump the Sweetberry Configuration(s) for this generator.
Args:
outdir: Directory to place the configuration files into.
"""
for outfile in self._configs_to_generate:
board_outpath = os.path.join(outdir, '%s.board' % outfile)
scenario_outpath = os.path.join(outdir, '%s.scenario' % outfile)
with open(scenario_outpath, 'w') as f:
f.write(self._scenario_content)
with open(board_outpath, 'w') as f:
f.write(self._board_content)
class ServoINAConfigGenerator(INAConfigGenerator):
"""Generator to make servod configurations given a template.
Attributes:
_servo_drv_dir = servo directory to check for ina driver.
_outfile_gen = servo config xml generator.
"""
def __init__(self, module_name, ina_pkg, servo_data_dir, servo_drv_dir=None):
"""Setup Generator by preparing an xml generator to output entire config.
Args:
module_name: name of the template module
ina_pkg: template loaded as a module
servo_data_dir: servo data directory to include configs
servo_drv_dir: servo drv directory to check drv availability
"""
super(ServoINAConfigGenerator, self).__init__(module_name, ina_pkg)
if not servo_drv_dir:
servo_drv_dir = os.path.join(servo_data_dir, '..', 'drv')
self._servo_drv_dir = servo_drv_dir
ina2xx_drv_cfg = os.path.join(servo_data_dir, 'ina2xx.xml')
powertools_drv_cfg = os.path.join(servo_data_dir, 'power_tools.xml')
if hasattr(ina_pkg, 'interface'):
interface = ina_pkg.interface
if type(interface) != int:
raise INAConfigGeneratorError('Invalid interface %r, should be int.'
% interface)
else:
interface = 2 # default I2C interface
comments = 'Autogenerated on %s' % time.asctime()
includes = []
body = ''
if os.path.isfile(powertools_drv_cfg):
includes.append(os.path.basename(powertools_drv_cfg))
if os.path.isfile(ina2xx_drv_cfg):
includes.append(os.path.basename(ina2xx_drv_cfg))
if hasattr(ina_pkg, 'inline'):
inline = ina_pkg.inline
else:
inline = ''
ctrl_gens = self.DumpADCs(ina_pkg.inas, interface)
self._outfile_gen = ServoConfigFileGenerator(ctrl_generators=ctrl_gens,
includes=includes,
inline=inline,
intro_comments=comments)
def DumpADCs(self, adcs, interface=2):
"""Dump XML formatted INAxxx adcs for servod.
Args:
adcs: array of adc elements. Each array element is a tuple consisting of:
drvname: string name of adc driver to enumerate to control the adc.
slv: int representing the i2c slave address.
optional channel/port if ADC (INA3221 only) has multiple channels
or adc is on a different i2c port (sweetberry only). For example,
"0x40" : address 0x40 ... no channel/port
"0x40:1" : address 0x40, channel/port 1
name: string name of the power rail
nom: float of nominal voltage of power rail.
sense: float of sense resitor size in ohms
mux: string name of i2c mux leg these ADC's live on
is_calib: boolean to indicate if calibration is possible for this rail
interface: interface index to handle low-level communication.
The adcs list above is in order, meaning this function looks for name at
adc[2], where adc is the tuple for a particular adc.
Returns:
string (large) of XML for the system config of these ADCs to eventually be
parsed by servod daemon ( servo/system_config.py )
"""
control_generators = []
for (drvname, slv, name, nom, sense, mux, is_calib) in adcs:
drvpath = os.path.join(self._servo_drv_dir, drvname + '.py')
if not os.path.isfile(drvpath):
raise INAConfigGeneratorError('Unable to locate driver for %s at %s'
% (drvname, drvpath))
ina_type = 'ina231' if drvname == 'sweetberry' else drvname
params_base = {
'type' : 'get',
'drv' : drvname,
'interface' : interface,
'slv' : slv,
'mux' : mux,
'rsense' : sense,
}
# Must match REG_IDX.keys() in servo/drv/ina2xx.py
regs = ['cfg', 'shv', 'busv', 'pwr', 'cur', 'cal']
if ina_type == 'ina231':
regs.extend(['msken', 'alrt'])
elif ina_type == 'ina3221':
regs = ['cfg', 'shv', 'busv', 'msken']
if ina_type == 'ina3221':
(slv, chan_id) = slv.split(':')
params_base['slv'] = slv
params_base['channel'] = chan_id
if drvname == 'sweetberry':
(slv, port) = slv.split(':')
params_base['slv'] = slv
params_base['port'] = port
mv_ctrl_docstring = ('Bus Voltage of %s rail in millivolts on i2c_mux:%s'
% (name, params_base['mux']))
mv_ctrl_params = {'subtype' : 'millivolts',
'nom' : nom}
mv_ctrl_params.update(params_base)
control_generators.append(ServoControlGenerator(name + '_mv',
mv_ctrl_docstring,
mv_ctrl_params))
shuntmv_ctrl_docstring = ('Shunt Voltage of %s rail in millivolts '
'on i2c_mux:%s' % (name, params_base['mux']))
shuntmv_ctrl_params = {'subtype' : 'shuntmv',
'nom' : nom}
shuntmv_ctrl_params.update(params_base)
control_generators.append(ServoControlGenerator(name + '_shuntmv',
shuntmv_ctrl_docstring,
shuntmv_ctrl_params))
# in some instances we may not know sense resistor size ( re-work ) or
# other custom factors may not allow for calibration and those reliable
# readings on the current and power registers. This boolean determines
# which controls should be enumerated based on rails input specification
if is_calib:
ma_ctrl_docstring = ('Current of %s rail in milliamps '
'on i2c_mux:%s' % (name, params_base['mux']))
ma_ctrl_params = {'subtype' : 'milliamps'}
ma_ctrl_params.update(params_base)
control_generators.append(ServoControlGenerator(name + '_ma',
ma_ctrl_docstring,
ma_ctrl_params))
mw_ctrl_docstring = ('Power of %s rail in milliwatts '
'on i2c_mux:%s' % (name, params_base['mux']))
mw_ctrl_params = {'subtype' : 'milliwatts'}
mw_ctrl_params.update(params_base)
control_generators.append(ServoControlGenerator(name + '_mw',
mw_ctrl_docstring,
mw_ctrl_params))
for reg in regs:
reg_ctrl_docstring = ('Raw register value of %s on i2c_mux:%s'
% (reg, params_base['mux']))
reg_ctrl_name = '%s_%s_reg' % (name, reg)
reg_ctrl_params_get = {'cmd' : 'get',
'subtype' : 'readreg',
'fmt' : 'hex',
'reg' : reg}
reg_ctrl_params_get.update(params_base)
# rsense and type are in params_base, but not needed here
del reg_ctrl_params_get['rsense']
del reg_ctrl_params_get['type']
reg_ctrl_params_set = None
if reg in ['cfg', 'cal']:
reg_ctrl_params_set = copy.copy(reg_ctrl_params_get)
reg_ctrl_params_set.update({'cmd' : 'set',
'subtype' : 'writereg'})
if reg == 'cal':
reg_ctrl_params_set['map'] = 'calibrate'
if reg == 'cfg':
reg_ctrl_params_set['map'] = '%s_cfg' % ina_type
control_generators.append(ServoControlGenerator(reg_ctrl_name,
reg_ctrl_docstring,
reg_ctrl_params_get,
reg_ctrl_params_set))
return control_generators
def ExportConfig(self, outdir):
"""Write the configuration files in the outdir.
Dump the XML Servo Configuration(s) for this generator.
Args:
outdir: Directory to place the configuration files into.
"""
for outfile in self._configs_to_generate:
outfile_dest = os.path.join(outdir, '%s.xml' % outfile)
self._outfile_gen.WriteToFile(outfile_dest)
def GenerateINAControls(servo_data_dir, servo_drv_dir=None, outdir=None,
export=True, candidates=[]):
"""Attempt to generate INA configurations for all modules.
Generates the configuration for every module found inside
|self._servo_data_dir| during init.
Optionally also provide where to write configurations to, and where to
look for servo drv files if not at known location.
Args:
servo_data_dir: directory where to look for .py files to generate
controls.
servo_drv_dir: directory where servo drivers are. Used to verify
that defined controls have a driver they can use.
If |None|, generator will look for drivers at
servo_data_dir/../drv/
outdir: directory where to dump generated configuration files.
If |None|, config files are dumped into |servo_data_dir|
export: if True config files will be exported to |outdir|
if False it's only a dry-run to detect errors
candidates: list of files in |servo_data_dir| to generate configs for.
if empty, all files in |servo_data_dir| will be considered.
"""
if not outdir:
outdir = servo_data_dir
generators = []
if not candidates:
candidates = os.listdir(servo_data_dir)
for candidate in candidates:
if candidate.endswith('.py'):
module_name = candidate[:-3]
ina_pkg = imp.load_module(module_name,
*imp.find_module(module_name,
[servo_data_dir]))
if not hasattr(ina_pkg, 'inas'):
continue
if hasattr(ina_pkg, 'config_type'):
config_type = ina_pkg.config_type
else:
# If config_type is not defined, it is a servod config
config_type = 'servod'
if config_type not in ['sweetberry', 'servod']:
raise INAConfigGeneratorError('Unknown config type %s' % config_type)
if config_type == 'sweetberry':
#translate inas from pin-style to i2c-addr style config (if applicable)
ina_pkg.inas = SweetberryPreprocessor.Preprocess(ina_pkg.inas)
#also output powerlog config files (.board/.scenario)
generators.append(PowerlogINAConfigGenerator(module_name,
ina_pkg))
#always output Servod configurations
generators.append(ServoINAConfigGenerator(module_name,
ina_pkg,
servo_data_dir,
servo_drv_dir))
if export:
for generator in generators:
generator.ExportConfig(outdir)
def main(cmdline=sys.argv[1:]):
"""cmdline interface to generate &| verify config files.
Note: This is mainly inteded as a development tool to verify a new or
modified powermap before submitting it, and without having to build the full
hdctools.
"""
parser = argparse.ArgumentParser(description='cmdline tool to generate and '
'validate servod power configurations.')
parser.add_argument('--dry-run', default=False, action='store_true',
help='Do not export files, only verify that no errors '
'on config generation occur.')
parser.add_argument('-i', '--input', action='store',
default=os.path.dirname(__file__),
help='file or directory to perform conversions on.')
args = parser.parse_args(cmdline)
if os.path.isdir(args.input):
servo_data_dir = args.input
candidates = glob.glob(os.path.join(servo_data_dir, '*.py'))
if os.path.isfile(args.input):
servo_data_dir = os.path.dirname(args.input)
candidates = [args.input]
#having only the basename is required for load_module to work better
candidates = [os.path.basename(candidate) for candidate in candidates]
#if dry_run is set then we don't want to export.
export = not args.dry_run
for candidate in candidates:
try:
GenerateINAControls(servo_data_dir=servo_data_dir,
export=export,
candidates=[candidate])
msg_prefix = 'Success:'
except Exception as e:
msg_prefix = 'FAILURE: %s' % e.message
print('%s for candidate file %s' % (msg_prefix, candidate))
if __name__ == '__main__':
main()