blob: 4baf4795269bd472ef1a04f5c033665be595743c [file] [log] [blame]
/* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright 2021 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/gpio.h>
#include <logging/log.h>
#include <shell/shell.h>
#include <sys/__assert.h>
#include "io.h"
LOG_MODULE_REGISTER(io);
struct gpio_info {
const char *const label;
const char *const dev_name;
const struct device *dev;
gpio_pin_t pin;
int flags;
};
#define GPIO(group, name) \
[name] = { DT_LABEL(DT_PATH(group, name)), \
DT_GPIO_LABEL(DT_PATH(group, name), gpios), NULL, \
DT_GPIO_PIN(DT_PATH(group, name), gpios), \
DT_GPIO_FLAGS(DT_PATH(group, name), gpios) }
static struct gpio_info gpios[] = {
GPIO(leds, power_good_led),
GPIO(sd_mux, sd_mux_sel),
GPIO(sd_mux, sd_mux_en_l),
GPIO(sd_mux, usd_pwr_sel),
GPIO(sd_mux, usd_pwr_en),
GPIO(sd_mux, usd_cd_det),
GPIO(sysmon, sysmon_sel),
GPIO(fpga, pwr_en),
GPIO(fpga, pwr_good),
GPIO(fpga, boot_mode1),
GPIO(fpga, boot_mode0),
GPIO(fpga, por_l_load_l),
GPIO(fpga, fpga_done),
GPIO(misc, tp126),
GPIO(misc, tp125),
GPIO(misc, board_version_2),
GPIO(misc, board_version_1),
GPIO(misc, board_version_0),
GPIO(i2c_switch, exp_reset),
GPIO(i2c_switch, exp_irq),
GPIO(videomux, gp213_it68051p1_ch_sel),
GPIO(videomux, dp1_ps8468_sw),
GPIO(videomux, hdmi1_gp213_ch_sel),
GPIO(videomux, somp1_mode_sel),
GPIO(videomux, gp213_it68051p0_ch_sel),
GPIO(videomux, dp2_ps8468_sw),
GPIO(videomux, hdmi2_gp213_ch_sel),
GPIO(videomux, somp2_mode_sel),
GPIO(videomux, dp1_ps8468_rst_l),
GPIO(videomux, dp2_ps8468_rst_l),
GPIO(videomux, dp1_hdmi_rst_l),
GPIO(videomux, dp2_hdmi_rst_l),
GPIO(videomux, hdmirx_hpd_sel),
GPIO(videomux, hdmi_hpd_sel),
GPIO(videomux, dp_hpd_sel),
GPIO(it68051, it68051_en),
GPIO(it68051, it68051p0_pwr_det),
GPIO(it68051, it68051p1_pwr_det),
GPIO(it68051, it68051_rst_l),
GPIO(it68051, it68051p0_ddc_bp_l),
GPIO(it68051, it68051p1_ddc_bp_l),
GPIO(it68051, gp213_it68051p1_hpd),
GPIO(it68051, gp213_it68051p0_ddc_scl),
GPIO(it68051, gp213_it68051p0_ddc_sda),
GPIO(it68051, stm32_it68051p0_ddc_scl),
GPIO(it68051, stm32_it68051p0_ddc_sda),
GPIO(it68051, gp213_it68051p1_ddc_scl),
GPIO(it68051, gp213_it68051p1_ddc_sda),
GPIO(it68051, stm32_it68051p1_ddc_scl),
GPIO(it68051, stm32_it68051p1_ddc_sda),
GPIO(hotplug, dp1_auxp_det),
GPIO(hotplug, dp1_auxn_det),
GPIO(hotplug, dp2_auxp_det),
GPIO(hotplug, dp2_auxn_det),
GPIO(hotplug, hdmi1_pp3300),
GPIO(hotplug, hdmi2_pp3300),
GPIO(hotplug, dp1_mcu_hpd),
GPIO(hotplug, dp2_mcu_hpd),
GPIO(hotplug, hdmi1_mcu_hpd),
GPIO(hotplug, hdmi2_mcu_hpd),
GPIO(hotplug, dp_in_pwr_en),
GPIO(dp_cfg, dp1_ps8468_mode),
GPIO(dp_cfg, dp1_ps8468_cfg0),
GPIO(dp_cfg, dp1_ps8468_cfg1),
GPIO(dp_cfg, dp1_ps8468_cfg2),
GPIO(dp_cfg, dp1_ps8468_cfg3),
GPIO(dp_cfg, dp1_ps8468_cfg4),
GPIO(dp_cfg, dp1_ps8468_eq0),
GPIO(dp_cfg, dp1_ps8468_eq1),
GPIO(dp_cfg, dp2_ps8468_mode),
GPIO(dp_cfg, dp2_ps8468_cfg0),
GPIO(dp_cfg, dp2_ps8468_cfg1),
GPIO(dp_cfg, dp2_ps8468_cfg2),
GPIO(dp_cfg, dp2_ps8468_cfg3),
GPIO(dp_cfg, dp2_ps8468_cfg4),
GPIO(dp_cfg, dp2_ps8468_eq0),
GPIO(dp_cfg, dp2_ps8468_eq1),
};
/*
* Shell commands for reading and setting GPIOs. See README.md for details.
*
* See also https://docs.zephyrproject.org/latest/reference/shell/index.html#dynamic-commands
*/
#define MAX_CMD_LEN (31u)
/* buffer holding dynamicly created user commands */
static char dynamic_cmd_buffer[ARRAY_SIZE(gpios)][MAX_CMD_LEN + 1];
/* commands counter */
static const uint8_t DYNAMIC_CMD_CNT = ARRAY_SIZE(gpios);
typedef int cmp_t(const void *, const void *);
extern void qsort(void *a, size_t n, size_t es, cmp_t *cmp);
/* function required by qsort */
static int string_cmp(const void *p_a, const void *p_b)
{
return strcmp((const char *)p_a, (const char *)p_b);
}
/**
* @brief Initialize GPIOs and shell commands to set/get
*
* Walk the array of GPIOs and look up each device name (e.g. "GPIOB"). Store
* the `const struct device *` into the struct for that GPIO. It would be
* nice to do this at compile time, but `device_get_binding` is a runtime
* call.
*
* Also copy the GPIO names into another array and sort them alphabetically.
* We'll use that array to set up dynamic shell commands to get and set GPIOs.
*
* As the Zephyr shell sees it, the name ("TP125") of the GPIO in the command
* `io get TP125` is actually a sub-command. We can use Zephyr's dynamic shell
* command to build up a list of sub-commands that we can re-use with multiple
* command handlers. Note that the sub-command list must be in sorted order,
* so we first fill the sub-command array and then sort it with `qsort`.
*
* @note If there were only one command that had all the GPIO names, and the
* GPIO name was the last item, we could use SHELL_SUBCMD_DICT_SET_CREATE,
* which specifies a list of text names and integer values. A dictionary
* command does not offer a way to specify a further sub-command, and the
* entries in the dictionary can only be used with a single command; attempts
* to declare multiple commands with the same list results in duplicate
* symbol errors.
*
* @param ptr Required by the API, but not used
* @retval 0 Success
*/
static int io_init_devices(const struct device *ptr)
{
ARG_UNUSED(ptr);
for (int idx = 0; idx < ARRAY_SIZE(gpios); ++idx) {
/*
* Store the GPIO name in the list that will be used for
* dynamic shell commands.
*/
strncpy(dynamic_cmd_buffer[idx], gpios[idx].label, MAX_CMD_LEN);
/* Look up the device by name. */
gpios[idx].dev = device_get_binding(gpios[idx].dev_name);
if (!gpios[idx].dev) {
LOG_ERR("io_init_devices: "
"device_get_binding(%s) failed!\n",
gpios[idx].dev_name);
continue;
}
/* Configure the GPIO pin. */
int ret = gpio_pin_configure(gpios[idx].dev, gpios[idx].pin,
gpios[idx].flags);
if (ret != 0) {
LOG_ERR("io_init_devices: "
"gpio_pin_configure(%s(*%p), %d(\"%s\"), 0x%x) "
"failed, ret = %d\n",
gpios[idx].dev_name, gpios[idx].dev,
gpios[idx].pin, gpios[idx].label,
gpios[idx].flags, ret);
}
}
/* Zephyr dynamic shell commands require a sorted list. */
qsort(dynamic_cmd_buffer, DYNAMIC_CMD_CNT,
sizeof(dynamic_cmd_buffer[0]), string_cmp);
return 0;
}
SYS_INIT(io_init_devices, APPLICATION, 1);
int gpio_get_level(enum gpio_signal signal)
{
return gpio_pin_get(gpios[signal].dev, gpios[signal].pin);
}
int gpio_set_level(enum gpio_signal signal, int value)
{
return gpio_pin_set(gpios[signal].dev, gpios[signal].pin, value);
}
const struct device *gpio_get_device(enum gpio_signal signal)
{
return gpios[signal].dev;
}
gpio_pin_t gpio_get_pin(enum gpio_signal signal)
{
return gpios[signal].pin;
}
/*
* @brief Look up an enum by text name
*
* Search the GPIO names to find a match.
*
* @note The gpios[] array is *not* sorted, so we have to do a linear search.
* This is a known compromise so that we can use the order as written in
* the code. Any compiled code that needs to refer to specific GPIOs can use
* the enumeration values directly. so this inefficiency only affects shell
* commands.
*
* @param name The text name of the GPIO
* @param pSignal Pointer to where to store the found value
* @retval 0 Found, value is in *pSignal
* @retval -EINVAL Not found
*/
static int gpio_find_by_name(const char *name, enum gpio_signal *pSignal)
{
__ASSERT(pSignal != NULL, "Passed a NULL pointer to gpio_find_by_name");
for (uint8_t idx = 0; idx < ARRAY_SIZE(gpios); idx++) {
if (!strcmp(name, gpios[idx].label)) {
*pSignal = (enum gpio_signal)idx;
return 0;
}
}
return -EINVAL;
}
/*
*
*/
static int cmd_get(const struct shell *shell, size_t argc, char **argv)
{
/*
* Initialize `signal` to something so compiler is happy. If
* `gpio_find_by_name` doesn't find the name, it won't set `signal`,
* but it also returns an error code, so we won't use `signal`
* anyway. But the compiler doesn't know that.
*/
enum gpio_signal signal = power_good_led;
if (argc == 1) {
/* No pin selected, do all */
for (enum gpio_signal signal = 0;
signal < sizeof(gpios) / sizeof(gpios[0]); signal++) {
int alignment;
shell_fprintf(shell, SHELL_VT100_COLOR_DEFAULT,
"(%s.%d)%n", // %s = %d
gpios[signal].dev_name, gpios[signal].pin,
&alignment);
shell_fprintf(shell, SHELL_VT100_COLOR_DEFAULT,
"%*s = %d\n", 40 - alignment,
gpios[signal].label,
gpio_get_level(signal));
}
return 0;
}
if (gpio_find_by_name(argv[1], &signal) != 0) {
shell_error(shell, "%s: unknown parameter: %s", argv[0],
argv[1]);
return -ENOEXEC;
}
int val = gpio_get_level(signal);
shell_fprintf(shell, SHELL_VT100_COLOR_DEFAULT, "%s = %d\n", argv[1],
val);
return 0;
}
/*
*
*/
static int cmd_set(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);
enum gpio_signal signal = power_good_led;
if (gpio_find_by_name(argv[-1], &signal) != 0) {
shell_error(shell, "%s: uknown parameter: %s", argv[-2],
argv[-1]);
return -ENOEXEC;
}
int value = 0;
if (!strcmp(argv[0], "on")) {
value = 1;
} else if (!strcmp(argv[0], "1")) {
value = 1;
} else if (!strcmp(argv[0], "off")) {
value = 0;
} else if (!strcmp(argv[0], "0")) {
value = 0;
} else {
shell_error(shell, "%s: uknown parameter: %s", argv[-2],
argv[0]);
return -ENOEXEC;
}
shell_fprintf(shell, SHELL_VT100_COLOR_DEFAULT, "set %s to %d\n",
argv[-1], value);
int ret = gpio_set_level(signal, value);
if (ret != 0) {
shell_error(shell, "gpio_set_level error %d", ret);
return -ENOEXEC;
}
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(
on_off, SHELL_CMD_ARG(on, NULL, "turn GPIO on", cmd_set, 1, 0),
SHELL_CMD_ARG(off, NULL, "turn GPIO off", cmd_set, 1, 0),
SHELL_CMD_ARG(1, NULL, "turn GPIO on", cmd_set, 1, 0),
SHELL_CMD_ARG(0, NULL, "turn GPIO off", cmd_set, 1, 0),
SHELL_SUBCMD_SET_END /* Array terminated. */
);
/*
* @brief Fill in details for a sub-command on `set`
*
* The Dictionary shell handler calls this function to get the information
* about each sub-command. When we run out of sub-commands, we fill in a
* NULL pointer.
*
* @note This routine fills in the sub-command handler so that every
* sub-command that is a GPIO name has a further "on/off" sub-command.
*
* @param idx Index into the dynamic command array to get
*/
static void dynamic_cmd_set(size_t idx, struct shell_static_entry *entry)
{
if (idx < DYNAMIC_CMD_CNT) {
entry->syntax = dynamic_cmd_buffer[idx];
entry->handler = NULL;
entry->subcmd = &on_off;
entry->help = NULL;
} else {
entry->syntax = NULL;
}
}
SHELL_DYNAMIC_CMD_CREATE(set_io, dynamic_cmd_set);
/*
* @brief Fill in details for a sub-command on `get`
*
* The Dictionary shell handler calls this function to get the information
* about each sub-command. When we run out of sub-commands, we fill in a
* NULL pointer.
*
* @param idx Index into the dynamic command array to get
*/
static void dynamic_cmd_get(size_t idx, struct shell_static_entry *entry)
{
if (idx < DYNAMIC_CMD_CNT) {
entry->syntax = dynamic_cmd_buffer[idx];
entry->handler = NULL;
entry->subcmd = NULL;
entry->help = NULL;
} else {
entry->syntax = NULL;
}
}
SHELL_DYNAMIC_CMD_CREATE(get_io, dynamic_cmd_get);
SHELL_STATIC_SUBCMD_SET_CREATE(io_cmds,
SHELL_CMD(get, &get_io, "get IO value", cmd_get),
SHELL_CMD(set, &set_io, "set IO value", NULL),
SHELL_SUBCMD_SET_END /* Array terminated. */
);
SHELL_CMD_REGISTER(io, &io_cmds, "Control IO pins", NULL);