From 6949328d7df7a98b88a8edc0f7238687a3083a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Klotzb=FCcher?= Date: Tue, 28 Feb 2006 18:05:25 +0100 Subject: First steps implementing NAND support. Not working, fails to read ID. --- board/delta/Makefile | 2 +- board/delta/config.mk | 3 + board/delta/delta.c | 2 +- board/delta/flash.c | 434 -------------------------------------------------- board/delta/nand.c | 306 +++++++++++++++++++++++++++++++++++ 5 files changed, 311 insertions(+), 436 deletions(-) delete mode 100644 board/delta/flash.c create mode 100644 board/delta/nand.c (limited to 'board/delta') diff --git a/board/delta/Makefile b/board/delta/Makefile index d968efbf81..e744eec2e0 100644 --- a/board/delta/Makefile +++ b/board/delta/Makefile @@ -26,7 +26,7 @@ include $(TOPDIR)/config.mk LIB = lib$(BOARD).a -OBJS := delta.o flash.o +OBJS := delta.o nand.o SOBJS := lowlevel_init.o $(LIB): $(OBJS) $(SOBJS) diff --git a/board/delta/config.mk b/board/delta/config.mk index d48fe054a0..b269b6efa1 100644 --- a/board/delta/config.mk +++ b/board/delta/config.mk @@ -4,3 +4,6 @@ #TEXT_BASE = 0x9ffe0000 TEXT_BASE = 0xa3008000 +# Compile the new NAND code (needed iff #ifdef CONFIG_NEW_NAND_CODE) +BOARDLIBS = drivers/nand/libnand.a + diff --git a/board/delta/delta.c b/board/delta/delta.c index e618ab96a1..3ffcc2af9b 100644 --- a/board/delta/delta.c +++ b/board/delta/delta.c @@ -41,7 +41,7 @@ int board_init (void) /* memory and cpu-speed are setup before relocation */ /* so we do _nothing_ here */ - /* arch number of Lubbock-Board */ + /* arch number of Lubbock-Board mk@tbd: fix this! */ gd->bd->bi_arch_number = MACH_TYPE_LUBBOCK; /* adress of boot parameters */ diff --git a/board/delta/flash.c b/board/delta/flash.c deleted file mode 100644 index 883c1ba3b3..0000000000 --- a/board/delta/flash.c +++ /dev/null @@ -1,434 +0,0 @@ -/* - * (C) Copyright 2001 - * Kyle Harris, Nexus Technologies, Inc. kharris@nexus-tech.net - * - * (C) Copyright 2001 - * Wolfgang Denk, DENX Software Engineering, wd@denx.de. - * - * See file CREDITS for list of people who contributed to this - * project. - * - * 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 -#include - - -flash_info_t flash_info[CFG_MAX_FLASH_BANKS]; /* info for FLASH chips */ - -/* Board support for 1 or 2 flash devices */ -#define FLASH_PORT_WIDTH32 -#undef FLASH_PORT_WIDTH16 - -#ifdef FLASH_PORT_WIDTH16 -#define FLASH_PORT_WIDTH ushort -#define FLASH_PORT_WIDTHV vu_short -#define SWAP(x) __swab16(x) -#else -#define FLASH_PORT_WIDTH ulong -#define FLASH_PORT_WIDTHV vu_long -#define SWAP(x) __swab32(x) -#endif - -#define FPW FLASH_PORT_WIDTH -#define FPWV FLASH_PORT_WIDTHV - -#define mb() __asm__ __volatile__ ("" : : : "memory") - -/*----------------------------------------------------------------------- - * Functions - */ -static ulong flash_get_size (FPW *addr, flash_info_t *info); -static int write_data (flash_info_t *info, ulong dest, FPW data); -static void flash_get_offsets (ulong base, flash_info_t *info); -void inline spin_wheel (void); - -/*----------------------------------------------------------------------- - */ - -unsigned long flash_init (void) -{ -#if 0 - int i; - ulong size = 0; - - for (i = 0; i < CFG_MAX_FLASH_BANKS; i++) { - switch (i) { - case 0: - flash_get_size ((FPW *) PHYS_FLASH_1, &flash_info[i]); - flash_get_offsets (PHYS_FLASH_1, &flash_info[i]); - break; - case 1: - flash_get_size ((FPW *) PHYS_FLASH_2, &flash_info[i]); - flash_get_offsets (PHYS_FLASH_2, &flash_info[i]); - break; - default: - panic ("configured too many flash banks!\n"); - break; - } - size += flash_info[i].size; - } - - /* Protect monitor and environment sectors - */ - flash_protect ( FLAG_PROTECT_SET, - CFG_FLASH_BASE, - CFG_FLASH_BASE + monitor_flash_len - 1, - &flash_info[0] ); - - flash_protect ( FLAG_PROTECT_SET, - CFG_ENV_ADDR, - CFG_ENV_ADDR + CFG_ENV_SIZE - 1, &flash_info[0] ); - - return size; -#endif - return 0; -} - -/*----------------------------------------------------------------------- - */ -static void flash_get_offsets (ulong base, flash_info_t *info) -{ - int i; - - if (info->flash_id == FLASH_UNKNOWN) { - return; - } - - if ((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_INTEL) { - for (i = 0; i < info->sector_count; i++) { - info->start[i] = base + (i * PHYS_FLASH_SECT_SIZE); - info->protect[i] = 0; - } - } -} - -/*----------------------------------------------------------------------- - */ -void flash_print_info (flash_info_t *info) -{ - int i; - - if (info->flash_id == FLASH_UNKNOWN) { - printf ("missing or unknown FLASH type\n"); - return; - } - - switch (info->flash_id & FLASH_VENDMASK) { - case FLASH_MAN_INTEL: - printf ("INTEL "); - break; - default: - printf ("Unknown Vendor "); - break; - } - - switch (info->flash_id & FLASH_TYPEMASK) { - case FLASH_28F128J3A: - printf ("28F128J3A\n"); - break; - default: - printf ("Unknown Chip Type\n"); - break; - } - - printf (" Size: %ld MB in %d Sectors\n", - info->size >> 20, info->sector_count); - - printf (" Sector Start Addresses:"); - for (i = 0; i < info->sector_count; ++i) { - if ((i % 5) == 0) - printf ("\n "); - printf (" %08lX%s", - info->start[i], - info->protect[i] ? " (RO)" : " "); - } - printf ("\n"); - return; -} - -/* - * The following code cannot be run from FLASH! - */ -static ulong flash_get_size (FPW *addr, flash_info_t *info) -{ - volatile FPW value; - - /* Write auto select command: read Manufacturer ID */ - addr[0x5555] = (FPW) 0x00AA00AA; - addr[0x2AAA] = (FPW) 0x00550055; - addr[0x5555] = (FPW) 0x00900090; - - mb (); - value = addr[0]; - - switch (value) { - - case (FPW) INTEL_MANUFACT: - info->flash_id = FLASH_MAN_INTEL; - break; - - default: - info->flash_id = FLASH_UNKNOWN; - info->sector_count = 0; - info->size = 0; - addr[0] = (FPW) 0x00FF00FF; /* restore read mode */ - return (0); /* no or unknown flash */ - } - - mb (); - value = addr[1]; /* device ID */ - - switch (value) { - - case (FPW) INTEL_ID_28F128J3A: - info->flash_id += FLASH_28F128J3A; - info->sector_count = 128; - info->size = 0x02000000; - break; /* => 16 MB */ - - default: - info->flash_id = FLASH_UNKNOWN; - break; - } - - if (info->sector_count > CFG_MAX_FLASH_SECT) { - printf ("** ERROR: sector count %d > max (%d) **\n", - info->sector_count, CFG_MAX_FLASH_SECT); - info->sector_count = CFG_MAX_FLASH_SECT; - } - - addr[0] = (FPW) 0x00FF00FF; /* restore read mode */ - - return (info->size); -} - - -/*----------------------------------------------------------------------- - */ - -int flash_erase (flash_info_t *info, int s_first, int s_last) -{ - int flag, prot, sect; - ulong type, start, last; - int rcode = 0; - - if ((s_first < 0) || (s_first > s_last)) { - if (info->flash_id == FLASH_UNKNOWN) { - printf ("- missing\n"); - } else { - printf ("- no sectors to erase\n"); - } - return 1; - } - - type = (info->flash_id & FLASH_VENDMASK); - if ((type != FLASH_MAN_INTEL)) { - printf ("Can't erase unknown flash type %08lx - aborted\n", - info->flash_id); - return 1; - } - - prot = 0; - for (sect = s_first; sect <= s_last; ++sect) { - if (info->protect[sect]) { - prot++; - } - } - - if (prot) { - printf ("- Warning: %d protected sectors will not be erased!\n", - prot); - } else { - printf ("\n"); - } - - start = get_timer (0); - last = start; - - /* Disable interrupts which might cause a timeout here */ - flag = disable_interrupts (); - - /* Start erase on unprotected sectors */ - for (sect = s_first; sect <= s_last; sect++) { - if (info->protect[sect] == 0) { /* not protected */ - FPWV *addr = (FPWV *) (info->start[sect]); - FPW status; - - printf ("Erasing sector %2d ... ", sect); - - /* arm simple, non interrupt dependent timer */ - reset_timer_masked (); - - *addr = (FPW) 0x00500050; /* clear status register */ - *addr = (FPW) 0x00200020; /* erase setup */ - *addr = (FPW) 0x00D000D0; /* erase confirm */ - - while (((status = *addr) & (FPW) 0x00800080) != (FPW) 0x00800080) { - if (get_timer_masked () > CFG_FLASH_ERASE_TOUT) { - printf ("Timeout\n"); - *addr = (FPW) 0x00B000B0; /* suspend erase */ - *addr = (FPW) 0x00FF00FF; /* reset to read mode */ - rcode = 1; - break; - } - } - - *addr = 0x00500050; /* clear status register cmd. */ - *addr = 0x00FF00FF; /* resest to read mode */ - - printf (" done\n"); - } - } - return rcode; -} - -/*----------------------------------------------------------------------- - * Copy memory to flash, returns: - * 0 - OK - * 1 - write timeout - * 2 - Flash not erased - * 4 - Flash not identified - */ - -int write_buff (flash_info_t *info, uchar *src, ulong addr, ulong cnt) -{ - ulong cp, wp; - FPW data; - int count, i, l, rc, port_width; - - if (info->flash_id == FLASH_UNKNOWN) { - return 4; - } -/* get lower word aligned address */ -#ifdef FLASH_PORT_WIDTH16 - wp = (addr & ~1); - port_width = 2; -#else - wp = (addr & ~3); - port_width = 4; -#endif - - /* - * handle unaligned start bytes - */ - if ((l = addr - wp) != 0) { - data = 0; - for (i = 0, cp = wp; i < l; ++i, ++cp) { - data = (data << 8) | (*(uchar *) cp); - } - for (; i < port_width && cnt > 0; ++i) { - data = (data << 8) | *src++; - --cnt; - ++cp; - } - for (; cnt == 0 && i < port_width; ++i, ++cp) { - data = (data << 8) | (*(uchar *) cp); - } - - if ((rc = write_data (info, wp, SWAP (data))) != 0) { - return (rc); - } - wp += port_width; - } - - /* - * handle word aligned part - */ - count = 0; - while (cnt >= port_width) { - data = 0; - for (i = 0; i < port_width; ++i) { - data = (data << 8) | *src++; - } - if ((rc = write_data (info, wp, SWAP (data))) != 0) { - return (rc); - } - wp += port_width; - cnt -= port_width; - if (count++ > 0x800) { - spin_wheel (); - count = 0; - } - } - - if (cnt == 0) { - return (0); - } - - /* - * handle unaligned tail bytes - */ - data = 0; - for (i = 0, cp = wp; i < port_width && cnt > 0; ++i, ++cp) { - data = (data << 8) | *src++; - --cnt; - } - for (; i < port_width; ++i, ++cp) { - data = (data << 8) | (*(uchar *) cp); - } - - return (write_data (info, wp, SWAP (data))); -} - -/*----------------------------------------------------------------------- - * Write a word or halfword to Flash, returns: - * 0 - OK - * 1 - write timeout - * 2 - Flash not erased - */ -static int write_data (flash_info_t *info, ulong dest, FPW data) -{ - FPWV *addr = (FPWV *) dest; - ulong status; - int flag; - - /* Check if Flash is (sufficiently) erased */ - if ((*addr & data) != data) { - printf ("not erased at %08lx (%lx)\n", (ulong) addr, *addr); - return (2); - } - /* Disable interrupts which might cause a timeout here */ - flag = disable_interrupts (); - - *addr = (FPW) 0x00400040; /* write setup */ - *addr = data; - - /* arm simple, non interrupt dependent timer */ - reset_timer_masked (); - - /* wait while polling the status register */ - while (((status = *addr) & (FPW) 0x00800080) != (FPW) 0x00800080) { - if (get_timer_masked () > CFG_FLASH_WRITE_TOUT) { - *addr = (FPW) 0x00FF00FF; /* restore read mode */ - return (1); - } - } - - *addr = (FPW) 0x00FF00FF; /* restore read mode */ - - return (0); -} - -void inline spin_wheel (void) -{ - static int p = 0; - static char w[] = "\\/-"; - - printf ("\010%c", w[p]); - (++p == 3) ? (p = 0) : 0; -} diff --git a/board/delta/nand.c b/board/delta/nand.c new file mode 100644 index 0000000000..9caddba064 --- /dev/null +++ b/board/delta/nand.c @@ -0,0 +1,306 @@ +/* + * (C) Copyright 2006 DENX Software Engineering + * + * See file CREDITS for list of people who contributed to this + * project. + * + * 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 + +#if (CONFIG_COMMANDS & CFG_CMD_NAND) +#ifdef CONFIG_NEW_NAND_CODE + +#include +#include + +/* + * hardware specific access to control-lines + * function borrowed from Linux 2.6 (drivers/mtd/nand/ppchameleonevb.c) + */ +static void delta_hwcontrol(struct mtd_info *mtdinfo, int cmd) +{ +#if 0 + struct nand_chip *this = mtdinfo->priv; + ulong base = (ulong) this->IO_ADDR_W; + + switch(cmd) { + case NAND_CTL_SETCLE: + MACRO_NAND_CTL_SETCLE((unsigned long)base); + break; + case NAND_CTL_CLRCLE: + MACRO_NAND_CTL_CLRCLE((unsigned long)base); + break; + case NAND_CTL_SETALE: + MACRO_NAND_CTL_SETALE((unsigned long)base); + break; + case NAND_CTL_CLRALE: + MACRO_NAND_CTL_CLRALE((unsigned long)base); + break; + case NAND_CTL_SETNCE: + MACRO_NAND_ENABLE_CE((unsigned long)base); + break; + case NAND_CTL_CLRNCE: + MACRO_NAND_DISABLE_CE((unsigned long)base); + break; + } +#endif +} + + +/* read device ready pin */ +static int delta_device_ready(struct mtd_info *mtdinfo) +{ + if(NDSR & NDSR_RDY) + return 1; + else + return 0; +#if 0 + struct nand_chip *this = mtdinfo->priv; + ulong rb_gpio_pin; + + /* use the base addr to find out which chip are we dealing with */ + switch((ulong) this->IO_ADDR_W) { + case CFG_NAND0_BASE: + rb_gpio_pin = CFG_NAND0_RDY; + break; + case CFG_NAND1_BASE: + rb_gpio_pin = CFG_NAND1_RDY; + break; + default: /* this should never happen */ + return 0; + break; + } + + if (in32(GPIO0_IR) & rb_gpio_pin) + return 1; +#endif + return 0; +} + +static u_char delta_read_byte(struct mtd_info *mtd) +{ +/* struct nand_chip *this = mtd->priv; */ + unsigned long tmp; + + /* wait for read request */ + while(1) { + if(NDSR & NDSR_RDDREQ) { + NDSR |= NDSR_RDDREQ; + break; + } + } + + tmp = NDDB; + printk("delta_read_byte: 0x%x.\n", tmp); + return (u_char) tmp; +} + +/* this is really monahans, not board specific ... */ +static void delta_cmdfunc(struct mtd_info *mtd, unsigned command, + int column, int page_addr) +{ + /* register struct nand_chip *this = mtd->priv; */ + unsigned long ndcb0=0, ndcb1=0, ndcb2=0; + uchar command2; + + /* Clear NDSR */ + NDSR = 0xFFF; + + /* apparently NDCR[NDRUN] needs to be set before writing to NDCBx */ + NDCR |= NDCR_ND_RUN; + + /* wait for write command request + * hmm, might be nice if this could time-out. mk@tbd + */ + while(1) { + if(NDSR & NDSR_WRCMDREQ) { + NDSR |= NDSR_WRCMDREQ; /* Ack */ + break; + } + } + + /* if command is a double byte cmd, we set bit double cmd bit 19 */ + command2 = (command>>8) & 0xFF; + ndcb0 = command | ((command2 ? 1 : 0) << 19); + + switch (command) { + case NAND_CMD_READID: + printk("delta_cmdfunc: NAND_CMD_READID.\n"); + ndcb0 |= ((3 << 21) | (2 << 16)); + break; + case NAND_CMD_PAGEPROG: + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + case NAND_CMD_SEQIN: + case NAND_CMD_STATUS: + return; + case NAND_CMD_RESET: + return; + default: + printk("delta_cmdfunc: error, unkown command issued.\n"); + return; + } + + NDCB0 = ndcb0; + NDCB1 = ndcb1; + NDCB2 = ndcb2; +} + +/* + * Board-specific NAND initialization. The following members of the + * argument are board-specific (per include/linux/mtd/nand_new.h): + * - IO_ADDR_R?: address to read the 8 I/O lines of the flash device + * - IO_ADDR_W?: address to write the 8 I/O lines of the flash device + * - hwcontrol: hardwarespecific function for accesing control-lines + * - dev_ready: hardwarespecific function for accesing device ready/busy line + * - enable_hwecc?: function to enable (reset) hardware ecc generator. Must + * only be provided if a hardware ECC is available + * - eccmode: mode of ecc, see defines + * - chip_delay: chip dependent delay for transfering data from array to + * read regs (tR) + * - options: various chip options. They can partly be set to inform + * nand_scan about special functionality. See the defines for further + * explanation + * Members with a "?" were not set in the merged testing-NAND branch, + * so they are not set here either. + */ +void board_nand_init(struct nand_chip *nand) +{ + unsigned long tCH, tCS, tWH, tWP, tRH, tRP, tRP_high, tR, tWHR, tAR; + + /* set up GPIO Control Registers */ + + /* turn on the NAND Controller Clock (104 MHz @ D0) */ + CKENA |= (CKENA_4_NAND | CKENA_9_SMC); + + /* NAND Timing Parameters (in ns) */ +#define NAND_TIMING_tCH 10 +#define NAND_TIMING_tCS 0 +#define NAND_TIMING_tWH 20 +#define NAND_TIMING_tWP 40 +#define NAND_TIMING_tRH 20 +#define NAND_TIMING_tRP 40 +#define NAND_TIMING_tR 11123 +#define NAND_TIMING_tWHR 110 +#define NAND_TIMING_tAR 10 + +/* Maximum values for NAND Interface Timing Registers in DFC clock + * periods */ +#define DFC_MAX_tCH 7 +#define DFC_MAX_tCS 7 +#define DFC_MAX_tWH 7 +#define DFC_MAX_tWP 7 +#define DFC_MAX_tRH 7 +#define DFC_MAX_tRP 15 +#define DFC_MAX_tR 65535 +#define DFC_MAX_tWHR 15 +#define DFC_MAX_tAR 15 + +#define DFC_CLOCK 104 /* DFC Clock is 104 MHz */ +#define DFC_CLK_PER_US DFC_CLOCK/1000 /* clock period in ns */ +#define MIN(x, y) ((x < y) ? x : y) + + + tCH = MIN(((unsigned long) (NAND_TIMING_tCH * DFC_CLK_PER_US) + 1), + DFC_MAX_tCH); + tCS = MIN(((unsigned long) (NAND_TIMING_tCS * DFC_CLK_PER_US) + 1), + DFC_MAX_tCS); + tWH = MIN(((unsigned long) (NAND_TIMING_tWH * DFC_CLK_PER_US) + 1), + DFC_MAX_tWH); + tWP = MIN(((unsigned long) (NAND_TIMING_tWP * DFC_CLK_PER_US) + 1), + DFC_MAX_tWP); + tRH = MIN(((unsigned long) (NAND_TIMING_tRH * DFC_CLK_PER_US) + 1), + DFC_MAX_tRH); + tRP = MIN(((unsigned long) (NAND_TIMING_tRP * DFC_CLK_PER_US) + 1), + DFC_MAX_tRP); + tR = MIN(((unsigned long) (NAND_TIMING_tR * DFC_CLK_PER_US) + 1), + DFC_MAX_tR); + tWHR = MIN(((unsigned long) (NAND_TIMING_tWHR * DFC_CLK_PER_US) + 1), + DFC_MAX_tWHR); + tAR = MIN(((unsigned long) (NAND_TIMING_tAR * DFC_CLK_PER_US) + 1), + DFC_MAX_tAR); + + + /* tRP value is split in the register */ + if(tRP & (1 << 4)) { + tRP_high = 1; + tRP &= ~(1 << 4); + } else { + tRP_high = 0; + } + + NDTR0CS0 = (tCH << 19) | + (tCS << 16) | + (tWH << 11) | + (tWP << 8) | + (tRP_high << 6) | + (tRH << 3) | + (tRP << 0); + + NDTR1CS0 = (tR << 16) | + (tWHR << 4) | + (tAR << 0); + + + + /* If it doesn't work (unlikely) think about: + * - ecc enable + * - chip select don't care + * - read id byte count + * + * Intentionally enabled by not setting bits: + * - dma (DMA_EN) + * - page size = 512 + * - cs don't care, see if we can enable later! + * - row address start position (after second cycle) + * - pages per block = 32 + */ + NDCR = (NDCR_ND_ARB_EN | /* enable bus arbiter */ + NDCR_SPARE_EN | /* use the spare area */ + NDCR_DWIDTH_C | /* 16bit DFC data bus width */ + NDCR_DWIDTH_M | /* 16 bit Flash device data bus width */ + (2 << 16) | /* read id count = 7 ???? mk@tbd */ + NDCE_RDYM | /* flash device ready ir masked */ + NDCE_CS0_PAGEDM | /* ND_nCSx page done ir masked */ + NDCE_CS1_PAGEDM | + NDCE_CS0_CMDDM | /* ND_CSx command done ir masked */ + NDCE_CS1_CMDDM | + NDCE_CS0_BBDM | /* ND_CSx bad block detect ir masked */ + NDCE_CS1_BBDM | + NDCE_DBERRM | /* double bit error ir masked */ + NDCE_SBERRM | /* single bit error ir masked */ + NDCE_WRDREQM | /* write data request ir masked */ + NDCE_RDDREQM | /* read data request ir masked */ + NDCE_WRCMDREQM); /* write command request ir masked */ + + + + nand->hwcontrol = delta_hwcontrol; + nand->dev_ready = delta_device_ready; + nand->eccmode = NAND_ECC_SOFT; + nand->chip_delay = NAND_DELAY_US; + nand->options = NAND_BUSWIDTH_16; + nand->read_byte = delta_read_byte; + nand->cmdfunc = delta_cmdfunc; + /* nand->options = NAND_SAMSUNG_LP_OPTIONS; */ +} + +#else +#error "U-Boot legacy NAND support not available for delta board." +#endif +#endif -- cgit v1.2.1