[PATCH v6 2/2] board: sl28: add board specific nvm command

Michael Walle
2020-08-30T20:03:01Z

The board supports 16 configuration bits which can be manipulated with
this command. See the board's README for a detailed explanation on each
bit.

Signed-off-by: Michael Walle <michael@walle.cc>
---
board/kontron/sl28/Makefile | 2 +-
board/kontron/sl28/README | 79 ++++++++++++++++
board/kontron/sl28/cmds.c | 178 ++++++++++++++++++++++++++++++++++++
3 files changed, 258 insertions(+), 1 deletion(-)
create mode 100644 board/kontron/sl28/cmds.c

diff --git a/board/kontron/sl28/Makefile b/board/kontron/sl28/Makefile
index 0f1866c874..74d8012f0f 100644
--- a/board/kontron/sl28/Makefile
+++ b/board/kontron/sl28/Makefile
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0+

ifndef CONFIG_SPL_BUILD
-obj-y += sl28.o
+obj-y += sl28.o cmds.o
endif

obj-y += common.o ddr.o
diff --git a/board/kontron/sl28/README b/board/kontron/sl28/README
index 3ddd9aeeb8..40f3197716 100644
--- a/board/kontron/sl28/README
+++ b/board/kontron/sl28/README
@@ -57,6 +57,85 @@ u-boot (yet). But you can use the i2c command to access it:
> i2c md 4a 3.1 1

+Non-volatile Board Configuration Bits
+=====================================
+
+The board has 16 configuration bits which are stored in the CPLD and are
+non-volatile. These can be changed by the `sl28 nvm` command.
+
+| Bit | Description |
+| --- | --------------------------------------------------------------- |
+| 0 | Power-on inhibit |
+| 1 | Enable eMMC boot |
+| 2 | Enable watchdog by default |
+| 3 | Disable failsafe watchdog by default |
+| 4 | Clock generator selection bit 0 |
+| 5 | Clock generator selection bit 1 |
+| 6 | Disable CPU SerDes clock #2 and PCIe-A clock output |
+| 7 | Disable PCIe-B and PCIe-C clock output |
+| 8 | Keep onboard PHYs in reset |
+| 9 | Keep USB hub in reset |
+| 10 | Keep eDP-to-LVDS converter in reset |
+| 11 | Enable I2C stuck recovery on I2C PM and I2C GP busses |
+| 12 | Enable automatic onboard PHY H/W reset |
+| 13 | reserved |
+| 14 | Used by the RCW to determine boot source |
+| 15 | Used by the RCW to determine boot source |
+
+Please note, that if the board is in failsafe mode, the bits will have the
+factory defaults, ie. all bits are off.
+
+Power-On Inhibit
+----------------
+
+If this is set, the board doesn't automatically turn on when power is
+applied. Instead, the user has to either toggle the `PWR_BTN#` line or
+use any other wake-up source such as RTC alarm or Wake-on-LAN.
+
+eMMC Boot
+---------
+
+If this is set, the RCW will be fetched from the on-board eMMC at offset
+1MiB. For further details, have a look at the [SMARC-sAL28 Reset
+Configuration Word documentation][1].
+
+Watchdog
+--------
+
+By default, the CPLD watchdog is enabled in failsafe mode. Using bits 2 and
+3, the user can change its mode or disable it altogether.
+
+| Bit 2 | Bit 3 | Description |
+| ----- | ----- | ------------------------------- |
+| 0 | 0 | Watchdog enabled, failsafe mode |
+| 0 | 1 | Watchdog disabled |
+| 1 | 0 | Watchdog enabled, failsafe mode |
+| 1 | 1 | Watchdog enabled, normal mode |
+
+Clock Generator Select
+----------------------
+
+The board is prepared to supply different SerDes clock speeds. But for now,
+only setting 0 is supported, otherwise the CPU will hang because the PLL
+will not lock.
+
+Clock Output Disable And Keep Devices In Reset
+----------------------------------------------
+
+To safe power, the user might disable different devices and clock output of
+the board. It is not supported to disable the "CPU SerDes clock #2" for
+now, otherwise the CPU will hang because the PLL will not lock.
+
+Automatic reset of the onboard PHYs
+-----------------------------------
+
+By default, there is no hardware reset of the onboard PHY. This is because
+for Wake-on-LAN, some registers have to retain their values. If you don't
+use the WOL feature and a soft reset of the PHY is not enough you can
+enable the hardware reset. The onboard PHY hardware reset follows the
+power-on reset.
+
+
Vendor Documentation
====================

diff --git a/board/kontron/sl28/cmds.c b/board/kontron/sl28/cmds.c
new file mode 100644
index 0000000000..046d3b4903
--- /dev/null
+++ b/board/kontron/sl28/cmds.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * sl28 extension commands
+ *
+ * Copyright (c) 2020 Kontron Europe GmbH
+ */
+
+#include <common.h>
+#include <command.h>
+#include <i2c.h>
+#include <linux/delay.h>
+
+#define CPLD_I2C_ADDR 0x4a
+#define REG_UFM_CTRL 0x02
+#define UFM_CTRL_DCLK BIT(1)
+#define UFM_CTRL_DIN BIT(2)
+#define UFM_CTRL_PROGRAM BIT(3)
+#define UFM_CTRL_ERASE BIT(4)
+#define UFM_CTRL_DSHIFT BIT(5)
+#define UFM_CTRL_DOUT BIT(6)
+#define UFM_CTRL_BUSY BIT(7)
+
+static int ufm_shift_data(struct udevice *dev, u16 data_in, u16 *data_out)
+{
+ int i;
+ int ret;
+ u16 data = 0;
+
+ /* latch data */
+ ret = dm_i2c_reg_write(dev, REG_UFM_CTRL, 0);
+ if (ret < 0)
+ return ret;
+ ret = dm_i2c_reg_write(dev, REG_UFM_CTRL, UFM_CTRL_DCLK);
+ if (ret < 0)
+ return ret;
+
+ /* assert drshift */
+ ret = dm_i2c_reg_write(dev, REG_UFM_CTRL,
+ UFM_CTRL_DSHIFT | UFM_CTRL_DCLK);
+ if (ret < 0)
+ return ret;
+
+ /* clock 16 data bits, reverse order */
+ for (i = 15; i >= 0; i--) {
+ u8 din = (data_in & (1 << i)) ? UFM_CTRL_DIN : 0;
+
+ ret = dm_i2c_reg_write(dev, REG_UFM_CTRL, UFM_CTRL_DSHIFT
+ | din);
+ if (ret < 0)
+ return ret;
+ if (data_out) {
+ ret = dm_i2c_reg_read(dev, REG_UFM_CTRL);
+ if (ret < 0)
+ return ret;
+ if (ret & UFM_CTRL_DOUT)
+ data |= (1 << i);
+ }
+ ret = dm_i2c_reg_write(dev, REG_UFM_CTRL,
+ UFM_CTRL_DSHIFT | UFM_CTRL_DCLK | din);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* deassert drshift */
+ ret = dm_i2c_reg_write(dev, REG_UFM_CTRL, UFM_CTRL_DCLK);
+ if (ret < 0)
+ return ret;
+
+ if (data_out)
+ *data_out = data;
+
+ return ret;
+}
+
+static int ufm_erase(struct udevice *dev)
+{
+ int ret;
+
+ /* erase, tEPMX is 500ms */
+ ret = dm_i2c_reg_write(dev, REG_UFM_CTRL,
+ UFM_CTRL_DCLK | UFM_CTRL_ERASE);
+ if (ret < 0)
+ return ret;
+ ret = dm_i2c_reg_write(dev, REG_UFM_CTRL, UFM_CTRL_DCLK);
+ if (ret < 0)
+ return ret;
+ mdelay(500);
+
+ return 0;
+}
+
+static int ufm_program(struct udevice *dev)
+{
+ int ret;
+
+ /* program, tPPMX is 100us */
+ ret = dm_i2c_reg_write(dev, REG_UFM_CTRL,
+ UFM_CTRL_DCLK | UFM_CTRL_PROGRAM);
+ if (ret < 0)
+ return ret;
+ ret = dm_i2c_reg_write(dev, REG_UFM_CTRL, UFM_CTRL_DCLK);
+ if (ret < 0)
+ return ret;
+ udelay(100);
+
+ return 0;
+}
+
+static int ufm_write(struct udevice *dev, u16 data)
+{
+ int ret;
+
+ ret = ufm_shift_data(dev, data, NULL);
+ if (ret < 0)
+ return ret;
+
+ ret = ufm_erase(dev);
+ if (ret < 0)
+ return ret;
+
+ return ufm_program(dev);
+}
+
+static int ufm_read(struct udevice *dev, u16 *data)
+{
+ return ufm_shift_data(dev, 0, data);
+}
+
+static int do_sl28_nvm(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct udevice *dev;
+ u16 nvm;
+ int ret;
+ char *endp;
+
+ if (i2c_get_chip_for_busnum(0, CPLD_I2C_ADDR, 1, &dev))
+ return CMD_RET_FAILURE;
+
+ if (argc > 1) {
+ nvm = simple_strtoul(argv[1], &endp, 16);
+ if (*endp != '\0') {
+ printf("ERROR: argument is not a valid number\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * We swap all bits, because the a zero bit in hardware means the
+ * feature is enabled. But this is hard for the user.
+ */
+ nvm ^= 0xffff;
+
+ ret = ufm_write(dev, nvm);
+ if (ret)
+ goto out;
+ printf("New settings will be activated after the next power cycle!\n");
+ } else {
+ ret = ufm_read(dev, &nvm);
+ if (ret)
+ goto out;
+ nvm ^= 0xffff;
+
+ printf("%04hx\n", nvm);
+ }
+
+ return CMD_RET_SUCCESS;
+
+out:
+ printf("command failed (%d)\n", ret);
+ return CMD_RET_FAILURE;
+}
+
+static char sl28_help_text[] =
+ "nvm [<hex>] - display/set the 16 non-volatile bits\n";
+
+U_BOOT_CMD_WITH_SUBCMDS(sl28, "SMARC-sAL28 specific", sl28_help_text,
+ U_BOOT_SUBCMD_MKENT(nvm, 2, 1, do_sl28_nvm));
--
2.20.1