
Initial commit of the Librem5 u-boot source
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 --- board/purism/librem5/librem5.c | 708 +++++++++++++++++++++++++++++++++ 1 file changed, 708 insertions(+) create mode 100644 board/purism/librem5/librem5.c
diff --git a/board/purism/librem5/librem5.c b/board/purism/librem5/librem5.c new file mode 100644 index 0000000000..b48e737e31 --- /dev/null +++ b/board/purism/librem5/librem5.c @@ -0,0 +1,708 @@ +// 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 <usb/xhci.h> +#include "librem5.h" + +DECLARE_GLOBAL_DATA_PTR; + +#define ENABLE_TPS_RESET + +#define WDOG_PAD_CTRL (PAD_CTL_DSE6 | PAD_CTL_HYS | PAD_CTL_PUE) + +static const iomux_v3_cfg_t wdog_pads[] = { + IMX8MQ_PAD_GPIO1_IO02__WDOG1_WDOG_B | MUX_PAD_CTRL(WDOG_PAD_CTRL), +}; + +int board_early_init_f(void) +{ + struct wdog_regs *wdog = (struct wdog_regs *)WDOG1_BASE_ADDR; + + log_debug("%s: starting\n", __func__); + + imx_iomux_v3_setup_multiple_pads(wdog_pads, ARRAY_SIZE(wdog_pads)); + set_wdog_reset(wdog); + + return 0; +} + +#ifdef CONFIG_OF_BOARD_SETUP +int ft_board_setup(void *blob, struct bd_info *bd) +{ + return 0; +} +#endif + +#ifdef CONFIG_BOARD_POSTCLK_INIT +int board_postclk_init(void) +{ + /* TODO */ + return 0; +} +#endif + +#ifndef CONFIG_DM_USB +int usb_gadget_handle_interrupts(void) +{ + dwc3_uboot_handle_interrupt(0); + return 0; +} + +#if defined(CONFIG_USB_DWC3) || defined(CONFIG_USB_XHCI_IMX8M) +static void dwc3_nxp_usb_phy_init(struct dwc3_device *dwc3) +{ + u32 RegData; + + RegData = readl(dwc3->base + USB_PHY_CTRL1); + RegData &= ~(USB_PHY_CTRL1_VDATSRCENB0 | USB_PHY_CTRL1_VDATDETENB0 | + USB_PHY_CTRL1_COMMONONN); + RegData |= USB_PHY_CTRL1_RESET | USB_PHY_CTRL1_ATERESET; + writel(RegData, dwc3->base + USB_PHY_CTRL1); + + RegData = readl(dwc3->base + USB_PHY_CTRL0); + RegData |= USB_PHY_CTRL0_REF_SSP_EN; + writel(RegData, dwc3->base + USB_PHY_CTRL0); + + RegData = readl(dwc3->base + USB_PHY_CTRL2); + RegData |= USB_PHY_CTRL2_TXENABLEN0; + writel(RegData, dwc3->base + USB_PHY_CTRL2); + + RegData = readl(dwc3->base + USB_PHY_CTRL1); + RegData &= ~(USB_PHY_CTRL1_RESET | USB_PHY_CTRL1_ATERESET); + writel(RegData, dwc3->base + USB_PHY_CTRL1); +} + +static struct dwc3_device dwc3_device0_data = { +#ifdef CONFIG_SPL_BUILD + .maximum_speed = USB_SPEED_HIGH, +#else + .maximum_speed = USB_SPEED_SUPER, +#endif + .base = USB1_BASE_ADDR, + .dr_mode = USB_DR_MODE_PERIPHERAL, + .index = 0, + .dis_u2_susphy_quirk = 1, +}; + +static struct dwc3_device dwc3_device1_data = { +#ifdef CONFIG_SPL_BUILD + .maximum_speed = USB_SPEED_HIGH, +#else + .maximum_speed = USB_SPEED_SUPER, +#endif + .base = USB2_BASE_ADDR, + .dr_mode = USB_DR_MODE_HOST, + .index = 1, + .dis_u2_susphy_quirk = 1, +}; + +int board_usb_init(int index, enum usb_init_type init) +{ + int ret = 0; + + imx8m_usb_power(index, true); + + if (index == 0 && init == USB_INIT_DEVICE) { + dwc3_nxp_usb_phy_init(&dwc3_device0_data); + ret = dwc3_uboot_init(&dwc3_device0_data); + } + if (index == 1 && init == USB_INIT_HOST) { + dwc3_nxp_usb_phy_init(&dwc3_device1_data); + ret = dwc3_uboot_init(&dwc3_device1_data); + } + + log_debug("%s: ending %d\n", __func__, ret); + + return ret; +} + +int board_usb_cleanup(int index, enum usb_init_type init) +{ + int ret = 0; + + if (index == 0 && init == USB_INIT_DEVICE) + dwc3_uboot_exit(index); + + if (index == 1 && init == USB_INIT_HOST) + dwc3_uboot_exit(index); + + imx8m_usb_power(index, false); + + return ret; +} +#endif + +int xhci_hcd_init(int index, struct xhci_hccr **hccr, struct xhci_hcor **hcor) +{ + return 0; +} + +void xhci_hcd_stop(int index) +{ +} +#endif + +#ifdef CONFIG_LOAD_ENV_FROM_MMC_BOOT_PARTITION +uint board_mmc_get_env_part(struct mmc *mmc) +{ + log_debug("%s: starting\n", __func__); + uint part = (mmc->part_config >> 3) & PART_ACCESS_MASK; + + if (part == 7) + part = 0; + return part; +} +#endif + +#define SPI_FLASH_CS IMX_GPIO_NR(5, 9) + +int board_spi_cs_gpio(unsigned int bus, unsigned int cs) +{ + return (bus == 0 && cs == 0) ? (SPI_FLASH_CS) : -1; +} + +#define ECSPI_PAD_CTRL (PAD_CTL_DSE2 | PAD_CTL_HYS) + +static const iomux_v3_cfg_t ecspi_pads[] = { + IMX8MQ_PAD_ECSPI1_SCLK__ECSPI1_SCLK | MUX_PAD_CTRL(ECSPI_PAD_CTRL), + IMX8MQ_PAD_ECSPI1_SS0__GPIO5_IO9 | MUX_PAD_CTRL(ECSPI_PAD_CTRL), + IMX8MQ_PAD_ECSPI1_MOSI__ECSPI1_MOSI | MUX_PAD_CTRL(ECSPI_PAD_CTRL), + IMX8MQ_PAD_ECSPI1_MISO__ECSPI1_MISO | MUX_PAD_CTRL(ECSPI_PAD_CTRL), +}; + +/* used if we reset the TPS65982 */ +static const iomux_v3_cfg_t ecspi_pads_hiz[] = { + IMX8MQ_PAD_ECSPI1_SCLK__GPIO5_IO6, + IMX8MQ_PAD_ECSPI1_MOSI__GPIO5_IO7, + IMX8MQ_PAD_ECSPI1_MISO__GPIO5_IO8, + IMX8MQ_PAD_ECSPI1_SS0__GPIO5_IO9, +}; + +extern int set_clk_ecspi(int); + +int board_ecspi_init(void) +{ + log_debug("%s: starting\n", __func__); + + imx_iomux_v3_setup_multiple_pads(ecspi_pads, ARRAY_SIZE(ecspi_pads)); + + set_clk_ecspi(1); + + return 0; +} + +void ecspi_hiz(void) +{ + log_debug("%s: starting\n", __func__); + + imx_iomux_v3_setup_multiple_pads(ecspi_pads_hiz, ARRAY_SIZE(ecspi_pads_hiz)); + + gpio_direction_input(SPI1_SCLK); + gpio_direction_input(SPI1_MISO); + gpio_direction_input(SPI1_MOSI); + gpio_direction_input(SPI1_SS0); +} + +int bq_read_reg(int reg_index) +{ + struct udevice *udev, *bus; + u8 reg; + int ret; + + ret = uclass_get_device_by_seq(UCLASS_I2C, 3, &bus); + if (ret) { + log_err("%s: No bus %d\n", __func__, 3); + return -ENODEV; + } + + ret = i2c_get_chip(bus, 0x6a, 1, &udev); + if (ret) { + log_err("%s: setting chip offset failed %d\n", __func__, ret); + return -EINVAL; + } + + dm_i2c_read(udev, reg_index, ®, 1); + + return reg; +} + +int bq_write_reg(int reg_index, u8 val) +{ + struct udevice *udev, *bus; + int ret; + + ret = uclass_get_device_by_seq(UCLASS_I2C, 3, &bus); + if (ret) { + log_err("%s: No bus %d\n", __func__, 3); + return 1; + } + + ret = i2c_get_chip(bus, 0x6a, 1, &udev); + if (ret) { + log_err("%s: setting chip offset failed %d\n", __func__, ret); + return 1; + } + + dm_i2c_write(udev, reg_index, &val, 1); + + return 0; +} + +int read_vbat(void) +{ + u8 reg02, reg03, reg0e; + int timeout = 1000; // ms + + /* disable the charger and enable BAT_LOADEN to discharge the decoupling + * CAP + * https://e2e.ti.com/support/power-management/f/196/t/567401 + */ + reg03 = bq_read_reg(0x03); + bq_write_reg(0x03, (reg03 | 0x80) & ~0x10); + + mdelay(10); + + /* force a conversion */ + reg02 = bq_read_reg(0x02); + bq_write_reg(0x02, (reg02 | 0x80) & ~0x40); + + do { + mdelay(10); + timeout -= 10; + } while ((bq_read_reg(0x02) & 0x80) && (timeout > 0)); + + reg0e = bq_read_reg(0x0e); + + /* return the charger to it's original state */ + bq_write_reg(0x03, reg03); + bq_write_reg(0x02, reg02); + + return (reg0e & 0x7f) * 20 + 2304; +} + +int tps65982_wait_for_app(int timeout, int timeout_step) +{ + int ret; + char response[6]; +#ifdef CONFIG_DM_I2C + struct udevice *udev, *bus; +#endif + + log_debug("%s: starting\n", __func__); + + /* Set the i2c bus */ +#ifndef CONFIG_DM_I2C + log_debug("%s: using i2c device mode\n", __func__); + i2c_set_bus_num(0); +#else + 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; + } +#endif + + while (timeout > 0) { +#ifndef CONFIG_DM_I2C + ret = i2c_read(0x3f, 0x03, 1, (u8 *)response, 5); +#else + ret = dm_i2c_read(udev, 0x03, (u8 *)response, 5); +#endif + 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; +} + +#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) +{ + int vbat; + + log_debug("%s: starting\n", __func__); + + if (tps65982_wait_for_app(500, 100)) { + /* check that VBAT is present and greater than 3000 mV. resetting the + * tps65982 disconnects VBUS so we need a different power source. + * Battery voltage greater than 4.15V likely means the battery is not + * present. + */ + vbat = read_vbat(); +#ifdef ENABLE_TPS_RESET + if (vbat > 3000 && vbat < 4150) { + log_info("tps65982 attempting reset - %d mV\n", vbat); + ecspi_hiz(); + + gpio_direction_output(TPS_RESET, 0); + mdelay(3); + gpio_direction_output(TPS_RESET, 1); + mdelay(2); + gpio_direction_output(TPS_RESET, 0); + mdelay(100); + + if (tps65982_wait_for_app(1000, 250)) { + log_err("tps65982 failed to boot\n"); + board_ecspi_init(); + gpio_direction_output(LED_G, 0); + gpio_direction_output(LED_R, 1); + return 1; + } + + board_ecspi_init(); + } else { + log_err("vbat out of range to reset tps65982 %d mV\n", vbat); + return 1; + } +#else + log_err("tps65982 APP boot failed %d mV\n", vbat); + return 1; +#endif + } + + log_info("tps65982 boot successful\n"); + return 0; +} + +int bq25896_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; +} + +/* + * set some safe defaults for the battery charger + */ +int init_charger_bq25896(void) +{ + u8 val; + int iinlim, vbat, 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; + } + + /* enable automatic DC conversions */ + val = dm_i2c_reg_read(udev, 0x02); + log_debug("REG0B 0x%x\n", val); + val = (val & ~0xc0) | 0x40; + dm_i2c_reg_write(udev, 0x02, val); + + /* check VBUS good */ + val = dm_i2c_reg_read(udev, 0x11); + log_debug("VBUS good %d\n", (val >> 7) & 1); + log_debug("VBUS mV %d\n", (val & 0x7f) * 100 + 2600); + + 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); + + /* check VBAT */ + vbat = read_vbat(); + log_debug("VBAT mV %d\n", vbat); + + if (vbat < 2800 && iinlim <= 500) { + /* battery voltage too low and + * insufficient current to boot linux. + */ + gpio_direction_output(LED_R, 1); + log_err("%s: voltage and current too low linux probably won't boot\n", __func__); + } + + /* 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); + + /* limit the VINDPM to 3.9V */ + dm_i2c_reg_write(udev, 0x0d, 0x8d); + + /* set the max voltage to 4.192V */ + val = dm_i2c_reg_read(udev, 0x6); + val = (val & ~0xFC) | 0x16 << 2; + dm_i2c_reg_write(udev, 0x6, val); + + /* set the SYS_MIN to 3.7V */ + val = dm_i2c_reg_read(udev, 0x3); + val = val | 0xE; + dm_i2c_reg_write(udev, 0x3, val); + + return 0; +} + +void init_usb_clk(void); + +int board_init(void) +{ + int tps_ret; + + log_debug("%s: starting %d mV\n", __func__, read_vbat()); + + init_pinmux(); + +#if defined(CONFIG_USB_DWC3) || defined(CONFIG_USB_XHCI_IMX8M) + log_debug("%s: initializing USB clk\n", __func__); + init_usb_clk(); +#endif + + tps_ret = init_tps65982(); + init_charger_bq25896(); + + if (!tps_ret) { + int current = tps65982_get_max_current(); + + if (current > 500) + bq25896_set_iinlim(current); + } + + return 0; +} + +#define VOL_DOWN IMX_GPIO_NR(1, 17) + +int fastboot_key_pressed(void) +{ + int val; + + gpio_direction_input(VOL_DOWN); + + val = gpio_get_value(VOL_DOWN); + log_debug("%s : %d\n", __func__, val); + + return !val; +} + +int board_late_init(void) +{ +#ifdef CONFIG_ENV_VARS_UBOOT_RUNTIME_CONFIG + u32 vid, rev; + char rev_str[3]; + + env_set("board_name", "librem5"); + if (fuse_read(8, 2, &vid) || fuse_read(14, 0, &rev)) { + env_set("board_rev", BOARD_REV_ERROR); + } else if (vid == 0) { + env_set("board_rev", BOARD_REV_UNKNOWN); + } else if (vid == (PURISM_PID << 16 | PURISM_VID)) { + 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")); +#endif + + if (is_usb_boot()) { + puts("USB Boot\n"); + env_set("bootcmd", "fastboot 0"); + } else if (fastboot_key_pressed()) { + log_debug("stop in u-boot\n"); + env_set("bootcmd", ""); + } + + return 0; +} +