* Author: David Woodhouse <dwmw2@infradead.org>
* Additional Diskonchip 2000 and Millennium support by Dan Brown <dan_brown@ieee.org>
* Diskonchip Millennium Plus support by Kalev Lember <kalev@smartlink.ee>
- *
+ *
+ * Error correction code lifted from the old docecc code
+ * Author: Fabrice Bellard (fabrice.bellard@netgem.com)
+ * Copyright (C) 2000 Netgem S.A.
+ * converted to the generic Reed-Solomon library by Thomas Gleixner <tglx@linutronix.de>
+ *
* Interface to generic NAND code for M-Systems DiskOnChip devices
*
- * $Id: diskonchip.c,v 1.34 2004/08/09 19:41:12 dbrown Exp $
+ * $Id: diskonchip.c,v 1.45 2005/01/05 18:05:14 dwmw2 Exp $
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/delay.h>
+#include <linux/rslib.h>
+#include <linux/moduleparam.h>
#include <asm/io.h>
#include <linux/mtd/mtd.h>
static struct mtd_info *doclist = NULL;
struct doc_priv {
- unsigned long virtadr;
+ void __iomem *virtadr;
unsigned long physadr;
u_char ChipID;
u_char CDSNControl;
static void doc200x_select_chip(struct mtd_info *mtd, int chip);
static int debug=0;
-MODULE_PARM(debug, "i");
+module_param(debug, int, 0);
static int try_dword=1;
-MODULE_PARM(try_dword, "i");
+module_param(try_dword, int, 0);
static int no_ecc_failures=0;
-MODULE_PARM(no_ecc_failures, "i");
+module_param(no_ecc_failures, int, 0);
+#ifdef CONFIG_MTD_PARTITIONS
static int no_autopart=0;
-MODULE_PARM(no_autopart, "i");
+module_param(no_autopart, int, 0);
+#endif
#ifdef MTD_NAND_DISKONCHIP_BBTWRITE
static int inftl_bbt_write=1;
#else
static int inftl_bbt_write=0;
#endif
-MODULE_PARM(inftl_bbt_write, "i");
+module_param(inftl_bbt_write, int, 0);
static unsigned long doc_config_location = CONFIG_MTD_DISKONCHIP_PROBE_ADDRESS;
-MODULE_PARM(doc_config_location, "l");
+module_param(doc_config_location, ulong, 0);
MODULE_PARM_DESC(doc_config_location, "Physical memory address at which to probe for DiskOnChip");
+
+/* Sector size for HW ECC */
+#define SECTOR_SIZE 512
+/* The sector bytes are packed into NB_DATA 10 bit words */
+#define NB_DATA (((SECTOR_SIZE + 1) * 8 + 6) / 10)
+/* Number of roots */
+#define NROOTS 4
+/* First consective root */
+#define FCR 510
+/* Number of symbols */
+#define NN 1023
+
+/* the Reed Solomon control structure */
+static struct rs_control *rs_decoder;
+
+/*
+ * The HW decoder in the DoC ASIC's provides us a error syndrome,
+ * which we must convert to a standard syndrom usable by the generic
+ * Reed-Solomon library code.
+ *
+ * Fabrice Bellard figured this out in the old docecc code. I added
+ * some comments, improved a minor bit and converted it to make use
+ * of the generic Reed-Solomon libary. tglx
+ */
+static int doc_ecc_decode (struct rs_control *rs, uint8_t *data, uint8_t *ecc)
+{
+ int i, j, nerr, errpos[8];
+ uint8_t parity;
+ uint16_t ds[4], s[5], tmp, errval[8], syn[4];
+
+ /* Convert the ecc bytes into words */
+ ds[0] = ((ecc[4] & 0xff) >> 0) | ((ecc[5] & 0x03) << 8);
+ ds[1] = ((ecc[5] & 0xfc) >> 2) | ((ecc[2] & 0x0f) << 6);
+ ds[2] = ((ecc[2] & 0xf0) >> 4) | ((ecc[3] & 0x3f) << 4);
+ ds[3] = ((ecc[3] & 0xc0) >> 6) | ((ecc[0] & 0xff) << 2);
+ parity = ecc[1];
+
+ /* Initialize the syndrom buffer */
+ for (i = 0; i < NROOTS; i++)
+ s[i] = ds[0];
+ /*
+ * Evaluate
+ * s[i] = ds[3]x^3 + ds[2]x^2 + ds[1]x^1 + ds[0]
+ * where x = alpha^(FCR + i)
+ */
+ for(j = 1; j < NROOTS; j++) {
+ if(ds[j] == 0)
+ continue;
+ tmp = rs->index_of[ds[j]];
+ for(i = 0; i < NROOTS; i++)
+ s[i] ^= rs->alpha_to[rs_modnn(rs, tmp + (FCR + i) * j)];
+ }
+
+ /* Calc s[i] = s[i] / alpha^(v + i) */
+ for (i = 0; i < NROOTS; i++) {
+ if (syn[i])
+ syn[i] = rs_modnn(rs, rs->index_of[s[i]] + (NN - FCR - i));
+ }
+ /* Call the decoder library */
+ nerr = decode_rs16(rs, NULL, NULL, 1019, syn, 0, errpos, 0, errval);
+
+ /* Incorrectable errors ? */
+ if (nerr < 0)
+ return nerr;
+
+ /*
+ * Correct the errors. The bitpositions are a bit of magic,
+ * but they are given by the design of the de/encoder circuit
+ * in the DoC ASIC's.
+ */
+ for(i = 0;i < nerr; i++) {
+ int index, bitpos, pos = 1015 - errpos[i];
+ uint8_t val;
+ if (pos >= NB_DATA && pos < 1019)
+ continue;
+ if (pos < NB_DATA) {
+ /* extract bit position (MSB first) */
+ pos = 10 * (NB_DATA - 1 - pos) - 6;
+ /* now correct the following 10 bits. At most two bytes
+ can be modified since pos is even */
+ index = (pos >> 3) ^ 1;
+ bitpos = pos & 7;
+ if ((index >= 0 && index < SECTOR_SIZE) ||
+ index == (SECTOR_SIZE + 1)) {
+ val = (uint8_t) (errval[i] >> (2 + bitpos));
+ parity ^= val;
+ if (index < SECTOR_SIZE)
+ data[index] ^= val;
+ }
+ index = ((pos >> 3) + 1) ^ 1;
+ bitpos = (bitpos + 10) & 7;
+ if (bitpos == 0)
+ bitpos = 8;
+ if ((index >= 0 && index < SECTOR_SIZE) ||
+ index == (SECTOR_SIZE + 1)) {
+ val = (uint8_t)(errval[i] << (8 - bitpos));
+ parity ^= val;
+ if (index < SECTOR_SIZE)
+ data[index] ^= val;
+ }
+ }
+ }
+ /* If the parity is wrong, no rescue possible */
+ return parity ? -1 : nerr;
+}
+
static void DoC_Delay(struct doc_priv *doc, unsigned short cycles)
{
volatile char dummy;
/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */
static int _DoC_WaitReady(struct doc_priv *doc)
{
- unsigned long docptr = doc->virtadr;
+ void __iomem *docptr = doc->virtadr;
unsigned long timeo = jiffies + (HZ * 10);
if(debug) printk("_DoC_WaitReady...\n");
static inline int DoC_WaitReady(struct doc_priv *doc)
{
- unsigned long docptr = doc->virtadr;
+ void __iomem *docptr = doc->virtadr;
int ret = 0;
if (DoC_is_MillenniumPlus(doc)) {
static void doc2000_write_byte(struct mtd_info *mtd, u_char datum)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
if(debug)printk("write_byte %02x\n", datum);
WriteDOC(datum, docptr, CDSNSlowIO);
static u_char doc2000_read_byte(struct mtd_info *mtd)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
u_char ret;
ReadDOC(docptr, CDSNSlowIO);
const u_char *buf, int len)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
int i;
if (debug)printk("writebuf of %d bytes: ", len);
for (i=0; i < len; i++) {
u_char *buf, int len)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
int i;
if (debug)printk("readbuf of %d bytes: ", len);
u_char *buf, int len)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
int i;
if (debug) printk("readbuf_dword of %d bytes: ", len);
const u_char *buf, int len)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
int i;
for (i=0; i < len; i++)
static uint16_t __init doc200x_ident_chip(struct mtd_info *mtd, int nr)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
+ struct doc_priv *doc = this->priv;
uint16_t ret;
doc200x_select_chip(mtd, nr);
uint32_t dword;
uint8_t byte[4];
} ident;
- unsigned long docptr = doc->virtadr;
+ void __iomem *docptr = doc->virtadr;
doc200x_hwcontrol(mtd, NAND_CTL_SETCLE);
doc2000_write_byte(mtd, NAND_CMD_READID);
static void __init doc2000_count_chips(struct mtd_info *mtd)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
+ struct doc_priv *doc = this->priv;
uint16_t mfrid;
int i;
static int doc200x_wait(struct mtd_info *mtd, struct nand_chip *this, int state)
{
- struct doc_priv *doc = (void *)this->priv;
+ struct doc_priv *doc = this->priv;
int status;
static void doc2001_write_byte(struct mtd_info *mtd, u_char datum)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
WriteDOC(datum, docptr, CDSNSlowIO);
WriteDOC(datum, docptr, Mil_CDSN_IO);
static u_char doc2001_read_byte(struct mtd_info *mtd)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
//ReadDOC(docptr, CDSNSlowIO);
/* 11.4.5 -- delay twice to allow extended length cycle */
const u_char *buf, int len)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
int i;
for (i=0; i < len; i++)
u_char *buf, int len)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
int i;
/* Start read pipeline */
const u_char *buf, int len)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
int i;
/* Start read pipeline */
static u_char doc2001plus_read_byte(struct mtd_info *mtd)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
u_char ret;
ReadDOC(docptr, Mplus_ReadPipeInit);
const u_char *buf, int len)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
int i;
if (debug)printk("writebuf of %d bytes: ", len);
u_char *buf, int len)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
int i;
if (debug)printk("readbuf of %d bytes: ", len);
const u_char *buf, int len)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
int i;
if (debug)printk("verifybuf of %d bytes: ", len);
static void doc2001plus_select_chip(struct mtd_info *mtd, int chip)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
int floor = 0;
if(debug)printk("select chip (%d)\n", chip);
static void doc200x_select_chip(struct mtd_info *mtd, int chip)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
int floor = 0;
if(debug)printk("select chip (%d)\n", chip);
static void doc200x_hwcontrol(struct mtd_info *mtd, int cmd)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
switch(cmd) {
case NAND_CTL_SETNCE:
static void doc2001plus_command (struct mtd_info *mtd, unsigned command, int column, int page_addr)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
/*
* Must terminate write pipeline before sending any commands
static int doc200x_dev_ready(struct mtd_info *mtd)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
if (DoC_is_MillenniumPlus(doc)) {
/* 11.4.2 -- must NOP four times before checking FR/B# */
static void doc200x_enable_hwecc(struct mtd_info *mtd, int mode)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
/* Prime the ECC engine */
switch(mode) {
static void doc2001plus_enable_hwecc(struct mtd_info *mtd, int mode)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
/* Prime the ECC engine */
switch(mode) {
unsigned char *ecc_code)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
int i;
int emptymatch = 1;
{
int i, ret = 0;
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
- unsigned long docptr = doc->virtadr;
+ struct doc_priv *doc = this->priv;
+ void __iomem *docptr = doc->virtadr;
volatile u_char dummy;
int emptymatch = 1;
erased block, in which case the ECC will not come out right.
We'll suppress the error and tell the caller everything's
OK. Because it is. */
- if (!emptymatch) ret = doc_decode_ecc (dat, calc_ecc);
+ if (!emptymatch) ret = doc_ecc_decode (rs_decoder, dat, calc_ecc);
if (ret > 0)
printk(KERN_ERR "doc200x_correct_data corrected %d errors\n", ret);
}
const char *id, int findmirror)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
+ struct doc_priv *doc = this->priv;
unsigned offs, end = (MAX_MEDIAHEADER_SCAN << this->phys_erase_shift);
int ret;
size_t retlen;
struct mtd_partition *parts)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
+ struct doc_priv *doc = this->priv;
int ret = 0;
u_char *buf;
struct NFTLMediaHeader *mh;
struct mtd_partition *parts)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
+ struct doc_priv *doc = this->priv;
int ret = 0;
u_char *buf;
struct INFTLMediaHeader *mh;
{
int ret, numparts;
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
+ struct doc_priv *doc = this->priv;
struct mtd_partition parts[2];
memset((char *) parts, 0, sizeof(parts));
{
int ret, numparts;
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
+ struct doc_priv *doc = this->priv;
struct mtd_partition parts[5];
if (this->numchips > doc->chips_per_floor) {
static inline int __init doc2000_init(struct mtd_info *mtd)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
+ struct doc_priv *doc = this->priv;
this->write_byte = doc2000_write_byte;
this->read_byte = doc2000_read_byte;
static inline int __init doc2001_init(struct mtd_info *mtd)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
+ struct doc_priv *doc = this->priv;
this->write_byte = doc2001_write_byte;
this->read_byte = doc2001_read_byte;
static inline int __init doc2001plus_init(struct mtd_info *mtd)
{
struct nand_chip *this = mtd->priv;
- struct doc_priv *doc = (void *)this->priv;
+ struct doc_priv *doc = this->priv;
this->write_byte = NULL;
this->read_byte = doc2001plus_read_byte;
struct mtd_info *mtd;
struct nand_chip *nand;
struct doc_priv *doc;
- unsigned long virtadr;
+ void __iomem *virtadr;
unsigned char save_control;
unsigned char tmp, tmpb, tmpc;
int reg, len, numchips;
int ret = 0;
- virtadr = (unsigned long)ioremap(physadr, DOC_IOREMAP_LEN);
+ virtadr = ioremap(physadr, DOC_IOREMAP_LEN);
if (!virtadr) {
printk(KERN_ERR "Diskonchip ioremap failed: 0x%x bytes at 0x%lx\n", DOC_IOREMAP_LEN, physadr);
return -EIO;
unsigned char oldval;
unsigned char newval;
nand = mtd->priv;
- doc = (void *)nand->priv;
+ doc = nand->priv;
/* Use the alias resolution register to determine if this is
in fact the same DOC aliased to a new address. If writes
to one chip's alias resolution register change the value on
sizeof(struct nand_chip) +
sizeof(struct doc_priv) +
(2 * sizeof(struct nand_bbt_descr));
- mtd = kmalloc(len, GFP_KERNEL);
+ mtd = kmalloc(len, GFP_KERNEL);
if (!mtd) {
printk(KERN_ERR "DiskOnChip kmalloc (%d bytes) failed!\n", len);
ret = -ENOMEM;
nand->bbt_td = (struct nand_bbt_descr *) (doc + 1);
nand->bbt_md = nand->bbt_td + 1;
- mtd->priv = (void *) nand;
+ mtd->priv = nand;
mtd->owner = THIS_MODULE;
- nand->priv = (void *) doc;
+ nand->priv = doc;
nand->select_chip = doc200x_select_chip;
nand->hwcontrol = doc200x_hwcontrol;
nand->dev_ready = doc200x_dev_ready;
nand->enable_hwecc = doc200x_enable_hwecc;
nand->calculate_ecc = doc200x_calculate_ecc;
nand->correct_data = doc200x_correct_data;
- //nand->data_buf
+
nand->autooob = &doc200x_oobinfo;
nand->eccmode = NAND_ECC_HW6_512;
nand->options = NAND_USE_FLASH_BBT | NAND_HWECC_SYNDROME;
actually a DiskOnChip. */
WriteDOC(save_control, virtadr, DOCControl);
fail:
- iounmap((void *)virtadr);
+ iounmap(virtadr);
return ret;
}
-int __init init_nanddoc(void)
+static void release_nanddoc(void)
{
- int i;
+ struct mtd_info *mtd, *nextmtd;
+ struct nand_chip *nand;
+ struct doc_priv *doc;
+
+ for (mtd = doclist; mtd; mtd = nextmtd) {
+ nand = mtd->priv;
+ doc = nand->priv;
+
+ nextmtd = doc->nextdoc;
+ nand_release(mtd);
+ iounmap(doc->virtadr);
+ kfree(mtd);
+ }
+}
+
+static int __init init_nanddoc(void)
+{
+ int i, ret = 0;
+
+ /* We could create the decoder on demand, if memory is a concern.
+ * This way we have it handy, if an error happens
+ *
+ * Symbolsize is 10 (bits)
+ * Primitve polynomial is x^10+x^3+1
+ * first consecutive root is 510
+ * primitve element to generate roots = 1
+ * generator polinomial degree = 4
+ */
+ rs_decoder = init_rs(10, 0x409, FCR, 1, NROOTS);
+ if (!rs_decoder) {
+ printk (KERN_ERR "DiskOnChip: Could not create a RS decoder\n");
+ return -ENOMEM;
+ }
if (doc_config_location) {
printk(KERN_INFO "Using configured DiskOnChip probe address 0x%lx\n", doc_config_location);
- return doc_probe(doc_config_location);
+ ret = doc_probe(doc_config_location);
+ if (ret < 0)
+ goto outerr;
} else {
for (i=0; (doc_locations[i] != 0xffffffff); i++) {
doc_probe(doc_locations[i]);
found, so the user knows we at least tried. */
if (!doclist) {
printk(KERN_INFO "No valid DiskOnChip devices found\n");
- return -ENODEV;
+ ret = -ENODEV;
+ goto outerr;
}
return 0;
+outerr:
+ free_rs(rs_decoder);
+ return ret;
}
-void __exit cleanup_nanddoc(void)
+static void __exit cleanup_nanddoc(void)
{
- struct mtd_info *mtd, *nextmtd;
- struct nand_chip *nand;
- struct doc_priv *doc;
-
- for (mtd = doclist; mtd; mtd = nextmtd) {
- nand = mtd->priv;
- doc = (void *)nand->priv;
+ /* Cleanup the nand/DoC resources */
+ release_nanddoc();
- nextmtd = doc->nextdoc;
- nand_release(mtd);
- iounmap((void *)doc->virtadr);
- kfree(mtd);
+ /* Free the reed solomon resources */
+ if (rs_decoder) {
+ free_rs(rs_decoder);
}
}