Commit 427d84f4 authored by Pavel Boldin's avatar Pavel Boldin

RT #90499: STM32F4X9-SOM NOR/SDRAM errata 2.8.7 fix

Copy image from NOR to SDRAM through On-Chip RAM buffer
setting SDRAM to Self-Refresh on each copy.

Implement flash_read16, flash_write16 and flash_write_buffer
to disable/enable SDRAM on flash access.

Start and immediately put SDRAM in self-refresh on start-up for flash
to work correctly.
parent f83d9706
......@@ -35,6 +35,9 @@
#if CONFIG_SYS_BOARD_REV == 0x2A
# include <asm/arch/fmc.h>
# include <flash.h>
# include <asm/io.h>
# include <asm/system.h>
#endif
#include <asm/arch/fsmc.h>
......@@ -217,16 +220,56 @@ out:
int board_init(void)
{
int rv;
#if CONFIG_SYS_BOARD_REV == 0x2A
int i;
char v;
#endif
rv = fmc_fsmc_setup_gpio();
if (rv)
return rv;
#if !defined(CONFIG_SYS_NO_FLASH)
#if CONFIG_SYS_BOARD_REV == 0x2A
/* Disable first bank */
fsmc_nor_psram_init(1, 0, 0, 0);
fsmc_nor_psram_init(3, 0, 0, 0);
fsmc_nor_psram_init(4, 0, 0, 0);
/*
* Put SDRAM in Self-refresh mode to workaround
* bug with Flash/SDRAM accessing,
* see errata 2.8.7
*/
STM32_RCC->ahb3enr |= 1;
__asm__ __volatile__ ("dsb" : : : "memory");
STM32_SDRAM_FMC->sdcr1 =
CONFIG_SYS_RAM_FREQ_DIV << FMC_SDCR_SDCLK_SHIFT;
STM32_SDRAM_FMC->sdcmr = FMC_SDCMR_BANK_1 | FMC_SDCMR_MODE_START_CLOCK;
FMC_BUSY_WAIT();
STM32_SDRAM_FMC->sdcmr = FMC_SDCMR_BANK_1 | FMC_SDCMR_MODE_SELFREFRESH;
FMC_BUSY_WAIT();
udelay(60);
#endif
if ((rv = fsmc_nor_psram_init(CONFIG_SYS_FLASH_CS, CONFIG_SYS_FSMC_FLASH_BCR,
CONFIG_SYS_FSMC_FLASH_BTR,
CONFIG_SYS_FSMC_FLASH_BWTR)))
return rv;
#if CONFIG_SYS_BOARD_REV == 0x2A
for (i = 1; i < 0x1000000; i <<= 1) {
v = *(volatile char*)(0x64000000 + i);
v = *(volatile char*)(0x64000000 + i - 1);
nop(); nop();
nop(); nop();
nop(); nop();
}
#endif
#endif
return 0;
......@@ -336,6 +379,8 @@ out:
*/
#define STM32_RCC_ENR_FMC (1 << 0) /* FMC module clock */
static int dram_initialized = 0;
static inline u32 _ns2clk(u32 ns, u32 freq)
{
......@@ -398,7 +443,7 @@ int dram_init(void)
SDRAM_MWID << FMC_SDCR_MWID_SHIFT |
SDRAM_NR << FMC_SDCR_NR_SHIFT |
SDRAM_NC << FMC_SDCR_NC_SHIFT |
1 << FMC_SDCR_RPIPE_SHIFT |
0 << FMC_SDCR_RPIPE_SHIFT |
1 << FMC_SDCR_RBURST_SHIFT
);
......@@ -415,10 +460,12 @@ int dram_init(void)
STM32_SDRAM_FMC->sdcmr = FMC_SDCMR_BANK_1 | FMC_SDCMR_MODE_START_CLOCK;
udelay(200); /* 200 us delay, page 10, "Power-Up" */
FMC_BUSY_WAIT();
STM32_SDRAM_FMC->sdcmr = FMC_SDCMR_BANK_1 | FMC_SDCMR_MODE_PRECHARGE;
udelay(100);
FMC_BUSY_WAIT();
STM32_SDRAM_FMC->sdcmr = (
FMC_SDCMR_BANK_1 | FMC_SDCMR_MODE_AUTOREFRESH |
......@@ -426,6 +473,8 @@ int dram_init(void)
);
udelay(100);
FMC_BUSY_WAIT();
#define SDRAM_MODE_BL_SHIFT 0
#define SDRAM_MODE_CAS_SHIFT 4
......@@ -441,7 +490,11 @@ int dram_init(void)
udelay(100);
STM32_SDRAM_FMC->sdcmr = FMC_SDCMR_BANK_1;
FMC_BUSY_WAIT();
STM32_SDRAM_FMC->sdcmr = FMC_SDCMR_BANK_1 | FMC_SDCMR_MODE_NORMAL;
FMC_BUSY_WAIT();
/* Refresh timer */
STM32_SDRAM_FMC->sdrtr = SDRAM_TREF;
......@@ -456,8 +509,127 @@ int dram_init(void)
cortex_m3_mpu_full_access();
dram_initialized = 1;
return rv;
}
/*
* STM32 Flash bug workaround.
*/
extern char _mem_ram_buf_base, _mem_ram_buf_size;
#define SOC_RAM_BUFFER_BASE (ulong)(&_mem_ram_buf_base)
#define SOC_RAM_BUFFER_SIZE (ulong)((&_mem_ram_buf_size) - 0x100)
void stop_ram(void)
{
if (!dram_initialized)
return;
STM32_SDRAM_FMC->sdcmr = FMC_SDCMR_BANK_1 | FMC_SDCMR_MODE_SELFREFRESH;
FMC_BUSY_WAIT();
}
void start_ram(void)
{
if (!dram_initialized)
return;
/*
* Precharge according to chip requirement, page 12.
*/
STM32_SDRAM_FMC->sdcmr = FMC_SDCMR_BANK_1 | FMC_SDCMR_MODE_PRECHARGE;
FMC_BUSY_WAIT();
STM32_SDRAM_FMC->sdcmr = FMC_SDCMR_BANK_1 | FMC_SDCMR_MODE_NORMAL;
FMC_BUSY_WAIT();
udelay(60);
}
#define NOP10() do { nop(); nop(); nop(); nop(); nop(); \
nop(); nop(); nop(); nop(); nop(); \
} while(0);
u16 flash_read16(void *addr)
{
u16 value;
stop_ram();
value = __raw_readw(addr);
NOP10();
start_ram();
return value;
}
void flash_write16(u16 value, void *addr)
{
stop_ram();
__raw_writew(value, addr);
NOP10();
NOP10();
start_ram();
}
__attribute__((noinline)) void copy_one(volatile u16* src, volatile u16* dst)
{
*dst = *src;
}
u32 flash_write_buffer(void *src, void *dst, int cnt, int portwidth)
{
u32 retval = 0;
if (portwidth != FLASH_CFI_16BIT) {
retval = ERR_INVAL;
goto out;
}
memcpy((void*)SOC_RAM_BUFFER_BASE, (void*)src, cnt * portwidth);
stop_ram();
__asm__ __volatile__("": : :"memory");
src = (void*) SOC_RAM_BUFFER_BASE;
while(cnt-- > 0) {
copy_one(src, dst);
src += 2, dst += 2;
NOP10();
NOP10();
}
__asm__ __volatile__("": : :"memory");
start_ram();
out:
return retval;
}
u32 flash_check_flag(void *src, void *dst, int cnt, int portwidth)
{
u32 flag = 1;
if (portwidth != FLASH_CFI_16BIT) {
flag = 0;
goto out;
}
stop_ram();
while((cnt-- > 0) && (flag == 1)) {
flag = *(u16*)dst == 0xFFFF;
dst += 2;
}
start_ram();
out:
return flag;
}
#endif /* CONFIG_SYS_BOARD_REV == 0x2A */
......
......@@ -30,6 +30,9 @@ include $(TOPDIR)/config.mk
LIB = $(obj)lib$(SOC).a
COBJS := clock.o cpu.o envm.o wdt.o fsmc.o
ifeq ($(CONFIG_CMD_BUFCOPY),y)
COBJS += cmd_bufcopy.o
endif
SOBJS :=
SRCS := $(COBJS:.o=.c)
......
/*
* (C) Copyright 2013
* Pavel Boldin, Emcraft Systems, paboldin@emcraft.com
*
* This is command to do a safe copy from NOR flash into SDRAM through
* SRAM buffer to workaround errata 2.8.7
* (SDRAM and NOR flash can't be accessed simultaneously).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <common.h>
#include <command.h>
#include <string.h>
#include <asm/arch/fmc.h>
#include <asm/errno.h>
/*
* Base address and the length of buffer in internal RAM, which
* we use as a source for buffer to be written to eNVM.
* Size of this buffer is set in the configuration file, address
* (position within internal RAM) is calculated automatically, at
* linking stage. Thus, the appropriate symbols are exported from
* the linker script *.lds.
*/
extern char _mem_ram_buf_base, _mem_ram_buf_size;
#define SOC_RAM_BUFFER_BASE (ulong)(&_mem_ram_buf_base)
#define SOC_RAM_BUFFER_SIZE (ulong)((&_mem_ram_buf_size) - 0x100)
int nor_sdram_bufcopy_onebuf(ulong dst, ulong src, ulong size)
{
int ret = 0;
if (size > SOC_RAM_BUFFER_SIZE) {
ret = -ENOMEM;
goto out;
}
/*
* Switch memory to self-refresh mode.
* Controller issues PALL command automatically before that.
*/
STM32_SDRAM_FMC->sdcmr = FMC_SDCMR_BANK_1 | FMC_SDCMR_MODE_SELFREFRESH;
/* Wait until Self-Refresh mode is enabled */
FMC_BUSY_WAIT();
memcpy((void*)SOC_RAM_BUFFER_BASE, (void*)src, size);
/*
* Precharge according to chip requirement, page 12.
*/
STM32_SDRAM_FMC->sdcmr = FMC_SDCMR_BANK_1 | FMC_SDCMR_MODE_PRECHARGE;
FMC_BUSY_WAIT();
STM32_SDRAM_FMC->sdcmr = FMC_SDCMR_BANK_1 | FMC_SDCMR_MODE_NORMAL;
/* tRFC delay */
udelay(60);
/* Are you still busy? */
FMC_BUSY_WAIT();
memcpy((void*)dst, (void*)SOC_RAM_BUFFER_BASE, size);
ret = size;
out:
return ret;
}
/*
* Read from NOR and write into SDRAM
* See errata 2.8.7.
*/
int nor_sdram_bufcopy(ulong dst, ulong src, ulong size)
{
int ret = 0;
ulong copysize = 0;
while(size) {
copysize = min(size, SOC_RAM_BUFFER_SIZE);
if ((ret = nor_sdram_bufcopy_onebuf(dst, src, copysize)) <= 0)
goto done;
dst += min(size, SOC_RAM_BUFFER_SIZE);
src += min(size, SOC_RAM_BUFFER_SIZE);
size -= min(size, SOC_RAM_BUFFER_SIZE);
}
done:
return ret;
}
/*
* bufcopy: copy content of a NOR flash into SDRAM through SRAM buffer
*/
int do_bufcopy(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
ulong dst;
ulong src;
ulong size;
int ret = 0;
/*
* Check that at least the destination is specified
*/
if (argc != 4) {
printf("%s: dst src size\n", (char *) argv[0]);
goto Done;
}
/*
* Parse the command arguments
*/
dst = simple_strtoul(argv[1], NULL, 16);
src = simple_strtoul(argv[2], NULL, 16);
size = simple_strtoul(argv[3], NULL, 16);
if (dst < CONFIG_SYS_RAM_BASE) {
printf("%s: dst is outside SDRAM\n", (char *) argv[0]);
goto Done;
}
printf("%s: Doing bufcopy, please wait ...\n", (char *) argv[0]);
/*
* Copy the buffer to the destination.
*/
if ((ret = nor_sdram_bufcopy(dst, src, size)) < 0) {
printf("%s: nor_sdram_bufcopy failed: %d\n",
(char *) argv[0], ret);
goto Done;
}
Done:
return ret;
}
U_BOOT_CMD(
stmbufcopy, 5, 0, do_bufcopy,
"copy from NOR flash into SDRAM through SRAM buf",
"dst src size"
);
......@@ -151,6 +151,9 @@ int fsmc_nor_psram_init(u32 cs, u32 bcr, u32 btr, u32 bwtr)
*/
STM32_RCC->ahb3enr |= STM32_RCC_ENR_FSMC;
/* Errata 2.1.6 */
__asm__ __volatile__ ("dsb" : : : "memory");
/*
* Fake BCR read; if don't do this, then BCR remains configured
* with defaults.
......
......@@ -136,6 +136,12 @@ u8 flash_read8(void *addr)__attribute__((weak, alias("__flash_read8")));
u16 flash_read16(void *addr)__attribute__((weak, alias("__flash_read16")));
u32 flash_read32(void *addr)__attribute__((weak, alias("__flash_read32")));
u64 flash_read64(void *addr)__attribute__((weak, alias("__flash_read64")));
# ifdef CONFIG_SYS_FLASH_USE_BUFFER_WRITE
u32 flash_write_buffer(void *src, void *dst, int cnt, int portwidth)__attribute__((weak, alias("__flash_write_buffer")));
u32 flash_check_flag(void *src, void *dst, int cnt, int portwidth)__attribute__((weak, alias("__flash_check_flag")));
# endif
#else
#define flash_write8 __flash_write8
#define flash_write16 __flash_write16
......@@ -145,6 +151,11 @@ u64 flash_read64(void *addr)__attribute__((weak, alias("__flash_read64")));
#define flash_read16 __flash_read16
#define flash_read32 __flash_read32
#define flash_read64 __flash_read64
# ifdef CONFIG_SYS_FLASH_USE_BUFFER_WRITE
# define flash_write_buffer __flash_write_buffer
# define flash_check_flag __flash_check_flag
# endif
#endif
/*-----------------------------------------------------------------------
......@@ -761,6 +772,76 @@ static int flash_write_cfiword (flash_info_t * info, ulong dest,
#ifdef CONFIG_SYS_FLASH_USE_BUFFER_WRITE
u32 __flash_check_flag(void *src, void *dst, int cnt, int portwidth)
{
int flag = 0;
while ((cnt-- > 0) && (flag == 0)) {
switch (portwidth) {
case FLASH_CFI_8BIT:
flag = ((flash_read8(dst) & flash_read8(src)) ==
flash_read8(src));
src += 1, dst += 1;
break;
case FLASH_CFI_16BIT:
flag = ((flash_read16(dst) & flash_read16(src)) ==
flash_read16(src));
src += 2, dst += 2;
break;
case FLASH_CFI_32BIT:
flag = ((flash_read32(dst) & flash_read32(src)) ==
flash_read32(src));
src += 4, dst += 4;
break;
case FLASH_CFI_64BIT:
flag = ((flash_read64(dst) & flash_read64(src)) ==
flash_read64(src));
src += 8, dst += 8;
break;
}
}
return flag;
}
u32 __flash_write_buffer(void *src, void *dst, int cnt, int portwidth)
{
int retcode = 0;
switch (portwidth) {
case FLASH_CFI_8BIT:
while (cnt-- > 0) {
flash_write8(flash_read8(src), dst);
src += 1, dst += 1;
}
break;
case FLASH_CFI_16BIT:
while (cnt-- > 0) {
flash_write16(flash_read16(src), dst);
src += 2, dst += 2;
}
break;
case FLASH_CFI_32BIT:
while (cnt-- > 0) {
flash_write32(flash_read32(src), dst);
src += 4, dst += 4;
}
break;
case FLASH_CFI_64BIT:
while (cnt-- > 0) {
flash_write64(flash_read64(src), dst);
src += 8, dst += 8;
}
break;
default:
retcode = ERR_INVAL;
goto out;
}
out:
return retcode;
}
static int flash_write_cfibuffer (flash_info_t * info, ulong dest, uchar * cp,
int len)
{
......@@ -769,7 +850,6 @@ static int flash_write_cfibuffer (flash_info_t * info, ulong dest, uchar * cp,
int retcode;
void *src = cp;
void *dst = (void *)dest;
void *dst2 = dst;
int flag = 0;
uint offset = 0;
unsigned int shift;
......@@ -795,36 +875,12 @@ static int flash_write_cfibuffer (flash_info_t * info, ulong dest, uchar * cp,
cnt = len >> shift;
while ((cnt-- > 0) && (flag == 0)) {
switch (info->portwidth) {
case FLASH_CFI_8BIT:
flag = ((flash_read8(dst2) & flash_read8(src)) ==
flash_read8(src));
src += 1, dst2 += 1;
break;
case FLASH_CFI_16BIT:
flag = ((flash_read16(dst2) & flash_read16(src)) ==
flash_read16(src));
src += 2, dst2 += 2;
break;
case FLASH_CFI_32BIT:
flag = ((flash_read32(dst2) & flash_read32(src)) ==
flash_read32(src));
src += 4, dst2 += 4;
break;
case FLASH_CFI_64BIT:
flag = ((flash_read64(dst2) & flash_read64(src)) ==
flash_read64(src));
src += 8, dst2 += 8;
break;
}
}
flag = flash_check_flag (src, dst, cnt, info->portwidth);
if (!flag) {
retcode = ERR_NOT_ERASED;
goto out_unmap;
}
src = cp;
sector = find_sector (info, dest);
switch (info->vendor) {
......@@ -844,29 +900,10 @@ static int flash_write_cfibuffer (flash_info_t * info, ulong dest, uchar * cp,
* the port */
cnt = len >> shift;
flash_write_cmd (info, sector, 0, cnt - 1);
while (cnt-- > 0) {
switch (info->portwidth) {
case FLASH_CFI_8BIT:
flash_write8(flash_read8(src), dst);
src += 1, dst += 1;
break;
case FLASH_CFI_16BIT:
flash_write16(flash_read16(src), dst);
src += 2, dst += 2;
break;
case FLASH_CFI_32BIT:
flash_write32(flash_read32(src), dst);
src += 4, dst += 4;
break;
case FLASH_CFI_64BIT:
flash_write64(flash_read64(src), dst);
src += 8, dst += 8;
break;
default:
retcode = ERR_INVAL;
goto out_unmap;
}
}
retcode = flash_write_buffer (
src, dst, cnt, info->portwidth);
if (retcode)
goto out_unmap;
flash_write_cmd (info, sector, 0,
FLASH_CMD_WRITE_BUFFER_CONFIRM);
retcode = flash_full_status_check (
......@@ -887,35 +924,10 @@ static int flash_write_cfibuffer (flash_info_t * info, ulong dest, uchar * cp,
cnt = len >> shift;
flash_write_cmd(info, sector, offset, cnt - 1);
switch (info->portwidth) {
case FLASH_CFI_8BIT:
while (cnt-- > 0) {
flash_write8(flash_read8(src), dst);
src += 1, dst += 1;
}
break;
case FLASH_CFI_16BIT:
while (cnt-- > 0) {
flash_write16(flash_read16(src), dst);
src += 2, dst += 2;
}
break;
case FLASH_CFI_32BIT:
while (cnt-- > 0) {
flash_write32(flash_read32(src), dst);
src += 4, dst += 4;
}
break;
case FLASH_CFI_64BIT:
while (cnt-- > 0) {
flash_write64(flash_read64(src), dst);
src += 8, dst += 8;
}
break;
default:
retcode = ERR_INVAL;
retcode = flash_write_buffer (
src, dst, cnt, info->portwidth);
if (retcode)
goto out_unmap;
}
flash_write_cmd (info, sector, 0, AMD_CMD_WRITE_BUFFER_CONFIRM);
retcode = flash_full_status_check (info, sector,
......
......@@ -71,14 +71,25 @@ struct stm32_fmc_regs {
#define FMC_SDCMR_NRFS_SHIFT 5
#define FMC_SDCMR_MODE_NORMAL 0
#define FMC_SDCMR_MODE_START_CLOCK 1
#define FMC_SDCMR_MODE_PRECHARGE 2
#define FMC_SDCMR_MODE_AUTOREFRESH 3
#define FMC_SDCMR_MODE_WRITE_MODE 4
#define FMC_SDCMR_MODE_SELFREFRESH 5
#define FMC_SDCMR_MODE_POWERDOWN 6
#define FMC_SDCMR_BANK_1 (1 << 4)
#define FMC_SDCMR_BANK_2 (1 << 3)
#define FMC_SDCMR_MODE_REGISTER_SHIFT 9
#define FMC_SDSR_BUSY (1 << 5)
#define FMC_BUSY_WAIT() do { \
__asm__ __volatile__ ("dsb" : : : "memory"); \
while(STM32_SDRAM_FMC->sdsr & FMC_SDSR_BUSY); \
} while(0);
#endif /* _MACH_FMC_H_ */
......@@ -184,8 +184,8 @@
* tNE switch = BUSTURN(19-16) = 10 ns = 2 HCLK
* ACCMODE(29-28) = 0x2 (mode C)
*/
#define CONFIG_SYS_FSMC_FLASH_BTR 0x20021206
#define CONFIG_SYS_FSMC_FLASH_BWTR 0x20021106
#define CONFIG_SYS_FSMC_FLASH_BTR 0x2002120f
#define CONFIG_SYS_FSMC_FLASH_BWTR 0x2002110f
#define CONFIG_FSMC_NOR_PSRAM_CS2_ENABLE
#define CONFIG_SYS_FLASH_BANK1_BASE FSMC_NOR_PSRAM_CS_ADDR(CONFIG_SYS_FLASH_CS)
......@@ -200,6 +200,10 @@
#define CONFIG_SYS_FLASH_PROTECTION 1
#define CONFIG_SYS_FLASH_USE_BUFFER_WRITE
#if CONFIG_SYS_BOARD_REV == 0x2A
# define CONFIG_CFI_FLASH_USE_WEAK_ACCESSORS
#endif
/*
* Store env in Flash memory
*/
......@@ -308,6 +312,11 @@
#undef CONFIG_CMD_SOURCE
#undef CONFIG_CMD_XIMG
#if CONFIG_SYS_BOARD_REV == 0x2A
/* For loading from flash into memory */
# define CONFIG_CMD_BUFCOPY
#endif
/*
* To save memory disable long help
*/
......@@ -331,7 +340,19 @@
# define CONFIG_BOOTARGS "stm32_platform=stm32f4x9-som "\
"console=ttyS0,115200 panic=10"
# define LOADADDR "0xC0000000"
# define LOADADDR "0xC0007FC0"
# define REV_EXTRA_ENV \
"flashboot=run addip;" \
"stmbufcopy ${loadaddr} ${flashaddr} ${kernelsize};" \
"bootm ${loadaddr}\0" \
"update=tftp ${image};" \
"prot off ${flashaddr} +${filesize};" \
"era ${flashaddr} +${filesize};" \
"cp.b ${loadaddr} ${flashaddr} ${filesize};" \
"setenv kernelsize ${filesize};" \
"setenv filesize; setenv fileaddr;" \
"saveenv\0"
#elif CONFIG_SYS_BOARD_REV == 0x1A
/* Rev 1.A boot args and env */
......@@ -339,7 +360,13 @@
# define CONFIG_BOOTARGS "stm32_platform=stm-som "\
"console=ttyS2,115200 panic=10"
# define LOADADDR "0x60000000"
# define LOADADDR "0x60000000"
# define REV_EXTRA_ENV \
"flashboot=run addip;bootm ${flashaddr}\0" \
"update=tftp ${image};" \
"prot off ${flashaddr} +${filesize};" \
"era ${flashaddr} +${filesize};" \
"cp.b ${loadaddr} ${flashaddr} ${filesize}\0"
#endif
......@@ -352,17 +379,13 @@
"loadaddr=" LOADADDR "\0" \
"addip=setenv bootargs ${bootargs} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}:eth0:off\0" \
"flashaddr=64020000\0" \
"flashboot=run addip;bootm ${flashaddr}\0" \
"ethaddr=C0:B1:3C:88:88:85\0" \
"ipaddr=172.17.4.206\0" \
"serverip=172.17.0.1\0" \
"image=stm32/uImage\0" \
"image=stm32f4x9/uImage\0" \