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;
+}