mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-01 00:49:43 +00:00
15c41a8d9e
The PMIC on Apalis iMX6 may have ECC errors in fuses that will prevent correct settings. Up to one bit error per fuse bank may be reported and corrected by the ECC logic. Two bit errors can only be reported. Signed-off-by: Gerard Salvatella <gerard.salvatella@toradex.com> Acked-by: Marcel Ziswiler <marcel.ziswiler@toradex.com>
282 lines
6.4 KiB
C
282 lines
6.4 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright (C) 2014-2019, Toradex AG
|
|
*/
|
|
|
|
/*
|
|
* Helpers for Freescale PMIC PF0100
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <i2c.h>
|
|
#include <asm/arch/imx-regs.h>
|
|
#include <asm/arch/iomux.h>
|
|
#include <asm/arch/mx6-pins.h>
|
|
#include <asm/gpio.h>
|
|
#include <asm/mach-imx/iomux-v3.h>
|
|
|
|
#include "pf0100_otp.inc"
|
|
#include "pf0100.h"
|
|
|
|
/* define for PMIC register dump */
|
|
/*#define DEBUG */
|
|
|
|
#define WARNBAR "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
|
|
|
|
/* use Apalis GPIO1 to switch on VPGM, ON: 1 */
|
|
static __maybe_unused iomux_v3_cfg_t const pmic_prog_pads[] = {
|
|
MX6_PAD_NANDF_D4__GPIO2_IO04 | MUX_PAD_CTRL(NO_PAD_CTRL),
|
|
# define PMIC_PROG_VOLTAGE IMX_GPIO_NR(2, 4)
|
|
};
|
|
|
|
unsigned pmic_init(void)
|
|
{
|
|
int rc;
|
|
struct udevice *dev = NULL;
|
|
unsigned programmed = 0;
|
|
uchar bus = 1;
|
|
uchar devid, revid, val;
|
|
|
|
puts("PMIC: ");
|
|
rc = i2c_get_chip_for_busnum(bus, PFUZE100_I2C_ADDR, 1, &dev);
|
|
if (rc) {
|
|
printf("failed to get device for PMIC at address 0x%x\n",
|
|
PFUZE100_I2C_ADDR);
|
|
return 0;
|
|
}
|
|
|
|
/* check for errors in PMIC fuses */
|
|
if (dm_i2c_read(dev, PFUZE100_INTSTAT3, &val, 1) < 0) {
|
|
puts("i2c pmic INTSTAT3 register read failed\n");
|
|
return 0;
|
|
}
|
|
if (val & PFUZE100_BIT_OTP_ECCI) {
|
|
puts("\n" WARNBAR);
|
|
puts("WARNING: ecc errors found in pmic fuse banks\n");
|
|
puts(WARNBAR);
|
|
}
|
|
if (dm_i2c_read(dev, PFUZE100_OTP_ECC_SE1, &val, 1) < 0) {
|
|
puts("i2c pmic ECC_SE1 register read failed\n");
|
|
return 0;
|
|
}
|
|
if (val & PFUZE100_BITS_ECC_SE1) {
|
|
puts(WARNBAR);
|
|
puts("WARNING: ecc has made bit corrections in banks 1 to 5\n");
|
|
puts(WARNBAR);
|
|
}
|
|
if (dm_i2c_read(dev, PFUZE100_OTP_ECC_SE2, &val, 1) < 0) {
|
|
puts("i2c pmic ECC_SE2 register read failed\n");
|
|
return 0;
|
|
}
|
|
if (val & PFUZE100_BITS_ECC_SE2) {
|
|
puts(WARNBAR);
|
|
puts("WARNING: ecc has made bit corrections in banks 6 to 10\n"
|
|
);
|
|
puts(WARNBAR);
|
|
}
|
|
if (dm_i2c_read(dev, PFUZE100_OTP_ECC_DE1, &val, 1) < 0) {
|
|
puts("i2c pmic ECC_DE register read failed\n");
|
|
return 0;
|
|
}
|
|
if (val & PFUZE100_BITS_ECC_DE1) {
|
|
puts(WARNBAR);
|
|
puts("ERROR: banks 1 to 5 have uncorrectable bits\n");
|
|
puts(WARNBAR);
|
|
}
|
|
if (dm_i2c_read(dev, PFUZE100_OTP_ECC_DE2, &val, 1) < 0) {
|
|
puts("i2c pmic ECC_DE register read failed\n");
|
|
return 0;
|
|
}
|
|
if (val & PFUZE100_BITS_ECC_DE2) {
|
|
puts(WARNBAR);
|
|
puts("ERROR: banks 6 to 10 have uncorrectable bits\n");
|
|
puts(WARNBAR);
|
|
}
|
|
|
|
/* get device ident */
|
|
if (dm_i2c_read(dev, PFUZE100_DEVICEID, &devid, 1) < 0) {
|
|
puts("i2c pmic devid read failed\n");
|
|
return 0;
|
|
}
|
|
if (dm_i2c_read(dev, PFUZE100_REVID, &revid, 1) < 0) {
|
|
puts("i2c pmic revid read failed\n");
|
|
return 0;
|
|
}
|
|
printf("device id: 0x%.2x, revision id: 0x%.2x, ", devid, revid);
|
|
|
|
/* get device programmed state */
|
|
val = PFUZE100_PAGE_REGISTER_PAGE1;
|
|
if (dm_i2c_write(dev, PFUZE100_PAGE_REGISTER, &val, 1)) {
|
|
puts("i2c write failed\n");
|
|
return 0;
|
|
}
|
|
if (dm_i2c_read(dev, PFUZE100_FUSE_POR1, &val, 1) < 0) {
|
|
puts("i2c fuse_por read failed\n");
|
|
return 0;
|
|
}
|
|
if (val & PFUZE100_FUSE_POR_M)
|
|
programmed++;
|
|
|
|
if (dm_i2c_read(dev, PFUZE100_FUSE_POR2, &val, 1) < 0) {
|
|
puts("i2c fuse_por read failed\n");
|
|
return programmed;
|
|
}
|
|
if (val & PFUZE100_FUSE_POR_M)
|
|
programmed++;
|
|
|
|
if (dm_i2c_read(dev, PFUZE100_FUSE_POR3, &val, 1) < 0) {
|
|
puts("i2c fuse_por read failed\n");
|
|
return programmed;
|
|
}
|
|
if (val & PFUZE100_FUSE_POR_M)
|
|
programmed++;
|
|
|
|
switch (programmed) {
|
|
case 0:
|
|
puts("not programmed\n");
|
|
break;
|
|
case 3:
|
|
puts("programmed\n");
|
|
break;
|
|
default:
|
|
puts("undefined programming state\n");
|
|
break;
|
|
}
|
|
|
|
/* The following is needed during production */
|
|
if (programmed != 3) {
|
|
/* set VGEN1 to 1.2V */
|
|
val = PFUZE100_VGEN1_VAL;
|
|
if (dm_i2c_write(dev, PFUZE100_VGEN1CTL, &val, 1)) {
|
|
puts("i2c write failed\n");
|
|
return programmed;
|
|
}
|
|
|
|
/* set SWBST to 5.0V */
|
|
val = PFUZE100_SWBST_VAL;
|
|
if (dm_i2c_write(dev, PFUZE100_SWBSTCTL, &val, 1))
|
|
puts("i2c write failed\n");
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
unsigned int i, j;
|
|
|
|
for (i = 0; i < 16; i++)
|
|
printf("\t%x", i);
|
|
for (j = 0; j < 0x80; ) {
|
|
printf("\n%2x", j);
|
|
for (i = 0; i < 16; i++) {
|
|
dm_i2c_read(dev, j + i, &val, 1);
|
|
printf("\t%2x", val);
|
|
}
|
|
j += 0x10;
|
|
}
|
|
printf("\nEXT Page 1");
|
|
|
|
val = PFUZE100_PAGE_REGISTER_PAGE1;
|
|
if (dm_i2c_write(dev, PFUZE100_PAGE_REGISTER, &val, 1)) {
|
|
puts("i2c write failed\n");
|
|
return 0;
|
|
}
|
|
|
|
for (j = 0x80; j < 0x100; ) {
|
|
printf("\n%2x", j);
|
|
for (i = 0; i < 16; i++) {
|
|
dm_i2c_read(dev, j + i, &val, 1);
|
|
printf("\t%2x", val);
|
|
}
|
|
j += 0x10;
|
|
}
|
|
printf("\nEXT Page 2");
|
|
|
|
val = PFUZE100_PAGE_REGISTER_PAGE2;
|
|
if (dm_i2c_write(dev, PFUZE100_PAGE_REGISTER, &val, 1)) {
|
|
puts("i2c write failed\n");
|
|
return 0;
|
|
}
|
|
|
|
for (j = 0x80; j < 0x100; ) {
|
|
printf("\n%2x", j);
|
|
for (i = 0; i < 16; i++) {
|
|
dm_i2c_read(dev, j + i, &val, 1);
|
|
printf("\t%2x", val);
|
|
}
|
|
j += 0x10;
|
|
}
|
|
printf("\n");
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
return programmed;
|
|
}
|
|
|
|
#ifndef CONFIG_SPL_BUILD
|
|
static int pf0100_prog(void)
|
|
{
|
|
int rc;
|
|
struct udevice *dev = NULL;
|
|
unsigned char bus = 1;
|
|
unsigned char val;
|
|
unsigned int i;
|
|
|
|
if (pmic_init() == 3) {
|
|
puts("PMIC already programmed, exiting\n");
|
|
return CMD_RET_FAILURE;
|
|
}
|
|
/* set up gpio to manipulate vprog, initially off */
|
|
imx_iomux_v3_setup_multiple_pads(pmic_prog_pads,
|
|
ARRAY_SIZE(pmic_prog_pads));
|
|
gpio_direction_output(PMIC_PROG_VOLTAGE, 0);
|
|
|
|
rc = i2c_get_chip_for_busnum(bus, PFUZE100_I2C_ADDR, 1, &dev);
|
|
if (rc) {
|
|
printf("failed to get device for PMIC at address 0x%x\n",
|
|
PFUZE100_I2C_ADDR);
|
|
return CMD_RET_FAILURE;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(pmic_otp_prog); i++) {
|
|
switch (pmic_otp_prog[i].cmd) {
|
|
case pmic_i2c:
|
|
val = (unsigned char) (pmic_otp_prog[i].value & 0xff);
|
|
if (dm_i2c_write(dev, pmic_otp_prog[i].reg, &val, 1)) {
|
|
printf("i2c write failed, reg 0x%2x, value 0x%2x\n",
|
|
pmic_otp_prog[i].reg, val);
|
|
return CMD_RET_FAILURE;
|
|
}
|
|
break;
|
|
case pmic_delay:
|
|
udelay(pmic_otp_prog[i].value * 1000);
|
|
break;
|
|
case pmic_vpgm:
|
|
gpio_direction_output(PMIC_PROG_VOLTAGE,
|
|
pmic_otp_prog[i].value);
|
|
break;
|
|
case pmic_pwr:
|
|
/* TODO */
|
|
break;
|
|
}
|
|
}
|
|
return CMD_RET_SUCCESS;
|
|
}
|
|
|
|
static int do_pf0100_prog(cmd_tbl_t *cmdtp, int flag, int argc,
|
|
char * const argv[])
|
|
{
|
|
int ret;
|
|
puts("Programming PMIC OTP...");
|
|
ret = pf0100_prog();
|
|
if (ret == CMD_RET_SUCCESS)
|
|
puts("done.\n");
|
|
else
|
|
puts("failed.\n");
|
|
return ret;
|
|
}
|
|
|
|
U_BOOT_CMD(
|
|
pf0100_otp_prog, 1, 0, do_pf0100_prog,
|
|
"Program the OTP fuses on the PMIC PF0100",
|
|
""
|
|
);
|
|
#endif /* CONFIG_SPL_BUILD */
|