u-boot/board/purism/librem5/librem5.c
Tom Rini 208fc7a9f9 Merge patch series "provide names for emmc hardware partitions"
Tim Harvey <tharvey@gateworks.com> says:

Modern eMMC v4+ devices have multiple hardware partitions per the JEDEC
specification described as:
 Boot Area Partition 1
 Boot Area Partition 2
 RPMB Partition
 General Purpose Partition 1
 General Purpose Partition 2
 General Purpose Partition 3
 General Purpose Partition 4
 User Data Area

These are referenced by fields in the PARTITION_CONFIG register
(Extended CSD Register 179) which is defined as:
bit 7: reserved
bit 6: BOOT_ACK
  0x0: No boot acknowledge sent (default
  0x1: Boot acknowledge sent during boot operation Bit
bit 5:3: BOOT_PARTITION_ENABLE
  0x0: Device not boot enabled (default)
  0x1: Boot Area partition 1 enabled for boot
  0x2: Boot Area partition 2 enabled for boot
  0x3-0x6: Reserved
  0x7: User area enabled for boot
bit 2:0 PARTITION_ACCESS
  0x0: No access to boot partition (default)
  0x1: Boot Area partition 1
  0x2: Boot Area partition 2
  0x3: Replay Protected Memory Block (RPMB)
  0x4: Access to General Purpose partition 1
  0x5: Access to General Purpose partition 2
  0x6: Access to General Purpose partition 3
  0x7: Access to General Purpose partition 4

Note that setting PARTITION_ACCESS to 0x0 results in selecting the User
Data Area partition.

You can see above that the two fields BOOT_PARTITION_ENABLE and
PARTITION_ACCESS do not use the same enumerated values.

U-Boot uses a set of macros to access fields of the PARTITION_CONFIG
register:
EXT_CSD_BOOT_ACK_ENABLE                 (1 << 6)
EXT_CSD_BOOT_PARTITION_ENABLE           (1 << 3)
EXT_CSD_PARTITION_ACCESS_ENABLE         (1 << 0)
EXT_CSD_PARTITION_ACCESS_DISABLE        (0 << 0)

EXT_CSD_BOOT_ACK(x)             (x << 6)
EXT_CSD_BOOT_PART_NUM(x)        (x << 3)
EXT_CSD_PARTITION_ACCESS(x)     (x << 0)

EXT_CSD_EXTRACT_BOOT_ACK(x) (((x) >> 6) & 0x1)
EXT_CSD_EXTRACT_BOOT_PART(x) (((x) >> 3) & 0x7)
EXT_CSD_EXTRACT_PARTITION_ACCESS(x) ((x) & 0x7)

There are various places in U-Boot where the BOOT_PARTITION_ENABLE field
is accessed via EXT_CSD_EXTRACT_PARTITION_ACCESS and converted to a
hardware partition consistent with the definition of the
PARTITION_ACCESS field used by the various mmc_switch incarnations.

To add some sanity to the distinction between BOOT_PARTITION_ENABLE
(used to specify the active device on power-cycle) and PARTITION_ACCESS
(used to switch between hardware partitions) create two enumerated types
and use them wherever struct mmc * part_config is used or the above
macros are used.

Additionally provide arrays of the field names and allow those to be
used in the 'mmc partconf' command and in board support files.

The first patch adds enumerated types and makes use of them which
represents no compiled code change.

The 2nd patch adds the array of names and uses them in the 'mmc
partconf' command.

The 3rd patch uses the array of hardware partition names in a board
support file to show what emmc hardware partition U-Boot is being loaded
from.
2024-09-05 12:13:24 -06:00

449 lines
9.9 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018 NXP
* Copyright 2021 Purism
*/
#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 = EXT_CSD_EXTRACT_BOOT_PART(mmc->part_config);
if (part == EMMC_BOOT_PART_USER)
part = EMMC_HWPART_DEFAULT;
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)) {
/*
* Use the r4 dtb by default as those are the most
* widespread devices.
*/
u32 rev, dtb_rev = 4;
char rev_str[3];
char fdt_str[50];
env_set("board_name", "librem5");
if (fuse_read(9, 0, &rev)) {
env_set("board_rev", BOARD_REV_ERROR);
} else if (rev == 0) {
/*
* If the fuses aren't burnt we should use either the
* r2 or r3 DTB. The latter makes more sense as there
* are far more r3 devices out there.
*/
dtb_rev = 3;
env_set("board_rev", BOARD_REV_UNKNOWN);
} else if (rev > 0) {
if (rev == 1)
dtb_rev = 2;
else if (rev < dtb_rev)
dtb_rev = rev;
/*
* FCC-approved devices report '5' as their board
* revision but use the r4 DTB as the PCB's are
* functionally identical.
*/
else if (rev == 5)
dtb_rev = 4;
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"));
sprintf(fdt_str, "freescale/imx8mq-librem5-r%u.dtb", dtb_rev);
env_set("fdtfile", fdt_str);
}
if (is_usb_boot()) {
puts("USB Boot\n");
env_set("bootcmd", "fastboot 0");
}
return 0;
}