blob: 3a62fbe91f2e4b71a85095683c3d62511e4233cb [file] [log] [blame]
/* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright 2020 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.
*/
#include <zephyr.h>
#include <device.h>
#include <devicetree.h>
#include <drivers/adc.h>
#include <drivers/gpio.h>
#include <shell/shell.h>
#include <sys/printk.h>
#include "io.h"
#include "sysmon.h"
/**
* Define the interval for the sysmon timer. Every SYSMON_INTERVAL, the
* sysmon task will read the next ADC input in the sequence.
*/
#define SYSMON_INTERVAL K_MSEC(100)
/**
* @brief Input selection for the analog switches.
*/
enum sysmon_sel {
SYSMON_SEL_SOM, /**< Select the FPGA modules voltage rails. */
SYSMON_SEL_REGULATORS /**< Select the regulator voltage rails. */
};
struct adc_hdl {
char *device_name;
const struct device *dev;
struct adc_channel_cfg channel_config;
};
/** @brief The STM32 ADC resolution is fixed at 12 bits. */
#define STM_ADC_RESOLUTION 12
/**
* @brief Declare the ADCs this module uses and the configuration.
*
* Note that this module does not use ADC2.
* 1. All of the analog signals are connected to ADC3 inputs, or inputs that
* either ADC1 or ADC2 can use.
* 2. ADC1 and ADC2 share an ISR. When both are enabled, Zephyr trips an
* assertion.
*/
static struct adc_hdl adc_list[] = {
{
.device_name = DT_LABEL(DT_NODELABEL(adc1)),
.dev = NULL,
.channel_config = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_DEFAULT,
.channel_id = 15,
},
},
{
.device_name = DT_LABEL(DT_NODELABEL(adc3)),
.dev = NULL,
.channel_config = {
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_DEFAULT,
.channel_id = 4,
},
}
};
/**
* @brief Storage for the ADC readings for each monitored voltage.
*/
static uint16_t adc_reading[SYSMON_VMON_NUM_INPUTS];
/**
* @brief Mutex protecting access to adc_reading.
*/
static K_MUTEX_DEFINE(adc_reading_mutex);
/**
* @brief Define a struct with details for each of the monitored voltages.
*
* This struct is indexed by the enum for the monitored voltage. It
* provides the setting for the analog switches, which ADC to read (of
* the ADCs in adc_list[]), the channel to read, a scaling factor when
* converting from ADC reading to floating-point, and a name for display
* purposes.
* The scale_factor is 1.0 for most of the voltages, but some of them are
* scaled in hardware to a 3.3V input range. For those voltages, the
* scale_factor is written as `full_range/scaled_range` and we let the
* do constant folding.
*/
struct sysmon_adc_input {
enum sysmon_sel sel; /**< Which way to flip the analog switches. */
int adc_num; /**< Index into adc_list[] for this signal. */
int channel; /**< ADC channel to read. */
float scale_factor; /**< Scaling factor. */
const char *name; /**< Human-readable name of the signal. */
};
/**
* @brief Map each monitored voltage to the correct `sysmon_adc_input` values.
*/
static struct sysmon_adc_input scan_table[SYSMON_VMON_NUM_INPUTS] = {
/* Note that SYSMON_VMON_12V and the CMON inputs are not multiplexed,
* so the `sel` value doesn't actually matter.
*/
[SYSMON_VMON_12V] = { .sel = SYSMON_SEL_REGULATORS,
.adc_num = 0,
.channel = 10,
.scale_factor = 12.0 / 3.0,
.name = "12V" },
[SYSMON_CMON_SOM] = { .sel = SYSMON_SEL_REGULATORS,
.adc_num = 0,
.channel = 11,
.scale_factor = 1.0,
.name = "CMON_SOM" },
[SYSMON_CMON_BOARD] = { .sel = SYSMON_SEL_REGULATORS,
.adc_num = 0,
.channel = 12,
.scale_factor = 1.0,
"CMON_BOARD" },
[SYSMON_VMON_PP1200] = { .sel = SYSMON_SEL_REGULATORS,
.adc_num = 0,
.channel = 13,
.scale_factor = 1.0,
.name = "PP1200" },
[SYSMON_CMON_PP1200] = { .sel = SYSMON_SEL_REGULATORS,
.adc_num = 0,
.channel = 14,
.scale_factor = 1.0,
.name = "CMON_PP1200" },
[SYSMON_CMON_PP3300] = { .sel = SYSMON_SEL_REGULATORS,
.adc_num = 0,
.channel = 15,
.scale_factor = 1.0,
.name = "CMON_PP3300" },
[SYSMON_VMON_9V] = { .sel = SYSMON_SEL_REGULATORS,
.adc_num = 1,
.channel = 5,
.scale_factor = 9.0 / 2.25,
.name = "VMON_9V" },
[SYSMON_VMON_5V] = { .sel = SYSMON_SEL_REGULATORS,
.adc_num = 1,
.channel = 6,
.scale_factor = 5.0 / 1.25,
.name = "VMON_5V" },
[SYSMON_VMON_3V3] = { .sel = SYSMON_SEL_REGULATORS,
.adc_num = 1,
.channel = 7,
.scale_factor = 3.3 / 2.2,
.name = "VMON_3V3" },
[SYSMON_VMON_PP1800] = { .sel = SYSMON_SEL_REGULATORS,
.adc_num = 1,
.channel = 8,
.scale_factor = 1.0,
.name = "PP1800" },
[SYSMON_SOM_VMON] = { .sel = SYSMON_SEL_SOM,
.adc_num = 0,
.channel = 13,
.scale_factor = 1.0,
.name = "SOM_VMON" },
[SYSMON_SOM_VMON_1V2] = { .sel = SYSMON_SEL_SOM,
.adc_num = 0,
.channel = 14,
.scale_factor = 1.0,
.name = "SOM_VMON_1V2" },
[SYSMON_SOM_VMON_MGT] = { .sel = SYSMON_SEL_SOM,
.adc_num = 0,
.channel = 15,
.scale_factor = 1.0,
.name = "SOM_VMON_MGT" },
[SYSMON_SOM_VMON_1V8A] = { .sel = SYSMON_SEL_SOM,
.adc_num = 1,
.channel = 4,
.scale_factor = 1.0,
.name = "SOM_VMON_1V8A" },
[SYSMON_SOM_VMON_C8] = { .sel = SYSMON_SEL_SOM,
.adc_num = 1,
.channel = 5,
.scale_factor = 1.0,
.name = "SOM_VMON_C8" },
[SYSMON_SOM_VREF_CS1] = { .sel = SYSMON_SEL_SOM,
.adc_num = 1,
.channel = 6,
.scale_factor = 1.0,
.name = "VREF_CS1" },
[SYSMON_SOM_VREF_CS2] = { .sel = SYSMON_SEL_SOM,
.adc_num = 1,
.channel = 7,
.scale_factor = 1.0,
.name = "VREF_CS2" },
[SYSMON_SOM_VREF_CS3] = { .sel = SYSMON_SEL_SOM,
.adc_num = 1,
.channel = 8,
.scale_factor = 1.0,
.name = "VREF_CS3" },
};
/**
* Define priotity for the sysmon task
*/
#define SYSMON_PRIORITY 5
/**
* Define the size of the stack for the sysmon task.
*/
#define SYSMON_STACK_SIZE 500
/**
* Zephyr macro to allocate the stack area.
*/
K_THREAD_STACK_DEFINE(sysmon_stack_area, SYSMON_STACK_SIZE);
/**
* Internal data structure for tracking the sysmon task.
*/
static struct k_thread sysmon_thread_data;
/**
* Binary semaphore to let the sysmon task wait for the next timer tick
*
* The timer handler will signal this semaphone, and the the sysmon task
* will wait on it. Every time the timer event handles, the sysmon task
* will get unblocked and sample another ADC input.
*/
K_SEM_DEFINE(sysmon_sem, 0, 1);
/**
* Timer handler to signal sysmon to sample another ADC input
*
* Note that we don't care if the semaphone has already been signaled; it
* is a binary semaphore, so internally the kernel will just ignore the
* extra give.
*/
void sysmon_timer_handler(struct k_timer *dummy)
{
ARG_UNUSED(dummy);
k_sem_give(&sysmon_sem);
}
K_TIMER_DEFINE(sysmon_timer, sysmon_timer_handler, NULL);
/**
* @brief Store the ADC value for a particular input
*
* This internal function provides a way for sysmon to store the ADC readings
* without worrying about how the tables are organized.
*
* @param input Which of the monitored inputs is being set.
* @param reading The ADC reading to store.
*/
static void sysmon_set_val(enum sysmon_input input, uint16_t reading)
{
__ASSERT(input < SYSMON_VMON_NUM_INPUTS && (int)input >= 0,
"Invalid enum passed to sysmon_set");
k_mutex_lock(&adc_reading_mutex, K_FOREVER);
adc_reading[input] = reading;
k_mutex_unlock(&adc_reading_mutex);
}
/**
* @brief Read the ADCs sequentially in a loop forever.
*
* The sysmon task will step through the list of inputs in an infinite loop.
* For each input, it will set the analog switches to the correct setting,
* read the channel from the ADC, and store the reading in the adc_reading
* array. When it reaches the end of the list, it starts over at the beginning.
* Between each reading, the thread sleeps to give other threads a chance
* to run.
*
* @param unused1 Required by the Zephyr thread API. Unused.
* @param unused2 Required by the Zephyr thread API. Unused.
* @param unused3 Required by the Zephyr thread API. Unused.
*/
static void sysmon_task(void *unused1, void *unused2, void *unused3)
{
ARG_UNUSED(unused1);
ARG_UNUSED(unused2);
ARG_UNUSED(unused3);
uint16_t sample_buffer;
int idx = SYSMON_VMON_NUM_INPUTS;
int ret;
k_thread_name_set(NULL, "sysmon_task");
/* Step through the scan_table one entry at a time, resetting to 0
* when we get to the end. Loop forever.
*/
for (;;) {
/*
* The delay and "next channel" logic is at the start of
* this infinite loop so that if we have an error, we
* can just `continue`, instead of having nested logic
* that goes to the next statement if the previous one
* succeeds.
*/
/*
* Wait for the timer to signal the semaphore, so we don't
* starve the system.
*/
k_sem_take(&sysmon_sem, K_FOREVER);
/* Move to the next channel */
idx++;
if (idx >= (int)SYSMON_VMON_NUM_INPUTS) {
idx = 0;
}
/* Read the ADCs one channel at a time. The Zephyr driver
* for the STM32 ADC doesn't seem to like a sequence of more
* than one ADC channel, even though the hardware supports it.
*/
const struct adc_sequence seq = {
.channels = BIT(scan_table[idx].channel),
.buffer = &sample_buffer,
.buffer_size = sizeof(sample_buffer),
.resolution = STM_ADC_RESOLUTION,
};
ret = gpio_set_level(
sysmon_sel,
(scan_table[idx].sel == SYSMON_SEL_REGULATORS) ? 1 : 0);
if (ret < 0) {
printk("gpio_set_level(sysmon_sel) failed, ret = %d\n",
ret);
continue;
}
if (adc_read(adc_list[scan_table[idx].adc_num].dev, &seq) < 0) {
continue;
}
sysmon_set_val((enum sysmon_input)idx, sample_buffer);
}
}
float sysmon_get_val(enum sysmon_input input)
{
__ASSERT((int)input < (int)SYSMON_VMON_NUM_INPUTS && (int)input >= 0,
"Invalid enum passed to sysmon_get");
/* Get the reading inside a mutex. */
k_mutex_lock(&adc_reading_mutex, K_FOREVER);
uint16_t reading = adc_reading[input];
k_mutex_unlock(&adc_reading_mutex);
/* Convert ADC reading to 3.3V. */
float val = (float)reading;
/* This division is by a power of two, so it will only adjust the
* floating-point exponent; we won't lose precision in the mantissa.
*/
val /= (float)(1 << STM_ADC_RESOLUTION);
val *= 3.3;
val *= scan_table[input].scale_factor;
return val;
}
/** @brief Initialize the system monitor
*
* sysmon is responsible for reading several ADC channels to monitor
* the supply current and voltages. There are more voltages to monitor
* than ADC input pins, so there are are analog switches to select which
* sets of voltages is connected to the ADC inputs. sysmon needs to
* initialize a GPIO to control the analog switches in addition to
* intializing the ADCs to sample inputs.
*/
static int sysmon_init(const struct device *ptr)
{
ARG_UNUSED(ptr);
int ret = 0;
/* Configure the ADCs. */
for (int idx = 0; idx < ARRAY_SIZE(adc_list); ++idx) {
adc_list[idx].dev =
device_get_binding(adc_list[idx].device_name);
if (adc_list[idx].dev == NULL) {
printk("Cannot get device %s\n",
adc_list[idx].device_name);
return -ENODEV;
}
ret = adc_channel_setup(adc_list[idx].dev,
&adc_list[idx].channel_config);
if (ret < 0) {
printk("adc_channel_setup failed, ret = %d\n", ret);
return ret;
}
}
/* Create the sysmon task and start it. */
k_thread_create(&sysmon_thread_data, sysmon_stack_area,
K_THREAD_STACK_SIZEOF(sysmon_stack_area), sysmon_task,
NULL, NULL, NULL, SYSMON_PRIORITY, 0, K_NO_WAIT);
/* Start the periodic timer for sysmon */
k_timer_start(&sysmon_timer, SYSMON_INTERVAL, SYSMON_INTERVAL);
return ret;
}
SYS_INIT(sysmon_init, APPLICATION, 50);
/**
* @brief Display all of the monitored values in a friendly format
*
* @param shell Our shell data struct; needed for some shell APIs.
* @param argc Count of arguments passed to this shell command. Unused.
* @param argv Arguments for this shell command. Unused.
*/
static int cmd_sysmon(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
for (int idx = 0; idx < (int)SYSMON_VMON_NUM_INPUTS; ++idx) {
shell_fprintf(shell, SHELL_VT100_COLOR_DEFAULT, "%s: %f\n",
scan_table[idx].name,
sysmon_get_val((enum sysmon_input)idx));
}
return 0;
}
SHELL_CMD_REGISTER(sysmon, NULL, "show the SYSMON values", cmd_sysmon);