[U-Boot] [PATCH 1/5] spi_sf: Skip the erasing of protected sectors

Many SPI flashes have protection bits (BP2, BP1 and BP0) in the status register that can protect selected regions of the SPI NOR.
Take these bits into account when performing erase operations, making sure that the protected areas are skipped.
Introduce the CONFIG_SPI_NOR_PROTECTION option that can be selected by systems that want to protect selected regions of SPI NOR flash.
Signed-off-by: Otavio Salvador otavio@ossystems.com.br ---
README | 4 ++ drivers/mtd/spi/sf_ops.c | 91 ++++++++++++++++++++++++++++++++++++++++++++-- drivers/mtd/spi/sf_probe.c | 2 + 3 files changed, 94 insertions(+), 3 deletions(-)
diff --git a/README b/README index 1acc355..18d61a6 100644 --- a/README +++ b/README @@ -2751,6 +2751,10 @@ CBFS (Coreboot Filesystem) support Timeout for waiting until spi transfer completed. default: (CONFIG_SYS_HZ/100) /* 10 ms */
+ CONFIG_SPI_NOR_PROTECTION + Enable the built-in protection mechanism provided by the + BP2, BP1 and BP0 bits from the status register. + - FPGA Support: CONFIG_FPGA
Enables FPGA subsystem. diff --git a/drivers/mtd/spi/sf_ops.c b/drivers/mtd/spi/sf_ops.c index 900ec1f..a7a07f9 100644 --- a/drivers/mtd/spi/sf_ops.c +++ b/drivers/mtd/spi/sf_ops.c @@ -264,6 +264,84 @@ int spi_flash_write_common(struct spi_flash *flash, const u8 *cmd, return ret; }
+#ifdef CONFIG_SPI_NOR_PROTECTION +static int is_protected(struct spi_flash *flash, u32 offset) +{ + int total_sector, ret, protected_sector; + bool *protected; + u8 sr; + + total_sector = flash->size / flash->erase_size; + protected = calloc(1, total_sector); + if (!protected) + return -ENOMEM; + + ret = spi_flash_cmd_read_status(flash, &sr); + if (ret < 0) + goto free_protected; + + /* Extract the protection bits: BP2, BP1 and BP0 */ + sr = (sr & 0x1c) >> 2; + + switch (sr) { + case 0: + protected_sector = 0; + break; + + case 1: + protected_sector = total_sector / 64; + break; + + case 2: + protected_sector = total_sector / 32; + break; + + case 3: + protected_sector = total_sector / 16; + break; + + case 4: + protected_sector = total_sector / 8; + break; + + case 5: + protected_sector = total_sector / 4; + break; + + case 6: + protected_sector = total_sector / 2; + break; + + case 7: + protected_sector = total_sector; + break; + + default: + ret = -EINVAL; + goto free_protected; + } + + while (protected_sector) { + protected[total_sector - protected_sector] = 1; + protected_sector--; + } + + if (protected[offset/flash->erase_size]) + return 1; + else + return 0; + +free_protected: + free(protected); + return ret; +} +#else +static int is_protected(struct spi_flash *flash, u32 offset) +{ + return 0; +} +#endif + int spi_flash_cmd_erase_ops(struct spi_flash *flash, u32 offset, size_t len) { u32 erase_size, erase_addr; @@ -294,10 +372,17 @@ int spi_flash_cmd_erase_ops(struct spi_flash *flash, u32 offset, size_t len) debug("SF: erase %2x %2x %2x %2x (%x)\n", cmd[0], cmd[1], cmd[2], cmd[3], erase_addr);
- ret = spi_flash_write_common(flash, cmd, sizeof(cmd), NULL, 0); - if (ret < 0) { - debug("SF: erase failed\n"); + if (is_protected(flash, offset) > 0) { + printf("Skipping to erase protected offset 0x%x\n", + offset); break; + } else { + ret = spi_flash_write_common(flash, cmd, sizeof(cmd), + NULL, 0); + if (ret < 0) { + debug("SF: erase failed\n"); + break; + } }
offset += erase_size; diff --git a/drivers/mtd/spi/sf_probe.c b/drivers/mtd/spi/sf_probe.c index 954376d..49c8b78 100644 --- a/drivers/mtd/spi/sf_probe.c +++ b/drivers/mtd/spi/sf_probe.c @@ -256,12 +256,14 @@ static int spi_flash_validate_params(struct spi_slave *spi, u8 *idcode, } #endif
+#if !defined(CONFIG_SPI_NOR_PROTECTION) /* Flash powers up read-only, so clear BP# bits */ #if defined(CONFIG_SPI_FLASH_ATMEL) || \ defined(CONFIG_SPI_FLASH_MACRONIX) || \ defined(CONFIG_SPI_FLASH_SST) spi_flash_cmd_write_status(flash, 0); #endif +#endif
return 0; }

Add a prototype for spi_flash_cmd_write_status(), so that it can be used by other files.
Signed-off-by: Otavio Salvador otavio@ossystems.com.br ---
include/spi_flash.h | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/include/spi_flash.h b/include/spi_flash.h index 3b2d555..4b13926 100644 --- a/include/spi_flash.h +++ b/include/spi_flash.h @@ -232,4 +232,6 @@ static inline int spi_flash_erase(struct spi_flash *flash, u32 offset, void spi_boot(void) __noreturn; void spi_spl_load_image(uint32_t offs, unsigned int size, void *vdst);
+int spi_flash_cmd_write_status(struct spi_flash *flash, u8 ws); + #endif /* _SPI_FLASH_H_ */

Add SPI NOR support:
=> sf probe SF: Detected SST25VF032B with page size 256 Bytes, erase size 4 KiB, total 4 MiB
Signed-off-by: Otavio Salvador otavio@ossystems.com.br ---
board/congatec/cgtqmx6eval/cgtqmx6eval.c | 23 +++++++++++++++++++++++ include/configs/cgtqmx6eval.h | 10 ++++++++++ 2 files changed, 33 insertions(+)
diff --git a/board/congatec/cgtqmx6eval/cgtqmx6eval.c b/board/congatec/cgtqmx6eval/cgtqmx6eval.c index 574891e..a9694d5 100644 --- a/board/congatec/cgtqmx6eval/cgtqmx6eval.c +++ b/board/congatec/cgtqmx6eval/cgtqmx6eval.c @@ -45,6 +45,10 @@ DECLARE_GLOBAL_DATA_PTR; PAD_CTL_DSE_40ohm | PAD_CTL_HYS | \ PAD_CTL_ODE | PAD_CTL_SRE_FAST)
+#define SPI_PAD_CTRL (PAD_CTL_HYS | \ + PAD_CTL_SPEED_MED | \ + PAD_CTL_DSE_40ohm | PAD_CTL_SRE_FAST) + #define MX6Q_QMX6_PFUZE_MUX IMX_GPIO_NR(6, 9)
@@ -152,6 +156,13 @@ static iomux_v3_cfg_t enet_pads_ar8035[] = { MX6_PAD_RGMII_RX_CTL__RGMII_RX_CTL | MUX_PAD_CTRL(ENET_PAD_CTRL), };
+static iomux_v3_cfg_t const ecspi1_pads[] = { + MX6_PAD_EIM_D16__ECSPI1_SCLK | MUX_PAD_CTRL(SPI_PAD_CTRL), + MX6_PAD_EIM_D17__ECSPI1_MISO | MUX_PAD_CTRL(SPI_PAD_CTRL), + MX6_PAD_EIM_D18__ECSPI1_MOSI | MUX_PAD_CTRL(SPI_PAD_CTRL), + MX6_PAD_EIM_D19__GPIO3_IO19 | MUX_PAD_CTRL(NO_PAD_CTRL), +}; + #define PC MUX_PAD_CTRL(I2C_PAD_CTRL) struct i2c_pads_info i2c_pad_info1 = { .scl = { @@ -382,6 +393,12 @@ static void setup_iomux_uart(void) imx_iomux_v3_setup_multiple_pads(uart2_pads, ARRAY_SIZE(uart2_pads)); }
+void setup_spinor(void) +{ + imx_iomux_v3_setup_multiple_pads(ecspi1_pads, ARRAY_SIZE(ecspi1_pads)); + gpio_direction_output(IMX_GPIO_NR(3, 19), 0); +} + #ifdef CONFIG_FSL_ESDHC static struct fsl_esdhc_cfg usdhc_cfg[] = { {USDHC2_BASE_ADDR}, @@ -647,6 +664,7 @@ int board_early_init_f(void) { setup_iomux_uart(); setup_display(); + setup_spinor();
return 0; } @@ -672,6 +690,11 @@ int checkboard(void) return 0; }
+int board_spi_cs_gpio(unsigned bus, unsigned cs) +{ + return (bus == 0 && cs == 0) ? (IMX_GPIO_NR(3, 19)) : -EINVAL; +} + #ifdef CONFIG_CMD_BMODE static const struct boot_mode board_boot_modes[] = { /* 4 bit bus width */ diff --git a/include/configs/cgtqmx6eval.h b/include/configs/cgtqmx6eval.h index 92930c8..d0d098a 100644 --- a/include/configs/cgtqmx6eval.h +++ b/include/configs/cgtqmx6eval.h @@ -29,6 +29,16 @@ /* MMC Configs */ #define CONFIG_SYS_FSL_ESDHC_ADDR 0
+/* SPI NOR */ +#define CONFIG_CMD_SF +#define CONFIG_SPI_FLASH +#define CONFIG_SPI_FLASH_STMICRO +#define CONFIG_SPI_FLASH_SST +#define CONFIG_MXC_SPI +#define CONFIG_SF_DEFAULT_BUS 0 +#define CONFIG_SF_DEFAULT_SPEED 20000000 +#define CONFIG_SF_DEFAULT_MODE (SPI_MODE_0) + /* Miscellaneous commands */ #define CONFIG_CMD_BMODE

Congatec boards boot from SPI NOR, so it makes more sense to use SPI NOR to store the environment variables.
Signed-off-by: Otavio Salvador otavio@ossystems.com.br ---
include/configs/cgtqmx6eval.h | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/include/configs/cgtqmx6eval.h b/include/configs/cgtqmx6eval.h index d0d098a..66f81ec 100644 --- a/include/configs/cgtqmx6eval.h +++ b/include/configs/cgtqmx6eval.h @@ -232,11 +232,21 @@ (CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)
/* Environment organization */ -#define CONFIG_ENV_SIZE (8 * 1024) - -#define CONFIG_ENV_IS_IN_MMC - +#if defined (CONFIG_ENV_IS_IN_MMC) #define CONFIG_ENV_OFFSET (6 * 64 * 1024) #define CONFIG_SYS_MMC_ENV_DEV 0 +#endif + +#define CONFIG_ENV_SIZE (8 * 1024) + +#define CONFIG_ENV_IS_IN_SPI_FLASH +#if defined(CONFIG_ENV_IS_IN_SPI_FLASH) +#define CONFIG_ENV_OFFSET (768 * 1024) +#define CONFIG_ENV_SECT_SIZE (64 * 1024) +#define CONFIG_ENV_SPI_BUS CONFIG_SF_DEFAULT_BUS +#define CONFIG_ENV_SPI_CS CONFIG_SF_DEFAULT_CS +#define CONFIG_ENV_SPI_MODE CONFIG_SF_DEFAULT_MODE +#define CONFIG_ENV_SPI_MAX_HZ CONFIG_SF_DEFAULT_SPEED +#endif
#endif /* __CONFIG_CGTQMX6EVAL_H */

The last 16 KiB of the SPI NOR contain some manufacturing information, which should not be erased/overwritten.
Configure the protection bits BP2, BP1 and BP0 so that this region is protected.
Signed-off-by: Otavio Salvador otavio@ossystems.com.br ---
board/congatec/cgtqmx6eval/cgtqmx6eval.c | 25 +++++++++++++++++++++++++ include/configs/cgtqmx6eval.h | 2 ++ 2 files changed, 27 insertions(+)
diff --git a/board/congatec/cgtqmx6eval/cgtqmx6eval.c b/board/congatec/cgtqmx6eval/cgtqmx6eval.c index a9694d5..9aff08d 100644 --- a/board/congatec/cgtqmx6eval/cgtqmx6eval.c +++ b/board/congatec/cgtqmx6eval/cgtqmx6eval.c @@ -31,6 +31,8 @@ #include <miiphy.h> #include <netdev.h> #include <micrel.h> +#include <spi_flash.h> +#include <spi.h>
DECLARE_GLOBAL_DATA_PTR;
@@ -399,6 +401,22 @@ void setup_spinor(void) gpio_direction_output(IMX_GPIO_NR(3, 19), 0); }
+static void spinor_protect(void) +{ + struct spi_flash *spi; + + spi = spi_flash_probe(CONFIG_ENV_SPI_BUS, CONFIG_ENV_SPI_CS, + CONFIG_ENV_SPI_MAX_HZ, CONFIG_ENV_SPI_MODE); + /* + * Set BP2 BP1 BP0 to 001, so that the last 64 sectors + * can be protected (0x3f0000 to 0x3fffff). + * + * This area stores some manufacturing information that + * should not be erased. + */ + spi_flash_cmd_write_status(spi, 1 << 2); +} + #ifdef CONFIG_FSL_ESDHC static struct fsl_esdhc_cfg usdhc_cfg[] = { {USDHC2_BASE_ADDR}, @@ -711,3 +729,10 @@ int misc_init_r(void) #endif return 0; } + +int board_late_init(void) +{ + spinor_protect(); + + return 0; +} diff --git a/include/configs/cgtqmx6eval.h b/include/configs/cgtqmx6eval.h index 66f81ec..64ee6f8 100644 --- a/include/configs/cgtqmx6eval.h +++ b/include/configs/cgtqmx6eval.h @@ -21,6 +21,7 @@ #define CONFIG_SYS_MALLOC_LEN (10 * 1024 * 1024)
#define CONFIG_BOARD_EARLY_INIT_F +#define CONFIG_BOARD_LATE_INIT #define CONFIG_MISC_INIT_R
#define CONFIG_MXC_UART @@ -34,6 +35,7 @@ #define CONFIG_SPI_FLASH #define CONFIG_SPI_FLASH_STMICRO #define CONFIG_SPI_FLASH_SST +#define CONFIG_SPI_NOR_PROTECTION #define CONFIG_MXC_SPI #define CONFIG_SF_DEFAULT_BUS 0 #define CONFIG_SF_DEFAULT_SPEED 20000000

On 2 September 2015 at 20:11, Otavio Salvador otavio@ossystems.com.br wrote:
Many SPI flashes have protection bits (BP2, BP1 and BP0) in the status register that can protect selected regions of the SPI NOR.
Take these bits into account when performing erase operations, making sure that the protected areas are skipped.
Introduce the CONFIG_SPI_NOR_PROTECTION option that can be selected by systems that want to protect selected regions of SPI NOR flash.
Signed-off-by: Otavio Salvador otavio@ossystems.com.br
README | 4 ++ drivers/mtd/spi/sf_ops.c | 91 ++++++++++++++++++++++++++++++++++++++++++++-- drivers/mtd/spi/sf_probe.c | 2 + 3 files changed, 94 insertions(+), 3 deletions(-)
diff --git a/README b/README index 1acc355..18d61a6 100644 --- a/README +++ b/README @@ -2751,6 +2751,10 @@ CBFS (Coreboot Filesystem) support Timeout for waiting until spi transfer completed. default: (CONFIG_SYS_HZ/100) /* 10 ms */
CONFIG_SPI_NOR_PROTECTION
Enable the built-in protection mechanism provided by the
BP2, BP1 and BP0 bits from the status register.
Does these bits specific for micron flash?
FPGA Support: CONFIG_FPGA
Enables FPGA subsystem.
diff --git a/drivers/mtd/spi/sf_ops.c b/drivers/mtd/spi/sf_ops.c index 900ec1f..a7a07f9 100644 --- a/drivers/mtd/spi/sf_ops.c +++ b/drivers/mtd/spi/sf_ops.c @@ -264,6 +264,84 @@ int spi_flash_write_common(struct spi_flash *flash, const u8 *cmd, return ret; }
+#ifdef CONFIG_SPI_NOR_PROTECTION +static int is_protected(struct spi_flash *flash, u32 offset) +{
int total_sector, ret, protected_sector;
bool *protected;
u8 sr;
total_sector = flash->size / flash->erase_size;
protected = calloc(1, total_sector);
if (!protected)
return -ENOMEM;
ret = spi_flash_cmd_read_status(flash, &sr);
if (ret < 0)
goto free_protected;
/* Extract the protection bits: BP2, BP1 and BP0 */
sr = (sr & 0x1c) >> 2;
Does this masking specific to particular flash part or it's generic way, please elaborate.
switch (sr) {
case 0:
protected_sector = 0;
break;
case 1:
protected_sector = total_sector / 64;
break;
case 2:
protected_sector = total_sector / 32;
break;
case 3:
protected_sector = total_sector / 16;
break;
case 4:
protected_sector = total_sector / 8;
break;
case 5:
protected_sector = total_sector / 4;
break;
case 6:
protected_sector = total_sector / 2;
break;
case 7:
protected_sector = total_sector;
break;
default:
ret = -EINVAL;
goto free_protected;
}
while (protected_sector) {
protected[total_sector - protected_sector] = 1;
protected_sector--;
}
if (protected[offset/flash->erase_size])
return 1;
else
return 0;
+free_protected:
free(protected);
return ret;
+} +#else +static int is_protected(struct spi_flash *flash, u32 offset) +{
return 0;
+} +#endif
int spi_flash_cmd_erase_ops(struct spi_flash *flash, u32 offset, size_t len) { u32 erase_size, erase_addr; @@ -294,10 +372,17 @@ int spi_flash_cmd_erase_ops(struct spi_flash *flash, u32 offset, size_t len) debug("SF: erase %2x %2x %2x %2x (%x)\n", cmd[0], cmd[1], cmd[2], cmd[3], erase_addr);
ret = spi_flash_write_common(flash, cmd, sizeof(cmd), NULL, 0);
if (ret < 0) {
debug("SF: erase failed\n");
if (is_protected(flash, offset) > 0) {
printf("Skipping to erase protected offset 0x%x\n",
offset); break;
} else {
ret = spi_flash_write_common(flash, cmd, sizeof(cmd),
NULL, 0);
if (ret < 0) {
debug("SF: erase failed\n");
break;
}
So this seems to be locking sector area which is been protected, why it's needed on erase? why can't we do it while probe? any side effects. and also do we have any way to unlock the protected sectors? does that really a need?
} offset += erase_size;
diff --git a/drivers/mtd/spi/sf_probe.c b/drivers/mtd/spi/sf_probe.c index 954376d..49c8b78 100644 --- a/drivers/mtd/spi/sf_probe.c +++ b/drivers/mtd/spi/sf_probe.c @@ -256,12 +256,14 @@ static int spi_flash_validate_params(struct spi_slave *spi, u8 *idcode, } #endif
+#if !defined(CONFIG_SPI_NOR_PROTECTION) /* Flash powers up read-only, so clear BP# bits */ #if defined(CONFIG_SPI_FLASH_ATMEL) || \ defined(CONFIG_SPI_FLASH_MACRONIX) || \ defined(CONFIG_SPI_FLASH_SST) spi_flash_cmd_write_status(flash, 0); #endif +#endif
return 0;
}
2.5.1
thanks!

On Thu, Sep 3, 2015 at 6:35 AM, Jagan Teki jteki@openedev.com wrote:
On 2 September 2015 at 20:11, Otavio Salvador otavio@ossystems.com.br wrote:
Many SPI flashes have protection bits (BP2, BP1 and BP0) in the status register that can protect selected regions of the SPI NOR.
Take these bits into account when performing erase operations, making sure that the protected areas are skipped.
Introduce the CONFIG_SPI_NOR_PROTECTION option that can be selected by systems that want to protect selected regions of SPI NOR flash.
Signed-off-by: Otavio Salvador otavio@ossystems.com.br
README | 4 ++ drivers/mtd/spi/sf_ops.c | 91 ++++++++++++++++++++++++++++++++++++++++++++-- drivers/mtd/spi/sf_probe.c | 2 + 3 files changed, 94 insertions(+), 3 deletions(-)
diff --git a/README b/README index 1acc355..18d61a6 100644 --- a/README +++ b/README @@ -2751,6 +2751,10 @@ CBFS (Coreboot Filesystem) support Timeout for waiting until spi transfer completed. default: (CONFIG_SYS_HZ/100) /* 10 ms */
CONFIG_SPI_NOR_PROTECTION
Enable the built-in protection mechanism provided by the
BP2, BP1 and BP0 bits from the status register.
Does these bits specific for micron flash?
They seem to be generic. I tested this on two part numbers from different vendors: Micron M25P32 and SST25VF032B. In this case the protection bits (BP) have the same meaning/layout.
If you prefer we can call this option CONFIG_SPI_NOR_PROTECTION_STM in case we have SPI NOR flashes with a different BP bits structure as done in the kernel.
FPGA Support: CONFIG_FPGA
Enables FPGA subsystem.
diff --git a/drivers/mtd/spi/sf_ops.c b/drivers/mtd/spi/sf_ops.c index 900ec1f..a7a07f9 100644 --- a/drivers/mtd/spi/sf_ops.c +++ b/drivers/mtd/spi/sf_ops.c @@ -264,6 +264,84 @@ int spi_flash_write_common(struct spi_flash *flash, const u8 *cmd, return ret; }
+#ifdef CONFIG_SPI_NOR_PROTECTION +static int is_protected(struct spi_flash *flash, u32 offset) +{
int total_sector, ret, protected_sector;
bool *protected;
u8 sr;
total_sector = flash->size / flash->erase_size;
protected = calloc(1, total_sector);
if (!protected)
return -ENOMEM;
ret = spi_flash_cmd_read_status(flash, &sr);
if (ret < 0)
goto free_protected;
/* Extract the protection bits: BP2, BP1 and BP0 */
sr = (sr & 0x1c) >> 2;
Does this masking specific to particular flash part or it's generic way, please elaborate.
Seems to be generic.
switch (sr) {
case 0:
protected_sector = 0;
break;
case 1:
protected_sector = total_sector / 64;
break;
case 2:
protected_sector = total_sector / 32;
break;
case 3:
protected_sector = total_sector / 16;
break;
case 4:
protected_sector = total_sector / 8;
break;
case 5:
protected_sector = total_sector / 4;
break;
case 6:
protected_sector = total_sector / 2;
break;
case 7:
protected_sector = total_sector;
break;
default:
ret = -EINVAL;
goto free_protected;
}
while (protected_sector) {
protected[total_sector - protected_sector] = 1;
protected_sector--;
}
if (protected[offset/flash->erase_size])
return 1;
else
return 0;
+free_protected:
free(protected);
return ret;
+} +#else +static int is_protected(struct spi_flash *flash, u32 offset) +{
return 0;
+} +#endif
int spi_flash_cmd_erase_ops(struct spi_flash *flash, u32 offset, size_t len) { u32 erase_size, erase_addr; @@ -294,10 +372,17 @@ int spi_flash_cmd_erase_ops(struct spi_flash *flash, u32 offset, size_t len) debug("SF: erase %2x %2x %2x %2x (%x)\n", cmd[0], cmd[1], cmd[2], cmd[3], erase_addr);
ret = spi_flash_write_common(flash, cmd, sizeof(cmd), NULL, 0);
if (ret < 0) {
debug("SF: erase failed\n");
if (is_protected(flash, offset) > 0) {
printf("Skipping to erase protected offset 0x%x\n",
offset); break;
} else {
ret = spi_flash_write_common(flash, cmd, sizeof(cmd),
NULL, 0);
if (ret < 0) {
debug("SF: erase failed\n");
break;
}
So this seems to be locking sector area which is been protected, why it's needed on erase? why can't we do it while probe? any side
We do not want to erase a sector that is protected, so that's why we skip it during the erase process.
effects. and also do we have any way to unlock the protected sectors? does that really a need?
Yes, we can lock and unlock sectors using the spi_flash_cmd_write_status() function. Please see patch 5/5.

On Wed, Sep 2, 2015 at 11:41 AM, Otavio Salvador otavio@ossystems.com.br wrote:
Many SPI flashes have protection bits (BP2, BP1 and BP0) in the status register that can protect selected regions of the SPI NOR.
Take these bits into account when performing erase operations, making sure that the protected areas are skipped.
Introduce the CONFIG_SPI_NOR_PROTECTION option that can be selected by systems that want to protect selected regions of SPI NOR flash.
Signed-off-by: Otavio Salvador otavio@ossystems.com.br
I was able to test it and the SPI NOR protection worked on a mx6qsabresd with a M25P32:
Tested-by: Fabio Estevam fabio.estevam@freescale.com
participants (4)
-
Fabio Estevam
-
Jagan Teki
-
Otavio Salvador
-
Otavio Salvador