diff options
author | Max Krummenacher <max.krummenacher@toradex.com> | 2018-09-06 18:05:09 +0200 |
---|---|---|
committer | Max Krummenacher <max.krummenacher@toradex.com> | 2020-03-05 19:22:16 +0100 |
commit | edbc70f3fa51419ac6943dfb1e50b4cc9eddc6c8 (patch) | |
tree | 6ed3c3b70b6cf4e5dfafbbf147a7ac121136b0e8 /board | |
parent | 8c41b2fafeabdd351e457a31d7252c5b73b34101 (diff) |
pf8100: add pmic fusing command
Note that this requires the SCFW in a version which provides access
to the PMIC I2C. Something which the regular SCFW should not do.
Signed-off-by: Max Krummenacher <max.krummenacher@toradex.com>
(cherry picked from commit 582a98a218dab6ac5a13ebd1cbd7a16e4b8305f3)
(cherry picked from commit 906059a7abf413bc165101812fb916743dd8eee7)
Diffstat (limited to 'board')
-rw-r--r-- | board/toradex/colibri-imx8qxp/Kconfig | 8 | ||||
-rw-r--r-- | board/toradex/colibri-imx8qxp/Makefile | 1 | ||||
-rw-r--r-- | board/toradex/colibri-imx8qxp/pf8100.c | 328 | ||||
-rw-r--r-- | board/toradex/colibri-imx8qxp/pf8100_otp.inc | 93 |
4 files changed, 430 insertions, 0 deletions
diff --git a/board/toradex/colibri-imx8qxp/Kconfig b/board/toradex/colibri-imx8qxp/Kconfig index 340fe728167..387c00b62b0 100644 --- a/board/toradex/colibri-imx8qxp/Kconfig +++ b/board/toradex/colibri-imx8qxp/Kconfig @@ -25,6 +25,14 @@ config TDX_CFG_BLOCK_PART config TDX_CFG_BLOCK_OFFSET default "-512" +config TDX_CMD_IMX_MFGR + bool "Enable factory testing commands for Toradex Colibri iMX8QXP modules" + help + This adds the commands + pf8100_otp_prog - Program the OTP fuses on the PMIC PF8100 + If executed on already fused modules it doesn't change any fuse setting. + default y + source "board/toradex/common/Kconfig" endif diff --git a/board/toradex/colibri-imx8qxp/Makefile b/board/toradex/colibri-imx8qxp/Makefile index 9e85b99179f..e0ffa88e4f1 100644 --- a/board/toradex/colibri-imx8qxp/Makefile +++ b/board/toradex/colibri-imx8qxp/Makefile @@ -5,3 +5,4 @@ # obj-y += colibri-imx8qxp.o +obj-$(CONFIG_TDX_CMD_IMX_MFGR) += pf8100.o diff --git a/board/toradex/colibri-imx8qxp/pf8100.c b/board/toradex/colibri-imx8qxp/pf8100.c new file mode 100644 index 00000000000..bc67538eb8e --- /dev/null +++ b/board/toradex/colibri-imx8qxp/pf8100.c @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2014-2016, Toradex AG + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +/* + * Helpers for Freescale PMIC PF8100 +*/ + +#include <common.h> +#include <i2c.h> +#include <asm/imx-common/sci/sci.h> +#include <asm/arch/imx8-pins.h> +#include <asm/gpio.h> +#include <asm/arch/iomux.h> + +#include "pf8100_otp.inc" + +/* Register Addresses */ +#define PF8100_DEVICEID 0x00 +#define PF8100_REVID 0x01 +#define PF8100_EMREV 0x02 +#define PF8100_PROGID 0x03 +#define PF8100_PAGE_SELECT 0x9f + +/* output some informational messages, return if device is programmed */ +/* i.e. 0: unprogrammed (progid=0xfff, 1: programmed */ +unsigned pmic_init(void); + +/* programmes OTP fuses to values required on a Toradex Apalis iMX6 */ +int pf8100_prog(void); + +/* define for PMIC register dump */ +#define DEBUG + +/* Fuse the PMIC in production + * + * TBBEN: ADC_IN2 LSIO.GPIO1.IO12 on module + * ProgVolt: SC_P_USB_SS3_TC1_GPIO4_IO04 SODIMM 133 +*/ +#define GPIO_PAD_CTRL ((SC_PAD_CONFIG_NORMAL << PADRING_CONFIG_SHIFT) | (SC_PAD_ISO_OFF << PADRING_LPCONFIG_SHIFT) \ + | (SC_PAD_28FDSOI_DSE_DV_HIGH << PADRING_DSE_SHIFT) | (SC_PAD_28FDSOI_PS_PU << PADRING_PULL_SHIFT)) +static iomux_cfg_t const pmic_prog_pads[] = { + SC_P_ADC_IN2 | MUX_MODE_ALT(4) | MUX_PAD_CTRL(GPIO_PAD_CTRL), +# define PMIC_TBBEN IMX_GPIO_NR(1, 12) + SC_P_USB_SS3_TC1 | MUX_MODE_ALT(4) | MUX_PAD_CTRL(GPIO_PAD_CTRL), +# define PMIC_PROG_VOLTAGE IMX_GPIO_NR(4, 4) +}; + + +int pmic_i2c_read(unsigned reg, unsigned *val) +{ + sc_err_t err; + err = sc_misc_set_control(SC_IPC_CH, SC_R_BOARD_R0, SC_C_PMIC_I2C_READ_REG, reg); + err |= sc_misc_get_control(SC_IPC_CH, SC_R_BOARD_R0, SC_C_PMIC_I2C, val); + return err != SC_ERR_NONE; +} + +int pmic_i2c_write(unsigned reg, unsigned val) +{ + sc_err_t err; + val = (val & 0xff) | ((reg & 0xff) << 8); + err = sc_misc_set_control(SC_IPC_CH, SC_R_BOARD_R0, SC_C_PMIC_I2C, val); + return err != SC_ERR_NONE; +} + +unsigned pmic_init(void) +{ + unsigned devid, revid, emrev, progid; + + puts("PMIC: "); + + /* get device ident */ + if (pmic_i2c_read(PF8100_DEVICEID, &devid)) { + puts("i2c pmic devid read failed\n"); + return 0; + } + if (pmic_i2c_read(PF8100_REVID, &revid) < 0) { + puts("i2c pmic revid read failed\n"); + return 0; + } + if (pmic_i2c_read(PF8100_EMREV, &emrev)) { + puts("i2c pmic emrev read failed\n"); + return 0; + } + if (pmic_i2c_read(PF8100_PROGID, &progid) < 0) { + puts("i2c pmic prog id read failed\n"); + return 0; + } + + progid |= (emrev & 0xf0) << 4; + printf("device id: 0x%.2x, revision id: 0x%.2x, emrev %x, prog id %.3x\n", + devid, revid, emrev & 7, progid); + + +#ifdef DEBUG + { + unsigned i, j, val; + + /* Set TBBEN high */ + gpio_request(PMIC_TBBEN, "PMIC_TBBEN"); + gpio_direction_output(PMIC_TBBEN, 1); + mdelay(100); + + /* Page select 1 */ + pmic_i2c_write(PF8100_PAGE_SELECT, 0); + mdelay(100); + pmic_i2c_read(PF8100_PAGE_SELECT, &val); + printf(" readback is %x\n", val); + + for (i = 0; i < 16; i++) + printf("\t%x", i); + for (j = 0; j < 0xa0; ) { + printf("\n%2x", j); + for (i = 0; i < 16; i++) { + pmic_i2c_read(j+i, &val); + printf("\t%2x", val); + } + j += 0x10; + } + + printf("\nEXT Page 1"); + /* Page select 1 */ + pmic_i2c_write(PF8100_PAGE_SELECT, 1); + mdelay(100); + pmic_i2c_read(PF8100_PAGE_SELECT, &val); + printf(" readback is %x", val); + + for (j = 0xa0; j < 0xf0; ) { + printf("\n%2x", j); + for (i = 0; i < 16; i++) { + pmic_i2c_read(j+i, &val); + printf("\t%2x", val); + } + j += 0x10; + } + + printf("\nEXT Page 2"); + /* Page select 2 */ + pmic_i2c_write(PF8100_PAGE_SELECT, 2); + mdelay(100); + pmic_i2c_read(PF8100_PAGE_SELECT, &val); + printf(" readback is %x", val); + + for (j = 0xa0; j < 0xc0; ) { + printf("\n%2x", j); + for (i = 0; i < 16; i++) { + pmic_i2c_read(j+i, &val); + printf("\t%2x", val); + } + j += 0x10; + } + printf("\n"); + + /* Set TBBEN low */ + gpio_direction_output(PMIC_TBBEN, 0); + mdelay(100); + } +#endif + + return (progid != 0xfff); +} + +int pf8100_prog(void) +{ + unsigned int err, i, read; + int ret = CMD_RET_SUCCESS; + + if (pmic_init() == 1) { + printf("PMIC already programmed, exiting\n"); + return CMD_RET_FAILURE; + } + /* set up gpio to manipulate vprog, initially off */ + imx8_iomux_setup_multiple_pads(pmic_prog_pads, ARRAY_SIZE(pmic_prog_pads)); + gpio_request(PMIC_TBBEN, "PMIC_TBBEN"); + gpio_request(PMIC_PROG_VOLTAGE, "PMIC_PROG_VOLT"); + + /* Set TBBEN high */ + gpio_direction_output(PMIC_TBBEN, 1); + mdelay(100); + + /* Page select 2 */ + if (pmic_i2c_write(PF8100_PAGE_SELECT, 2)) { + printf("PMIC i2c page select failed\n"); + ret = CMD_RET_FAILURE; + goto out1; + } + + err = pmic_i2c_read(0xA7, &read); + if (err | read) { + printf("PMIC i2c page 2, A7 != 0\n"); + ret = CMD_RET_FAILURE; + goto out1; + } + + /* PROG_VOLTAGE at 8 V */ + gpio_direction_output(PMIC_PROG_VOLTAGE, 1); + mdelay(200); + + /* Page select 1 */ + if (pmic_i2c_write(PF8100_PAGE_SELECT, 1)) { + printf("PMIC i2c page select failed\n"); + ret = CMD_RET_FAILURE; + goto out2; + } + + /* write all OTP mirror values */ + for(i = 0; i < ARRAY_SIZE(pf8100fuses); i++) + if (pmic_i2c_write(pf8100fuses[i].address, pf8100fuses[i].data)) { + printf("PMIC i2c OTP write failed at 0x%.2x\n", pf8100fuses[i].address); + ret = CMD_RET_FAILURE; + goto out2; + } + err = pmic_i2c_write(0x9F,0x02); //4.Write 0x9F -> 0x02 // Set register Page to OTP controller page + err |= pmic_i2c_write(0xA0,0x80); //5.Write 0xA0 -> 0x80 // Set OTP Controller in Idle state + err |= pmic_i2c_write(0xA1,0x00); //6.Write 0xA1 -> 0x00 // Set Start Address to 0x00 in Fuse Array + err |= pmic_i2c_write(0XA2,0x42); //7.Write 0XA2 -> 0x42 // Set Stop Address to 0x48 in Fuse Array + err |= pmic_i2c_write(0xA8,0x0F); //8.Write 0xA8 -> 0x0F // Set the Maximum number of programming attempts + err |= pmic_i2c_write(0xA9,0x02); //9.Write 0xA9 -> 0x02 // Set MRR voltage regulator + err |= pmic_i2c_write(0xAA,0x07); //10.Write 0xAA -> 0x07 //Write SC Reference 1 + err |= pmic_i2c_write(0xAB,0x09); //11.Write 0xAB -> 0x09 // Write I Reference 1 + err |= pmic_i2c_write(0xAC,0x07); //12.Write 0xAC -> 0x07 // Write SC Reference 2 + err |= pmic_i2c_write(0xAD,0x35); //13.Write 0xAD -> 0x35 // Write I Reference 2 + err |= pmic_i2c_write(0xAE,0x04); //14.Write 0xAE -> 0x04 // Set the High byte of the MR_TEST register + err |= pmic_i2c_write(0xAF,0x42); //15.Write 0xAF -> 0x42 // Set the Low byte of the MR_TEST register + err |= pmic_i2c_write(0xB0,0x00); //16.Write 0xB0 -> 0x00 // Set the High byte of the MREF_TEST register + err |= pmic_i2c_write(0xB1,0x00); //17. Write 0xB1 -> 0x00 // Set the Low byte of the MREF_TEST register + + //Calculate CRC to be written to OTP + err |= pmic_i2c_write(0xA0,0xA5); //18.Write 0xA0 -> 0xA5 // Calculate CRC values + err |= pmic_i2c_write(0xA0,0xA4); //19.Write 0xA0 -> 0xA4 // Test CRC Values + + //Verify CRC is generated -optional (CRC value is dependent of the OTP configuration) + err |= pmic_i2c_write(0x9F,0x01); //20.Write 0x9F -> 0x01 // Page select 01 + err |= pmic_i2c_read(0xE7,&read); //21.Read -> 0xE7 // Must be different than 0x00 + if (read == 0x00) + printf("ERROR register 0xE7 = 0x0\r\n"); + err |= pmic_i2c_read(0xE8,&read); //22.Read -> 0xE8 // Must be different than 0x00 + if (read == 0x00) + printf("ERROR register 0xE8 = 0x0\r\n"); + + err |= pmic_i2c_write(0x9F,0x02); //24.Write 0x9F -> 0x02 // Page select 02 + err |= pmic_i2c_read(0xA7,&read); + printf("Register 0xA7 = 0x%X\r\n",read); + err |= pmic_i2c_read(0xA6,&read); + printf("Register 0xA6 = 0x%X\r\n",read); + if (err) { + printf("Programming preparation failed! Don't starting the programming step\n"); + ret = CMD_RET_FAILURE; + goto out2; + } + +#if 0 + //Lets burn the fuses + err |= pmic_i2c_write(0x9F,0x02); //24.Write 0x9F -> 0x02 // Page select 02 + + err |= pmic_i2c_write(0xA0,0x96); //25.Write 0xA0 -> 0x96 + read = 0xFF; + mdelay(200); + do { + err |= pmic_i2c_read(0xA6,&read); //26.Read -> 0xA0 // if Bit 0 = 1 Controller is still busy. + } while((read & 0x1) == 0x1); //27.Loop step 22 until bit 0 = 0 (Controller is done) + + err |= pmic_i2c_write(0xA2,0xFF); //28.Write A2 -> 0xFF // Set mask to write to full byte. + err |= pmic_i2c_write(0xA1,0xFC); //29.Write A1 -> 0xFC // Set single Write Address to Write Protect(WP) Low Byte + err |= pmic_i2c_write(0xA3,0xAA); //30.Write A3 -> 0xAA // Set Data for single write on Write Protect(WP) Low Byte + err |= pmic_i2c_write(0xA0,0x87); //31.Write A0 -> 0x87 // Burn Write Protect low Byte + err |= pmic_i2c_write(0xA1,0xFD); //32.Write A1 -> 0xFD // Set single Write Address to Write Protect high Byte + err |= pmic_i2c_write(0xA3,0x55); //33.Write A3 -> 0x55 // Set Data for single write on Write Protect high Byte + err |= pmic_i2c_write(0xA0,0x87); //34.Write A0 -> 0x87 // Burn Write Protect low Byte + err |= pmic_i2c_write(0xA1,0xFE); //35.Write A1 -> 0xFE // Set single Write Address to Boot Enable (BE)Low Byte + err |= pmic_i2c_write(0xA3,0xAA); //36.Write A3 -> 0xAA // Set Data for single write to Boot enable(BE) Low Byte + err |= pmic_i2c_write(0xA0,0x87); //37.Write A0 -> 0x87 // Burn Boot enable Low Byte + err |= pmic_i2c_write(0xA1,0xFF); //38.Write A1 -> 0xFF // Set single Write Address to Boot Enable High Byte + err |= pmic_i2c_write(0xA3,0x55); //39.Write A3 -> 0x55 // Set Data for single write Boot enable High Byte + err |= pmic_i2c_write(0xA0,0x87); //40.Write A0 -> 0x87 // Burn Boot enable High Byte + mdelay(200); + + //41.Apply VDDOTP = 1.5V (but should be 0V) + gpio_direction_output(PMIC_PROG_VOLTAGE, 0); + mdelay(100); + + err |= pmic_i2c_read(0x0,&read); // + printf("Device ID = 0x%X\r\n",read); + err |= pmic_i2c_write(0x9F,0x2); + err |= pmic_i2c_read(0xA7,&read); // + printf("SECT_STATUS = 0x%X\r\n",read); + err |= pmic_i2c_read(0xA6,&read); // + printf("FSTATUS = 0x%X\r\n",read); + + if (err) { + printf("Programming failed!\n"); + ret = CMD_RET_FAILURE; + goto out2; + } + +#endif +out2: + gpio_direction_output(PMIC_PROG_VOLTAGE, 0); + mdelay(100); + +out1: + /* Set TBBEN low */ + gpio_direction_output(PMIC_TBBEN, 0); + mdelay(100); + + return ret; +} + +int do_pf8100_prog(cmd_tbl_t *cmdtp, int flag, int argc, + char * const argv[]) +{ + int ret; + puts("Programming PMIC OTP..."); + ret = pf8100_prog(); + if (ret == CMD_RET_SUCCESS) + puts("done.\n"); + else + puts("failed.\n"); + return ret; +} + +U_BOOT_CMD( + pf8100_otp_prog, 1, 0, do_pf8100_prog, + "Program the OTP fuses on the PMIC PF8100", + "" +); diff --git a/board/toradex/colibri-imx8qxp/pf8100_otp.inc b/board/toradex/colibri-imx8qxp/pf8100_otp.inc new file mode 100644 index 00000000000..a71291185a4 --- /dev/null +++ b/board/toradex/colibri-imx8qxp/pf8100_otp.inc @@ -0,0 +1,93 @@ +// Device Configuration: PF8100 +// Customer: Toradex AG +// Program: Colibri iMX8X +// Sample marking: PC33PF8100A0ES +// Date: 23.08.2018 +// Time: 16:56:36 +// Generated from Spreadsheet Revision: Rev 2.5 + +// converted by: +// - dos2unix A0_PF8100_S_HEX.txt +// - sed 's/^\([0-9A-Fa-f][0-9A-Fa-f]\),.\(..\)/\t{0x\1, 0x\2},/' A0_PF8100_S_HEX.txt > otp.h +// - added struct: + +typedef struct +{ + unsigned address; + unsigned data; +} t_pf8100data; + +t_pf8100data pf8100fuses[] = +{ + {0xA0, 0x00}, + {0xA1, 0x00}, + {0xA2, 0x02}, + {0xA3, 0x45}, + {0xA4, 0x01}, + {0xA5, 0x80}, + {0xA6, 0x0B}, + {0xA7, 0x00}, + {0xA8, 0x30}, + {0xA9, 0x07}, + {0xAA, 0xAF}, + {0xAB, 0x00}, + {0xAC, 0x0F}, + {0xAD, 0x00}, + {0xAE, 0x81}, + {0xAF, 0x02}, + {0xB0, 0x15}, + {0xB1, 0x00}, + {0xB2, 0x60}, + {0xB3, 0x01}, + {0xB4, 0x53}, + {0xB5, 0x3A}, + {0xB6, 0x60}, + {0xB7, 0x01}, + {0xB8, 0x53}, + {0xB9, 0x1A}, + {0xBA, 0x70}, + {0xBB, 0x00}, + {0xBC, 0x53}, + {0xBD, 0x02}, + {0xBE, 0x70}, + {0xBF, 0x00}, + {0xC0, 0x53}, + {0xC1, 0x0A}, + {0xC2, 0x70}, + {0xC3, 0x03}, + {0xC4, 0x53}, + {0xC5, 0x12}, + {0xC6, 0xB1}, + {0xC7, 0x03}, + {0xC8, 0x53}, + {0xC9, 0x22}, + {0xCA, 0x15}, + {0xCB, 0x00}, + {0xCC, 0x53}, + {0xCD, 0x22}, + {0xCE, 0x52}, + {0xCF, 0x01}, + {0xD0, 0x04}, + {0xD1, 0x5B}, + {0xD2, 0x03}, + {0xD3, 0x24}, + {0xD4, 0x5B}, + {0xD5, 0x00}, + {0xD6, 0x05}, + {0xD7, 0x5B}, + {0xD8, 0x03}, + {0xD9, 0x05}, + {0xDA, 0x02}, + {0xDB, 0x00}, + {0xDC, 0x00}, + {0xDD, 0x00}, + {0xDE, 0x00}, + {0xDF, 0x00}, + {0xE0, 0x00}, + {0xE1, 0x00}, + {0xE2, 0x00}, + {0xE3, 0x01}, + {0xE4, 0x00}, + {0xE5, 0x00}, + {0xE6, 0x00}, +}; |