blob: fc68d561176ee5f08492f1f206edda43c70a93a6 [file]
/* Copyright 2024 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "include/platform.h"
#include "rts5453.h"
#include "smbus_usermode.h"
#include "um_ppm_chardev.h"
/* Some extra functionality that's used by this binary that's not necessarily
* for UCSI (such as getting info about the PD controller, firmware update,
* etc).
*/
struct extra_driver_ops {
int (*get_info)(struct ucsi_pd_driver* driver);
int (*do_firmware_update)(struct ucsi_pd_driver* driver, const char* filepath,
int dry_run);
// Establish communication with SMBUS transport LPMs.
struct ucsi_pd_driver* (*smbus_lpm_open)(struct smbus_driver* smbus,
struct pd_driver_config* config);
};
struct extra_driver_ops rts5453_ops = {
.get_info = rts5453_get_info,
.do_firmware_update = rts5453_do_firmware_update,
.smbus_lpm_open = rts5453_open,
};
// Set up the um_ppm device to start communicating with kernel.
int cdev_prepare_um_ppm(char* um_test_devpath, struct ucsi_pd_driver* pd,
struct smbus_driver* smbus,
struct pd_driver_config* config) {
// Open the kernel um_ppm chardev to establish the PPM communication.
struct um_ppm_cdev* cdev =
um_ppm_cdev_open(um_test_devpath, pd, smbus, config);
if (!cdev) {
ELOG("Failed to initialize PPM chardev. Exit early!");
return -1;
}
// TODO - Register sigterm handler so we know when to exit.
// Mainloop with chardev handling.
um_ppm_cdev_mainloop(cdev);
return 0;
}
static const char* usage_str =
("um_ppm [options]\n"
"\n"
"Userspace PPM implementation and helper functions. Use this to\n"
"develop against and test new devkits.\n"
"\n"
"General options:\n"
"\t-p PD driver config to use. Valid values: ['rts5453']\n"
"\t-b I2C Bus number (/dev/i2c-N) (required)\n"
"\t-g /dev/gpiochip[N] (required)\n"
"\t-l Gpio line for LPM alert (required)\n"
"\t-v Enable verbose logs\n"
"Actions (exclusive):\n"
"\t-f <path> Do firmware update with file at this path\n"
"\t-k <dev path> Attach ucsi_um_kernel driver via this chardev path\n"
"\t-d Demo commands\n");
void usage() { printf(usage_str); }
int main(int argc, char* argv[]) {
int opt;
int fwupdate = 0;
char* fwupdate_file = NULL;
int demo = 0;
int i2c_bus = -1;
int i2c_chip_address = -1;
int gpio_chip = -1;
int gpio_line = -1;
int attach_to_kernel = 0;
char* ucsi_um_kernel_dev = NULL;
char* driver_config_in = NULL;
struct pd_driver_config driver_config;
struct extra_driver_ops* ops;
while ((opt = getopt(argc, argv, ":f:k:dvb:p:g:l:")) != -1) {
switch (opt) {
case 'b':
i2c_bus = strtol(optarg, NULL, 10);
break;
case 'p':
driver_config_in = strdup(optarg);
break;
case 'g':
gpio_chip = strtol(optarg, NULL, 10);
break;
case 'l':
gpio_line = strtol(optarg, NULL, 10);
break;
case 'f':
fwupdate = 1;
fwupdate_file = strdup(optarg);
break;
case 'k':
attach_to_kernel = 1;
ucsi_um_kernel_dev = strdup(optarg);
break;
case 'd':
demo = 1;
break;
case 'v':
platform_set_debug(true);
break;
case ':':
printf("Missing arg for %c\n", optopt);
usage();
return -1;
case '?':
default:
printf("Unknown option: %c\n", optopt);
usage();
return -1;
}
}
struct smbus_driver* smbus = NULL;
struct ucsi_pd_driver* pd_driver = NULL;
if (i2c_bus == -1) {
ELOG("Invalid bus (%d) parameter", i2c_bus);
return -1;
}
if (!driver_config_in) {
ELOG("No driver config given. Defaulting to rts5453.");
driver_config_in = "rts5453";
}
// Get driver config
if (strcmp(driver_config_in, "rts5453") == 0) {
driver_config = rts5453_get_driver_config();
ops = &rts5453_ops;
// Use port-0 for smbus addressing
i2c_chip_address = driver_config.port_address_map[0];
} else {
ELOG("Unsupported PD driver config: %s", driver_config_in);
return -1;
}
// Open usermode smbus.
smbus = smbus_um_open(i2c_bus, i2c_chip_address, gpio_chip, gpio_line);
if (!smbus) {
ELOG("Failed to open smbus");
goto handle_error;
}
// Open PD driver
pd_driver = ops->smbus_lpm_open(smbus, &driver_config);
if (!pd_driver) {
ELOG("Failed to open PD driver.");
goto handle_error;
}
DLOG("RTS5453 is initialized. Now taking desired action...");
if (demo) {
return ops->get_info(pd_driver);
} else if (fwupdate && fwupdate_file) {
return ops->do_firmware_update(pd_driver, fwupdate_file, /*dry_run=*/0);
} else if (attach_to_kernel) {
return cdev_prepare_um_ppm(ucsi_um_kernel_dev, pd_driver, smbus,
&driver_config);
}
return 0;
handle_error:
if (smbus) {
smbus->cleanup(smbus);
free(smbus);
}
if (pd_driver) {
pd_driver->cleanup(pd_driver);
free(pd_driver);
}
return -1;
}