u-boot/drivers/misc/gsc.c
Tom Rini 0478dac62a kbuild: Remove uncmd_spl logic
At this point in the conversion there should be no need to have logic to
disable some symbol during the SPL build as all symbols should have an
SPL counterpart.

The main real changes done here are that we now must make proper use of
CONFIG_IS_ENABLED(DM_SERIAL) rather than many of the odd tricks we
developed prior to CONFIG_IS_ENABLED() being available.

Signed-off-by: Tom Rini <trini@konsulko.com>
2022-12-23 10:15:13 -05:00

633 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2022 Gateworks Corporation
*/
#include <command.h>
#include <gsc.h>
#include <i2c.h>
#include <rtc.h>
#include <asm/unaligned.h>
#include <linux/delay.h>
#include <dm/device.h>
#include <dm/device-internal.h>
#include <dm/ofnode.h>
#include <dm/read.h>
#define GSC_BUSNO 0
#define GSC_SC_ADDR 0x20
#define GSC_HWMON_ADDR 0x29
#define GSC_RTC_ADDR 0x68
/* System Controller registers */
enum {
GSC_SC_CTRL0 = 0,
GSC_SC_CTRL1 = 1,
GSC_SC_TIME = 2,
GSC_SC_TIME_ADD = 6,
GSC_SC_STATUS = 10,
GSC_SC_FWCRC = 12,
GSC_SC_FWVER = 14,
GSC_SC_WP = 15,
GSC_SC_RST_CAUSE = 16,
GSC_SC_THERM_PROTECT = 19,
};
/* System Controller Control1 bits */
enum {
GSC_SC_CTRL1_SLEEP_EN = 0, /* 1 = enable sleep */
GSC_SC_CTRL1_SLEEP_ACTIVATE = 1, /* 1 = activate sleep */
GSC_SC_CTRL1_SLEEP_ADD = 2, /* 1 = latch and add sleep time */
GSC_SC_CTRL1_SLEEP_NOWAKEPB = 3, /* 1 = do not wake on sleep on button press */
GSC_SC_CTRL1_WDTIME = 4, /* 1 = 60s timeout, 0 = 30s timeout */
GSC_SC_CTRL1_WDEN = 5, /* 1 = enable, 0 = disable */
GSC_SC_CTRL1_BOOT_CHK = 6, /* 1 = enable alt boot check */
GSC_SC_CTRL1_WDDIS = 7, /* 1 = disable boot watchdog */
};
/* System Controller Interrupt bits */
enum {
GSC_SC_IRQ_PB = 0, /* Pushbutton switch */
GSC_SC_IRQ_SECURE = 1, /* Secure Key erase operation complete */
GSC_SC_IRQ_EEPROM_WP = 2, /* EEPROM write violation */
GSC_SC_IRQ_GPIO = 4, /* GPIO change */
GSC_SC_IRQ_TAMPER = 5, /* Tamper detect */
GSC_SC_IRQ_WATCHDOG = 6, /* Watchdog trip */
GSC_SC_IRQ_PBLONG = 7, /* Pushbutton long hold */
};
/* System Controller WP bits */
enum {
GSC_SC_WP_ALL = 0, /* Write Protect All EEPROM regions */
GSC_SC_WP_BOARDINFO = 1, /* Write Protect Board Info region */
};
/* System Controller Reset Cause */
enum {
GSC_SC_RST_CAUSE_VIN = 0,
GSC_SC_RST_CAUSE_PB = 1,
GSC_SC_RST_CAUSE_WDT = 2,
GSC_SC_RST_CAUSE_CPU = 3,
GSC_SC_RST_CAUSE_TEMP_LOCAL = 4,
GSC_SC_RST_CAUSE_TEMP_REMOTE = 5,
GSC_SC_RST_CAUSE_SLEEP = 6,
GSC_SC_RST_CAUSE_BOOT_WDT = 7,
GSC_SC_RST_CAUSE_BOOT_WDT_MAN = 8,
GSC_SC_RST_CAUSE_SOFT_PWR = 9,
GSC_SC_RST_CAUSE_MAX = 10,
};
#if CONFIG_IS_ENABLED(DM_I2C)
struct gsc_priv {
int gscver;
int fwver;
int fwcrc;
struct udevice *hwmon;
struct udevice *rtc;
};
/*
* GSCv2 will fail to ACK an I2C transaction if it is busy, which can occur
* during its 1HZ timer tick while reading ADC's. When this does occur,
* it will never be busy longer than 2 back-to-back transfers so retry 3 times.
*/
static int gsc_i2c_read(struct udevice *dev, uint addr, u8 *buf, int len)
{
struct gsc_priv *priv = dev_get_priv(dev);
int retry = (priv->gscver == 3) ? 1 : 3;
int n = 0;
int ret;
while (n++ < retry) {
ret = dm_i2c_read(dev, addr, buf, len);
if (!ret)
break;
if (ret != -EREMOTEIO)
break;
mdelay(10);
}
return ret;
}
static int gsc_i2c_write(struct udevice *dev, uint addr, const u8 *buf, int len)
{
struct gsc_priv *priv = dev_get_priv(dev);
int retry = (priv->gscver == 3) ? 1 : 3;
int n = 0;
int ret;
while (n++ < retry) {
ret = dm_i2c_write(dev, addr, buf, len);
if (!ret)
break;
if (ret != -EREMOTEIO)
break;
mdelay(10);
}
return ret;
}
static struct udevice *gsc_get_dev(int busno, int slave)
{
struct udevice *dev, *bus;
int ret;
ret = uclass_get_device_by_seq(UCLASS_I2C, busno, &bus);
if (ret)
return NULL;
ret = dm_i2c_probe(bus, slave, 0, &dev);
if (ret)
return NULL;
return dev;
}
static int gsc_thermal_get_info(struct udevice *dev, u8 *outreg, int *tmax, bool *enable)
{
struct gsc_priv *priv = dev_get_priv(dev);
int ret;
u8 reg;
if (priv->gscver > 2 && priv->fwver > 52) {
ret = gsc_i2c_read(dev, GSC_SC_THERM_PROTECT, &reg, 1);
if (!ret) {
if (outreg)
*outreg = reg;
if (tmax) {
*tmax = ((reg & 0xf8) >> 3) * 2;
if (*tmax)
*tmax += 70;
else
*tmax = 120;
}
if (enable)
*enable = reg & 1;
}
} else {
ret = -ENODEV;
}
return ret;
}
static int gsc_thermal_get_temp(struct udevice *dev)
{
struct gsc_priv *priv = dev_get_priv(dev);
u32 reg, mode, val;
const char *label;
ofnode node;
u8 buf[2];
ofnode_for_each_subnode(node, dev_read_subnode(dev, "adc")) {
if (ofnode_read_u32(node, "reg", &reg))
reg = -1;
if (ofnode_read_u32(node, "gw,mode", &mode))
mode = -1;
label = ofnode_read_string(node, "label");
if ((reg == -1) || (mode == -1) || !label)
continue;
if (mode != 0 || strcmp(label, "temp"))
continue;
memset(buf, 0, sizeof(buf));
if (!gsc_i2c_read(priv->hwmon, reg, buf, sizeof(buf))) {
val = buf[0] | buf[1] << 8;
if (val > 0x8000)
val -= 0xffff;
return val;
}
}
return 0;
}
static void gsc_thermal_info(struct udevice *dev)
{
struct gsc_priv *priv = dev_get_priv(dev);
switch (priv->gscver) {
case 2:
printf("board_temp:%dC ", gsc_thermal_get_temp(dev) / 10);
break;
case 3:
if (priv->fwver > 52) {
bool enabled;
int tmax;
if (!gsc_thermal_get_info(dev, NULL, &tmax, &enabled)) {
puts("Thermal protection:");
if (enabled)
printf("enabled at %dC ", tmax);
else
puts("disabled ");
}
}
break;
}
}
static void gsc_reset_info(struct udevice *dev)
{
struct gsc_priv *priv = dev_get_priv(dev);
static const char * const names[] = {
"VIN",
"PB",
"WDT",
"CPU",
"TEMP_L",
"TEMP_R",
"SLEEP",
"BOOT_WDT1",
"BOOT_WDT2",
"SOFT_PWR",
};
u8 reg;
/* reset cause */
switch (priv->gscver) {
case 2:
if (!gsc_i2c_read(dev, GSC_SC_STATUS, &reg, 1)) {
if (reg & BIT(GSC_SC_IRQ_WATCHDOG)) {
puts("RST:WDT");
reg &= ~BIT(GSC_SC_IRQ_WATCHDOG);
gsc_i2c_write(dev, GSC_SC_STATUS, &reg, 1);
} else {
puts("RST:VIN");
}
printf(" WDT:%sabled ",
(reg & BIT(GSC_SC_CTRL1_WDEN)) ? "en" : "dis");
}
break;
case 3:
if (priv->fwver > 52 &&
!gsc_i2c_read(dev, GSC_SC_RST_CAUSE, &reg, 1)) {
puts("RST:");
if (reg < ARRAY_SIZE(names))
printf("%s ", names[reg]);
else
printf("0x%02x ", reg);
}
break;
}
}
/* display hardware monitor ADC channels */
static int gsc_hwmon(struct udevice *dev)
{
struct gsc_priv *priv = dev_get_priv(dev);
u32 reg, mode, val, offset;
const char *label;
ofnode node;
u8 buf[2];
u32 r[2];
int ret;
/* iterate over hwmon nodes */
ofnode_for_each_subnode(node, dev_read_subnode(dev, "adc")) {
if (ofnode_read_u32(node, "reg", &reg))
reg = -1;
if (ofnode_read_u32(node, "gw,mode", &mode))
mode = -1;
label = ofnode_read_string(node, "label");
if ((reg == -1) || (mode == -1) || !label)
continue;
memset(buf, 0, sizeof(buf));
ret = gsc_i2c_read(priv->hwmon, reg, buf, sizeof(buf));
if (ret) {
printf("i2c error: %d\n", ret);
continue;
}
val = buf[0] | buf[1] << 8;
switch (mode) {
case 0: /* temperature (C*10) */
if (val > 0x8000)
val -= 0xffff;
printf("%-8s: %d.%ldC\n", label, val / 10, abs(val % 10));
break;
case 1: /* prescaled voltage */
if (val != 0xffff)
printf("%-8s: %d.%03dV\n", label, val / 1000, val % 1000);
break;
case 2: /* scaled based on ref volt and resolution */
val *= 2500;
val /= 1 << 12;
/* apply pre-scaler voltage divider */
if (!ofnode_read_u32_index(node, "gw,voltage-divider-ohms", 0, &r[0]) &&
!ofnode_read_u32_index(node, "gw,voltage-divider-ohms", 1, &r[1]) &&
r[0] && r[1]) {
val *= (r[0] + r[1]);
val /= r[1];
}
/* adjust by offset */
val += (offset / 1000);
printf("%-8s: %d.%03dV\n", label, val / 1000, val % 1000);
break;
}
}
return 0;
}
static int gsc_banner(struct udevice *dev)
{
struct gsc_priv *priv = dev_get_priv(dev);
/* banner */
printf("GSCv%d : v%d 0x%04x ", priv->gscver, priv->fwver, priv->fwcrc);
gsc_reset_info(dev);
gsc_thermal_info(dev);
puts("\n");
/* Display RTC */
if (priv->rtc) {
u8 buf[4];
time_t timestamp;
struct rtc_time tm;
if (!gsc_i2c_read(priv->rtc, 0, buf, 4)) {
timestamp = get_unaligned_le32(buf);
rtc_to_tm(timestamp, &tm);
printf("RTC : %4d-%02d-%02d %2d:%02d:%02d UTC\n",
tm.tm_year, tm.tm_mon, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);
}
}
return 0;
}
static int gsc_probe(struct udevice *dev)
{
struct gsc_priv *priv = dev_get_priv(dev);
u8 buf[32];
int ret;
ret = gsc_i2c_read(dev, 0, buf, sizeof(buf));
if (ret)
return ret;
/*
* GSC chip version:
* GSCv2 has 16 registers (which overlap)
* GSCv3 has 32 registers
*/
priv->gscver = memcmp(buf, buf + 16, 16) ? 3 : 2;
priv->fwver = buf[GSC_SC_FWVER];
priv->fwcrc = buf[GSC_SC_FWCRC] | buf[GSC_SC_FWCRC + 1] << 8;
priv->hwmon = gsc_get_dev(GSC_BUSNO, GSC_HWMON_ADDR);
if (priv->hwmon)
dev_set_priv(priv->hwmon, priv);
priv->rtc = gsc_get_dev(GSC_BUSNO, GSC_RTC_ADDR);
if (priv->rtc)
dev_set_priv(priv->rtc, priv);
#ifdef CONFIG_SPL_BUILD
gsc_banner(dev);
#endif
return 0;
};
static const struct udevice_id gsc_ids[] = {
{ .compatible = "gw,gsc", },
{ }
};
U_BOOT_DRIVER(gsc) = {
.name = "gsc",
.id = UCLASS_MISC,
.of_match = gsc_ids,
.probe = gsc_probe,
.priv_auto = sizeof(struct gsc_priv),
.flags = DM_FLAG_PRE_RELOC,
};
static int gsc_sleep(struct udevice *dev, unsigned long secs)
{
u8 regs[4];
int ret;
printf("GSC Sleeping for %ld seconds\n", secs);
put_unaligned_le32(secs, regs);
ret = gsc_i2c_write(dev, GSC_SC_TIME_ADD, regs, sizeof(regs));
if (ret)
goto err;
ret = gsc_i2c_read(dev, GSC_SC_CTRL1, regs, 1);
if (ret)
goto err;
regs[0] |= BIT(GSC_SC_CTRL1_SLEEP_ADD);
ret = gsc_i2c_write(dev, GSC_SC_CTRL1, regs, 1);
if (ret)
goto err;
regs[0] &= ~BIT(GSC_SC_CTRL1_SLEEP_ADD);
regs[0] |= BIT(GSC_SC_CTRL1_SLEEP_EN) | BIT(GSC_SC_CTRL1_SLEEP_ACTIVATE);
ret = gsc_i2c_write(dev, GSC_SC_CTRL1, regs, 1);
if (ret)
goto err;
return 0;
err:
printf("i2c error: %d\n", ret);
return ret;
}
static int gsc_wd_disable(struct udevice *dev)
{
int ret;
u8 reg;
ret = gsc_i2c_read(dev, GSC_SC_CTRL1, &reg, 1);
if (ret)
goto err;
reg |= BIT(GSC_SC_CTRL1_WDDIS);
reg &= ~BIT(GSC_SC_CTRL1_BOOT_CHK);
ret = gsc_i2c_write(dev, GSC_SC_CTRL1, &reg, 1);
if (ret)
goto err;
puts("GSC : boot watchdog disabled\n");
return 0;
err:
puts("i2c error");
return ret;
}
static int gsc_thermal(struct udevice *dev, const char *cmd, const char *val)
{
struct gsc_priv *priv = dev_get_priv(dev);
int ret, tmax;
bool enabled;
u8 reg;
if (priv->gscver < 3 || priv->fwver < 53)
return -EINVAL;
ret = gsc_thermal_get_info(dev, &reg, &tmax, &enabled);
if (ret)
return ret;
if (cmd && !strcmp(cmd, "enable")) {
if (val && *val) {
tmax = clamp((int)simple_strtoul(val, NULL, 0), 72, 122);
reg &= ~0xf8;
reg |= ((tmax - 70) / 2) << 3;
}
reg |= BIT(0);
gsc_i2c_write(dev, GSC_SC_THERM_PROTECT, &reg, 1);
} else if (cmd && !strcmp(cmd, "disable")) {
reg &= ~BIT(0);
gsc_i2c_write(dev, GSC_SC_THERM_PROTECT, &reg, 1);
} else if (cmd) {
return -EINVAL;
}
/* show status */
gsc_thermal_info(dev);
puts("\n");
return 0;
}
/* override in board files to display additional board EEPROM info */
__weak void board_gsc_info(void)
{
}
static void gsc_info(struct udevice *dev)
{
gsc_banner(dev);
board_gsc_info();
}
static int do_gsc(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
{
struct udevice *dev;
int ret;
/* get/probe driver */
ret = uclass_get_device_by_driver(UCLASS_MISC, DM_DRIVER_GET(gsc), &dev);
if (ret)
return CMD_RET_USAGE;
if (argc < 2) {
gsc_info(dev);
return CMD_RET_SUCCESS;
} else if (strcasecmp(argv[1], "sleep") == 0) {
if (argc < 3)
return CMD_RET_USAGE;
if (!gsc_sleep(dev, dectoul(argv[2], NULL)))
return CMD_RET_SUCCESS;
} else if (strcasecmp(argv[1], "hwmon") == 0) {
if (!gsc_hwmon(dev))
return CMD_RET_SUCCESS;
} else if (strcasecmp(argv[1], "wd-disable") == 0) {
if (!gsc_wd_disable(dev))
return CMD_RET_SUCCESS;
} else if (strcasecmp(argv[1], "thermal") == 0) {
char *cmd, *val;
cmd = (argc > 2) ? argv[2] : NULL;
val = (argc > 3) ? argv[3] : NULL;
if (!gsc_thermal(dev, cmd, val))
return CMD_RET_SUCCESS;
}
return CMD_RET_USAGE;
}
U_BOOT_CMD(gsc, 4, 1, do_gsc, "Gateworks System Controller",
"[sleep <secs>]|[hwmon]|[wd-disable][thermal [disable|enable [temp]]]\n");
/* disable boot watchdog - useful for an SPL that wants to use falcon mode */
int gsc_boot_wd_disable(void)
{
struct udevice *dev;
int ret;
/* get/probe driver */
ret = uclass_get_device_by_driver(UCLASS_MISC, DM_DRIVER_GET(gsc), &dev);
if (!ret)
ret = gsc_wd_disable(dev);
return ret;
}
# else
/*
* GSCv2 will fail to ACK an I2C transaction if it is busy, which can occur
* during its 1HZ timer tick while reading ADC's. When this does occur,
* it will never be busy longer than 2 back-to-back transfers so retry 3 times.
*/
static int gsc_i2c_read(uint chip, uint addr, u8 *buf, int len)
{
int retry = 3;
int n = 0;
int ret;
while (n++ < retry) {
ret = i2c_read(chip, addr, 1, buf, len);
if (!ret)
break;
if (ret != -EREMOTEIO)
break;
printf("%s 0x%02x retry %d\n", __func__, addr, n);
mdelay(10);
}
return ret;
}
static int gsc_i2c_write(uint chip, uint addr, u8 *buf, int len)
{
int retry = 3;
int n = 0;
int ret;
while (n++ < retry) {
ret = i2c_write(chip, addr, 1, buf, len);
if (!ret)
break;
if (ret != -EREMOTEIO)
break;
printf("%s 0x%02x retry %d\n", __func__, addr, n);
mdelay(10);
}
return ret;
}
/* disable boot watchdog - useful for an SPL that wants to use falcon mode */
int gsc_boot_wd_disable(void)
{
u8 buf[32];
int ret;
i2c_set_bus_num(GSC_BUSNO);
ret = gsc_i2c_read(GSC_SC_ADDR, 0, buf, sizeof(buf));
if (!ret) {
buf[GSC_SC_CTRL1] |= BIT(GSC_SC_CTRL1_WDDIS);
ret = gsc_i2c_write(GSC_SC_ADDR, GSC_SC_CTRL1, &buf[GSC_SC_CTRL1], 1);
printf("GSCv%d: v%d 0x%04x ",
memcmp(buf, buf + 16, 16) ? 3 : 2,
buf[GSC_SC_FWVER],
buf[GSC_SC_FWCRC] | buf[GSC_SC_FWCRC + 1] << 8);
if (buf[GSC_SC_STATUS] & BIT(GSC_SC_IRQ_WATCHDOG)) {
puts("RST:WDT ");
buf[GSC_SC_STATUS] &= ~BIT(GSC_SC_IRQ_WATCHDOG);
gsc_i2c_write(GSC_SC_ADDR, GSC_SC_STATUS, &buf[GSC_SC_STATUS], 1);
} else {
puts("RST:VIN ");
}
puts("WDT:disabled\n");
}
return ret;
}
#endif