board: purism: add the Purism Librem5 phone
Initial commit of Librem5 u-boot and SPL
Signed-off-by: Angus Ainslie <angus@akkea.ca>
Co-developed-by: Sebastian Krzyszkowiak <sebastian.krzyszkowiak@puri.sm>
Signed-off-by: Sebastian Krzyszkowiak <sebastian.krzyszkowiak@puri.sm>
Reviewed-by: Fabio Estevam <festevam@denx.de>
diff --git a/board/purism/librem5/librem5.c b/board/purism/librem5/librem5.c
new file mode 100644
index 0000000..caa0265
--- /dev/null
+++ b/board/purism/librem5/librem5.c
@@ -0,0 +1,425 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2018 NXP
+ * Copyright 2021 Purism
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <errno.h>
+#include <asm/io.h>
+#include <miiphy.h>
+#include <asm/mach-imx/iomux-v3.h>
+#include <asm-generic/gpio.h>
+#include <asm/arch/sys_proto.h>
+#include <fsl_esdhc.h>
+#include <mmc.h>
+#include <asm/arch/imx8mq_pins.h>
+#include <asm/arch/sys_proto.h>
+#include <asm/mach-imx/gpio.h>
+#include <asm/mach-imx/mxc_i2c.h>
+#include <asm/arch/clock.h>
+#include <asm/mach-imx/video.h>
+#include <fuse.h>
+#include <i2c.h>
+#include <spl.h>
+#include <usb.h>
+#include <dwc3-uboot.h>
+#include <linux/delay.h>
+#include <linux/bitfield.h>
+#include <power/regulator.h>
+#include <usb/xhci.h>
+#include "librem5.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+int board_early_init_f(void)
+{
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_LOAD_ENV_FROM_MMC_BOOT_PARTITION)
+uint board_mmc_get_env_part(struct mmc *mmc)
+{
+ uint part = (mmc->part_config >> 3) & PART_ACCESS_MASK;
+
+ if (part == 7)
+ part = 0;
+ return part;
+}
+#endif
+
+int tps65982_wait_for_app(int timeout, int timeout_step)
+{
+ int ret;
+ char response[6];
+ struct udevice *udev, *bus;
+
+ log_debug("%s: starting\n", __func__);
+
+ /* Set the i2c bus */
+ ret = uclass_get_device_by_seq(UCLASS_I2C, 0, &bus);
+ if (ret) {
+ log_err("%s: No bus %d\n", __func__, 0);
+ return 1;
+ }
+
+ ret = i2c_get_chip(bus, 0x3f, 1, &udev);
+ if (ret) {
+ log_err("%s: setting chip offset failed %d\n", __func__, ret);
+ return 1;
+ }
+
+ while (timeout > 0) {
+ ret = dm_i2c_read(udev, 0x03, (u8 *)response, 5);
+ log_debug("tps65982 mode %s\n", response);
+ if (response[1] == 'A')
+ return 0;
+ mdelay(timeout_step);
+ timeout -= timeout_step;
+ log_debug("tps65982 waited %d ms %c\n", timeout_step, response[1]);
+ }
+
+ return 1;
+}
+
+int tps65982_clear_dead_battery(void)
+{
+ int ret;
+ char cmd[5] = "\04DBfg";
+ struct udevice *udev, *bus;
+
+ log_debug("%s: starting\n", __func__);
+
+ /* Set the i2c bus */
+ ret = uclass_get_device_by_seq(UCLASS_I2C, 0, &bus);
+ if (ret) {
+ log_err("%s: No bus %d\n", __func__, 0);
+ return 1;
+ }
+
+ ret = i2c_get_chip(bus, 0x3f, 1, &udev);
+ if (ret) {
+ log_err("%s: setting chip offset failed %d\n", __func__, ret);
+ return 1;
+ }
+
+ /* clearing the dead battery flag when not in dead battery condition
+ * is a no-op, so there's no need to check if it's in effect
+ */
+ ret = dm_i2c_write(udev, 0x08, cmd, 5);
+ if (ret) {
+ log_err("%s: writing 4CC command failed %d", __func__, ret);
+ return 1;
+ }
+
+ return 0;
+}
+
+#define TPS_POWER_STATUS_PWROPMODE(x) FIELD_GET(GENMASK(3, 2), x)
+
+#define TPS_PDO_CONTRACT_TYPE(x) FIELD_GET(GENMASK(31, 30), x)
+#define TPS_PDO_CONTRACT_FIXED 0
+#define TPS_PDO_CONTRACT_BATTERY 1
+#define TPS_PDO_CONTRACT_VARIABLE 2
+
+#define TPS_TYPEC_PWR_MODE_USB 0
+#define TPS_TYPEC_PWR_MODE_1_5A 1
+#define TPS_TYPEC_PWR_MODE_3_0A 2
+#define TPS_TYPEC_PWR_MODE_PD 3
+
+#define TPS_PDO_FIXED_CONTRACT_MAX_CURRENT(x) (FIELD_GET(GENMASK(9, 0), x) * 10)
+#define TPS_PDO_VAR_CONTRACT_MAX_CURRENT(x) (FIELD_GET(GENMASK(9, 0), x) * 10)
+#define TPS_PDO_BAT_CONTRACT_MAX_VOLTAGE(x) (FIELD_GET(GENMASK(29, 20), x) * 50)
+#define TPS_PDO_BAT_CONTRACT_MAX_POWER(x) (FIELD_GET(GENMASK(9, 0), x) * 250)
+
+int tps65982_get_max_current(void)
+{
+ int ret;
+ u8 buf[7];
+ u8 pwr_status;
+ u32 contract;
+ int type, mode;
+ struct udevice *udev, *bus;
+
+ log_debug("%s: starting\n", __func__);
+
+ /* Set the i2c bus */
+ ret = uclass_get_device_by_seq(UCLASS_I2C, 0, &bus);
+ if (ret) {
+ log_debug("%s: No bus %d\n", __func__, 0);
+ return -1;
+ }
+
+ ret = i2c_get_chip(bus, 0x3f, 1, &udev);
+ if (ret) {
+ log_debug("%s: setting chip offset failed %d\n", __func__, ret);
+ return -1;
+ }
+
+ ret = dm_i2c_read(udev, 0x3f, buf, 3);
+ if (ret) {
+ log_debug("%s: reading pwr_status failed %d\n", __func__, ret);
+ return -1;
+ }
+
+ pwr_status = buf[1];
+
+ if (!(pwr_status & 1))
+ return 0;
+
+ mode = TPS_POWER_STATUS_PWROPMODE(pwr_status);
+ switch (mode) {
+ case TPS_TYPEC_PWR_MODE_1_5A:
+ return 1500;
+ case TPS_TYPEC_PWR_MODE_3_0A:
+ return 3000;
+ case TPS_TYPEC_PWR_MODE_PD:
+ ret = dm_i2c_read(udev, 0x34, buf, 7);
+ if (ret) {
+ log_debug("%s: reading active contract failed %d\n", __func__, ret);
+ return -1;
+ }
+
+ contract = buf[1] + (buf[2] << 8) + (buf[3] << 16) + (buf[4] << 24);
+
+ type = TPS_PDO_CONTRACT_TYPE(contract);
+
+ switch (type) {
+ case TPS_PDO_CONTRACT_FIXED:
+ return TPS_PDO_FIXED_CONTRACT_MAX_CURRENT(contract);
+ case TPS_PDO_CONTRACT_BATTERY:
+ return 1000 * TPS_PDO_BAT_CONTRACT_MAX_POWER(contract)
+ / TPS_PDO_BAT_CONTRACT_MAX_VOLTAGE(contract);
+ case TPS_PDO_CONTRACT_VARIABLE:
+ return TPS_PDO_VAR_CONTRACT_MAX_CURRENT(contract);
+ default:
+ log_debug("Unknown contract type: %d\n", type);
+ return -1;
+ }
+ case TPS_TYPEC_PWR_MODE_USB:
+ return 500;
+ default:
+ log_debug("Unknown power mode: %d\n", mode);
+ return -1;
+ }
+}
+
+int init_tps65982(void)
+{
+ log_debug("%s: starting\n", __func__);
+
+ if (tps65982_wait_for_app(500, 100)) {
+ log_err("tps65982 APP boot failed\n");
+ return 1;
+ }
+
+ log_info("tps65982 boot successful\n");
+ return 0;
+}
+
+int bq25895_set_iinlim(int current)
+{
+ u8 val, iinlim;
+ int ret;
+ struct udevice *udev, *bus;
+
+ /* Set the i2c bus */
+ ret = uclass_get_device_by_seq(UCLASS_I2C, 3, &bus);
+ if (ret) {
+ log_err("%s: No bus 3\n", __func__);
+ return ret;
+ }
+
+ ret = i2c_get_chip(bus, 0x6a, 1, &udev);
+ if (ret) {
+ log_err("%s: setting chip offset failed %d\n", __func__, ret);
+ return ret;
+ }
+
+ if (current > 3250)
+ current = 3250;
+ if (current < 100)
+ current = 100;
+
+ val = dm_i2c_reg_read(udev, 0x00);
+ iinlim = ((current - 100) / 50) & 0x3f;
+ val = (val & 0xc0) | iinlim;
+ dm_i2c_reg_write(udev, 0x00, val);
+ log_debug("REG00 0x%x\n", val);
+
+ return 0;
+}
+
+bool bq25895_battery_present(void)
+{
+ u8 val;
+ int ret;
+ struct udevice *udev, *bus;
+
+ /* Set the i2c bus */
+ ret = uclass_get_device_by_seq(UCLASS_I2C, 3, &bus);
+ if (ret) {
+ log_err("%s: No bus 3\n", __func__);
+ return ret;
+ }
+
+ ret = i2c_get_chip(bus, 0x6a, 1, &udev);
+ if (ret) {
+ log_err("%s: setting chip offset failed %d\n", __func__, ret);
+ return ret;
+ }
+
+ /* note that this may return false negatives when there's
+ * no external power applied and the battery voltage is below
+ * Vsys. this isn't a problem when used for clearing the dead
+ * battery flag though, since it's certain that there's an external
+ * power applied in this case
+ */
+ val = dm_i2c_reg_read(udev, 0x0e) & 0x7f;
+ if (val == 0x00 || val == 0x7f)
+ return false;
+
+ return true;
+}
+
+/*
+ * set some safe defaults for the battery charger
+ */
+int init_charger_bq25895(void)
+{
+ u8 val;
+ int iinlim, ret;
+ struct udevice *udev, *bus;
+
+ /* Set the i2c bus */
+ ret = uclass_get_device_by_seq(UCLASS_I2C, 3, &bus);
+ if (ret) {
+ log_debug("%s: No bus 3\n", __func__);
+ return ret;
+ }
+
+ ret = i2c_get_chip(bus, 0x6a, 1, &udev);
+ if (ret) {
+ log_debug("%s: setting chip offset failed %d\n", __func__, ret);
+ return ret;
+ }
+
+ val = dm_i2c_reg_read(udev, 0x0b);
+ log_debug("REG0B 0x%x\n", val);
+
+ log_debug("VBUS_STAT 0x%x\n", val >> 5);
+ switch (val >> 5) {
+ case 0:
+ log_debug("VBUS not detected\n");
+ break;
+ case 1:
+ log_debug("USB SDP IINLIM 500mA\n");
+ break;
+ case 2:
+ log_debug("USB CDP IINLIM 1500mA\n");
+ break;
+ case 3:
+ log_debug("USB DCP IINLIM 3500mA\n");
+ break;
+ case 4:
+ log_debug("MAXCHARGE IINLIM 1500mA\n");
+ break;
+ case 5:
+ log_debug("Unknown IINLIM 500mA\n");
+ break;
+ case 6:
+ log_debug("DIVIDER IINLIM > 1000mA\n");
+ break;
+ case 7:
+ log_debug("OTG\n");
+ break;
+ };
+
+ log_debug("CHRG_STAT 0x%x\n", (val >> 3) & 0x3);
+ log_debug("PG_STAT 0x%x\n", (val >> 2) & 1);
+ log_debug("SDP_STAT 0x%x\n", (val >> 1) & 1);
+ log_debug("VSYS_STAT 0x%x\n", val & 1);
+
+ val = dm_i2c_reg_read(udev, 0x00);
+ log_debug("REG00 0x%x\n", val);
+ iinlim = 100 + (val & 0x3f) * 50;
+ log_debug("IINLIM %d mA\n", iinlim);
+ log_debug("EN_HIZ 0x%x\n", (val >> 7) & 1);
+ log_debug("EN_ILIM 0x%x\n", (val >> 6) & 1);
+
+ /* set 1.6A charge limit */
+ dm_i2c_reg_write(udev, 0x04, 0x19);
+
+ /* re-enable charger */
+ val = dm_i2c_reg_read(udev, 0x03);
+ val = val | 0x10;
+ dm_i2c_reg_write(udev, 0x03, val);
+
+ return 0;
+}
+
+int board_init(void)
+{
+ struct udevice *dev;
+ int tps_ret;
+
+ if (IS_ENABLED(CONFIG_USB_DWC3) || IS_ENABLED(CONFIG_USB_XHCI_IMX8M)) {
+ log_debug("%s: initializing USB clk\n", __func__);
+
+ /* init_usb_clk won't enable the second clock if it's a USB boot */
+ if (is_usb_boot()) {
+ clock_enable(CCGR_USB_CTRL2, 1);
+ clock_enable(CCGR_USB_PHY2, 1);
+ }
+
+ printf("Enabling regulator-hub\n");
+ if (!regulator_get_by_devname("regulator-hub", &dev)) {
+ if (regulator_set_enable(dev, true))
+ pr_err("Failed to enable regulator-hub\n");
+ }
+ }
+
+ tps_ret = init_tps65982();
+ init_charger_bq25895();
+
+ if (!tps_ret) {
+ int current = tps65982_get_max_current();
+
+ if (current > 500)
+ bq25895_set_iinlim(current);
+
+ if (bq25895_battery_present())
+ tps65982_clear_dead_battery();
+ }
+
+ return 0;
+}
+
+int board_late_init(void)
+{
+ if (IS_ENABLED(CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG)) {
+ u32 rev;
+ char rev_str[3];
+
+ env_set("board_name", "librem5");
+ if (fuse_read(9, 0, &rev)) {
+ env_set("board_rev", BOARD_REV_ERROR);
+ } else if (rev == 0) {
+ env_set("board_rev", BOARD_REV_UNKNOWN);
+ } else if (rev > 0) {
+ sprintf(rev_str, "%u", rev);
+ env_set("board_rev", rev_str);
+ }
+
+ printf("Board name: %s\n", env_get("board_name"));
+ printf("Board rev: %s\n", env_get("board_rev"));
+ }
+
+ if (is_usb_boot()) {
+ puts("USB Boot\n");
+ env_set("bootcmd", "fastboot 0");
+ }
+
+ return 0;
+}