// SPDX-License-Identifier: GPL-2.0+ /* * Copyright 2018 NXP * Copyright 2021 Purism */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 = EXT_CSD_EXTRACT_BOOT_PART(mmc->part_config); 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; }