/*
 * Copyright 2006, Freie Universitaet Berlin. All rights reserved.
 * 
 * These sources were developed at the Freie Universitaet Berlin, Computer
 * Systems and Telematics group.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions 
 * are met:
 * - Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * - Neither the name of Freie Universitaet Berlin (FUB) nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * This software is provided by FUB and the contributors on an "as is"
 * basis, without any representations or warranties of any kind, express
 * or implied including, but not limited to, representations or
 * warranties of non-infringement, merchantability or fitness for a
 * particular purpose. In no event shall FUB or contributors be liable
 * for any direct, indirect, incidental, special, exemplary, or
 * consequential damages (including, but not limited to, procurement of
 * substitute goods or services; loss of use, data, or profits; or
 * business interruption) however caused and on any theory of liability,
 * whether in contract, strict liability, or tort (including negligence
 * or otherwise) arising in any way out of the use of this software, 
 * even if advised of the possibility of such damage.
 *
 * This implementation was originally developed by the CST group at the FUB.
 */

/**
 * \file	cc1020.c
 * \author	FUB ScatterWeb Developers, Michael Baar, Nicolas Tsiftes
 **/

#include <stdio.h>
#include <string.h>

#include "contiki.h"
#include "contiki-msb430.h"
#include "cc1020-internal.h"
#include "cc1020.h"
#include "lib/random.h"
#include "lib/crc16.h"
#include "net/rime/rimestats.h"
#include "dev/dma.h"
#include "sys/energest.h"
#include "isr_compat.h"

#define DEBUG 0
#if DEBUG
#include <stdio.h>
#define PRINTF(...) printf(__VA_ARGS__)
#else
#define PRINTF(...)
#endif

#define SEND_TIMEOUT 10

static int cc1020_calibrate(void);
static int cc1020_setupTX(int);
static int cc1020_setupRX(int);
static void cc1020_setupPD(void);
static void cc1020_wakeupTX(int);
static void cc1020_wakeupRX(int);
static uint8_t cc1020_read_reg(uint8_t addr);
static void cc1020_write_reg(uint8_t addr, uint8_t adata);
static void cc1020_load_config(const uint8_t *);
static void cc1020_reset(void);

static const uint8_t syncword[SYNCWORD_SIZE] = {0xD3, 0x91};

/* current mode of cc1020 chip */
static volatile enum cc1020_state cc1020_state = CC1020_OFF;
static volatile uint8_t cc1020_rxbuf[HDR_SIZE + CC1020_BUFFERSIZE];
static uint8_t cc1020_txbuf[PREAMBLE_SIZE + SYNCWORD_SIZE + HDR_SIZE +
			   CC1020_BUFFERSIZE + TAIL_SIZE];

/* number of bytes in receive and transmit buffers respectively. */
static uint8_t cc1020_rxlen;
static uint8_t cc1020_txlen;

/* received signal strength indicator reading for last received packet */
static volatile uint8_t rssi;

/* callback when a packet has been received */
static uint8_t cc1020_pa_power = PA_POWER;

static volatile char dma_done;

/* Radio driver AAPI functions. */
static int cc1020_init(void);
static int cc1020_prepare(const void *payload, unsigned short payload_len);
static int cc1020_transmit(unsigned short transmit_len);
static int cc1020_send(const void *payload, unsigned short payload_len);
static int cc1020_read(void *buf, unsigned short buf_len);
static int cc1020_channel_clear(void);
static int cc1020_receiving_packet(void);
static int cc1020_pending_packet(void);
static int cc1020_on(void);
static int cc1020_off(void);

const struct radio_driver cc1020_driver =
  {
    cc1020_init,
    cc1020_prepare,
    cc1020_transmit,
    cc1020_send,
    cc1020_read,
    cc1020_channel_clear,
    cc1020_receiving_packet,
    cc1020_pending_packet,
    cc1020_on,
    cc1020_off
  };

#define MS_DELAY(x) clock_delay(354 * (x))

PROCESS(cc1020_receiver_process, "CC1020 receiver");

static void
dma_callback(void)
{
  dma_done = 1;
}

static void
reset_receiver(void)
{
  /* reset receiver */
  cc1020_rxlen = 0;

  if((cc1020_state & CC1020_TURN_OFF) && (cc1020_txlen == 0)) {
    cc1020_off();
  } else {
    CC1020_SET_OPSTATE(CC1020_RX | CC1020_RX_SEARCHING);
    cc1020_set_rx();
    ENABLE_RX_IRQ();
  }
}

static int
cc1020_init(void)
{
  cc1020_setupPD();
  cc1020_reset();
  cc1020_load_config(cc1020_config_19200);

  /* init tx buffer with preamble + syncword */
  memset(cc1020_txbuf, PREAMBLE, PREAMBLE_SIZE);
  cc1020_txbuf[PREAMBLE_SIZE] = syncword[0];
  cc1020_txbuf[PREAMBLE_SIZE + 1] = syncword[1];

  /* calibrate receiver */
  cc1020_wakeupRX(RX_CURRENT);
  if(!cc1020_calibrate()) {
    PRINTF("cc1020: rx calibration failed\n");
    return -1;
  }

  /* calibrate transmitter */
  cc1020_wakeupTX(TX_CURRENT);
  if(!cc1020_calibrate()) {
    PRINTF("cc1020: tx calibration failed\n");
    return -1;
  }

  /* power down */
  cc1020_setupPD();

  process_start(&cc1020_receiver_process, NULL);
  dma_subscribe(0, dma_callback);

  return 0;
}

void
cc1020_set_rx(void)
{
  int s;

  s = splhigh();

  /* Reset SEL for P3[1-3] (CC DIO, DIO, DCLK) and P3[4-5] (Camera Rx+Tx) */
  P3SEL &= ~0x3E;
  IFG1 &= ~(UTXIE0 | URXIE0);	/* Clear interrupt flags */
  ME1 &= ~(UTXE0 | URXE0);	/* Disable Uart0 Tx + Rx */
  UCTL0 = SWRST;		/* U0 into reset state. */
  UCTL0 |= CHAR | SYNC;		/* 8-bit character, SPI, Slave mode */

  /* CKPH works also, but not CKPH+CKPL or none of them!! */
  UTCTL0 = CKPL | STC;
  URCTL0 = 0x00;
  UBR00 = 0x00;			/* No baudrate divider  */
  UBR10 = 0x00;			/* settings for a spi */
  UMCTL0 = 0x00;		/* slave. */
  ME1 |= URXE0;		/* Enable USART0 RXD, disabling does not yield any powersavings */
  P3SEL |= 0x0A;		/* Select rx line and clk */
  UCTL0 &= ~SWRST;		/* Clear reset bit */
  splx(s);

  /* configure driver */
  cc1020_rxlen = 0;		/* receive buffer position to start */
  CC1020_SET_OPSTATE(CC1020_RX | CC1020_RX_SEARCHING); /* driver state to receive mode */

  /* configure radio */
  ENERGEST_ON(ENERGEST_TYPE_LISTEN);
  cc1020_wakeupRX(RX_CURRENT);
  cc1020_setupRX(RX_CURRENT);
  LNA_POWER_ON();		/* enable amplifier */

  /* activate */
  IE1 |= URXIE0;		/* enable interrupt */
}

void
cc1020_set_tx(void)
{
  int s;

  /* configure radio rx */
  ENERGEST_OFF(ENERGEST_TYPE_LISTEN);
  LNA_POWER_OFF();		/* power down LNA */
  s = splhigh();
  DISABLE_RX_IRQ();
  P3SEL &= ~0x02;		/* Ensure Rx line is off */
  splx(s);

  /* configure radio tx */
  ENERGEST_ON(ENERGEST_TYPE_TRANSMIT);
  cc1020_wakeupTX(TX_CURRENT);
  cc1020_setupTX(TX_CURRENT);
  P3SEL |= 0x0C;		/* select Tx line and clk */
  U0CTL |= SWRST;		/* UART to reset mode */
  IFG1 &= ~UTXIFG0;		/* Reset IFG. */

  /* configure driver */
  CC1020_SET_OPSTATE(CC1020_TX);
}

void
cc1020_set_power(uint8_t pa_power)
{
  cc1020_pa_power = pa_power;
}

static int
cc1020_prepare(const void *payload, unsigned short payload_len)
{
  return -1;
}

static int
cc1020_transmit(unsigned short transmit_len)
{
  return 0;
}

static int
cc1020_send(const void *buf, unsigned short len)
{
  int normal_header = HDR_SIZE + len;
  uint16_t rxcrc = 0xffff; /* For checksum purposes */
  rtimer_clock_t timeout_time;

  timeout_time = RTIMER_NOW() + RTIMER_SECOND / 1000 * SEND_TIMEOUT;
  while(cc1020_state & CC1020_RX_RECEIVING) {
    if(RTIMER_CLOCK_LT(timeout_time, RTIMER_NOW())) {
      PRINTF("cc1020: transmission blocked by reception in progress\n");
      return RADIO_TX_ERR;
    }
  }
  
  if(cc1020_state == CC1020_OFF) {
    return RADIO_TX_ERR;
  }

  if(len > CC1020_BUFFERSIZE) {
    return RADIO_TX_ERR;
  }

  /* The preamble and the sync word are already in buffer. */
  cc1020_txlen = PREAMBLE_SIZE + SYNCWORD_SIZE;

  /* header */
  cc1020_txbuf[cc1020_txlen++] = 0x00;   
  cc1020_txbuf[cc1020_txlen++] = normal_header + CRC_SIZE;
  
  /* Adding the checksum on header and data */
  rxcrc = crc16_add(normal_header & 0xff, rxcrc); 
  rxcrc = crc16_add((normal_header >> 8) & 0xff, rxcrc);

  rxcrc = crc16_data(buf, len, rxcrc);  
  
  /* data to send */
  memcpy((char *)cc1020_txbuf + cc1020_txlen, buf, len);
  cc1020_txlen += len;

  /* Send checksum */
  cc1020_txbuf[cc1020_txlen++] = rxcrc >> 8;
  cc1020_txbuf[cc1020_txlen++] = rxcrc & 0xff;
   
  /* suffix */
  cc1020_txbuf[cc1020_txlen++] = TAIL;
  cc1020_txbuf[cc1020_txlen++] = TAIL; 

  /* Switch to transceive mode. */
  cc1020_set_tx();

  /* Initiate radio transfer. */
  dma_done = 0;
  dma_transfer((unsigned char *)&TXBUF0, cc1020_txbuf, cc1020_txlen);
  while(!dma_done);

  ENERGEST_OFF(ENERGEST_TYPE_TRANSMIT);
  RIMESTATS_ADD(lltx);

  /* clean up */
  cc1020_txlen = 0;
  if(cc1020_state & CC1020_TURN_OFF) {
    cc1020_off();
  } else {
    cc1020_set_rx();
  }

  return RADIO_TX_OK;
}

static int
cc1020_read(void *buf, unsigned short size)
{
  unsigned len;

  if(cc1020_rxlen <= HDR_SIZE) {
    return 0;
  }

  len = cc1020_rxlen - HDR_SIZE;
  if(len > size) {
    RIMESTATS_ADD(toolong);
    return -1;
  }

  memcpy(buf, (char *)cc1020_rxbuf + HDR_SIZE, len);
  RIMESTATS_ADD(llrx);
  reset_receiver();

  return len;
}

static int
cc1020_channel_clear(void)
{
  return (cc1020_read_reg(CC1020_STATUS) & CARRIER_SENSE);
}

static int
cc1020_receiving_packet(void)
{
  return cc1020_state & CC1020_RX_RECEIVING;
}

static int
cc1020_pending_packet(void)
{
  return cc1020_state & CC1020_RX_PROCESSING;
}

int
cc1020_on(void)
{
  if(cc1020_state == CC1020_OFF) {
    cc1020_set_rx();
  } else if(cc1020_state & CC1020_TURN_OFF) {
    cc1020_state &= ~CC1020_TURN_OFF;
    cc1020_set_rx();
  }

  return 1;
}

int
cc1020_off(void)
{
  int s;

  if(cc1020_state != CC1020_OFF) {
    if(cc1020_state & CC1020_RX_SEARCHING) {
      /* Discard the current read buffer when the radio is shutting down. */
      cc1020_rxlen = 0;

      LNA_POWER_OFF();		/* power down lna */
      s = splhigh();
      DISABLE_RX_IRQ();
      cc1020_state = CC1020_OFF;
      splx(s);
      cc1020_setupPD();		/* power down radio */
      ENERGEST_OFF(ENERGEST_TYPE_LISTEN);
      cc1020_state = CC1020_OFF;
    } else {
      cc1020_state |= CC1020_TURN_OFF;
    }
  }
  return 1;
}

uint8_t
cc1020_get_rssi(void)
{
  return (cc1020_read_reg(CC1020_RSS) & 0x7F);
}

uint8_t
cc1020_get_packet_rssi(void)
{
  return rssi;
}

PROCESS_THREAD(cc1020_receiver_process, ev, data)
{
  int len;

  PROCESS_BEGIN();

  while(1) {
    ev = PROCESS_EVENT_NONE;
    PROCESS_YIELD_UNTIL(ev == PROCESS_EVENT_POLL);	

    /* Verify the checksum. */
    uint16_t expected_crc = 0xffff;
    uint16_t actual_crc;

    actual_crc = (cc1020_rxbuf[cc1020_rxlen - CRC_SIZE] << 8) |
                  cc1020_rxbuf[cc1020_rxlen - CRC_SIZE + 1];
    cc1020_rxlen -= CRC_SIZE;
      
    expected_crc = crc16_add(cc1020_rxlen & 0xff, expected_crc);
    expected_crc = crc16_add((cc1020_rxlen >> 8) & 0xff, expected_crc);
    expected_crc = crc16_data((char *)&cc1020_rxbuf[HDR_SIZE],
			cc1020_rxlen - HDR_SIZE, expected_crc);

    if(expected_crc == actual_crc) {
      len = cc1020_read(packetbuf_dataptr(), PACKETBUF_SIZE);
      packetbuf_set_datalen(len);
      NETSTACK_RDC.input();
    } else {
      RIMESTATS_ADD(badcrc);
      reset_receiver();
    }
  }

  PROCESS_END();
}

ISR(UART0RX, cc1020_rxhandler)
{
  static signed char syncbs;
  static union {
    struct {
      uint8_t b2;
      uint8_t b1;
      uint8_t b4;
      uint8_t b3;
    };
    struct {
      uint16_t i1;
      uint16_t i2;
    };
  } shiftbuf;
  static unsigned char pktlen;

  if(cc1020_state & CC1020_RX_SEARCHING) {
    shiftbuf.b1 = shiftbuf.b2;
    shiftbuf.b2 = shiftbuf.b3;
    shiftbuf.b3 = shiftbuf.b4;
    shiftbuf.b4 = RXBUF0;
    if(shiftbuf.i1 == 0xAAD3 && shiftbuf.b3 == 0x91) {
      /* 0  AA D3 91 00 | FF 00 | */
      syncbs = 0;
      cc1020_rxbuf[cc1020_rxlen++] = shiftbuf.b4;
    } else if(shiftbuf.i1 == 0x5569 && shiftbuf.i2 == 0xC880) {
      /* 1  55 69 C8 80 | 7F 80 | */
      syncbs = -1;
    } else if(shiftbuf.i1 == 0xAAB4 && shiftbuf.i2 == 0xE440) {
      /* 2  AA B4 E4 40 | 3F C0 | */
      syncbs = -2;
    } else if(shiftbuf.i1 == 0x555A && shiftbuf.i2 == 0x7220) {
      /* 3  55 5A 72 20 | 1F E0 | */
      syncbs = -3;
    } else if(shiftbuf.i1 == 0xAAAD && shiftbuf.i2 == 0x3910) {
      /* 4  AA AD 39 10 | 0F F0 | */
      syncbs = -4;
    } else if(shiftbuf.i1 == 0x5556 && shiftbuf.i2 == 0x9C88) {
      /* 5  55 56 9C 88 | 07 F8 | */
      syncbs = +3;
    } else if(shiftbuf.i1 == 0xAAAB && shiftbuf.i2 == 0x4E44) {
      /* 6  AA AB 4E 44 | 03 FC | */
      syncbs = +2;
    } else if(shiftbuf.i1 == 0x5555 && shiftbuf.i2 == 0xA722) {
      /* 7  55 55 A7 22 | 01 FE | */
      syncbs = +1;
    } else {
      return;
    }
    rssi = cc1020_get_rssi();
    CC1020_SET_OPSTATE(CC1020_RX | CC1020_RX_RECEIVING);
  } else if(cc1020_state & CC1020_RX_RECEIVING) {
    if(syncbs == 0) {
      cc1020_rxbuf[cc1020_rxlen] = RXBUF0;
    } else {
      shiftbuf.b3 = shiftbuf.b4;
      shiftbuf.b4 = RXBUF0;
      if(syncbs < 0) {
        shiftbuf.i1 = shiftbuf.i2 << -syncbs;
        cc1020_rxbuf[cc1020_rxlen] = shiftbuf.b1;
      } else {
        shiftbuf.i1 = shiftbuf.i2 >> syncbs;
        cc1020_rxbuf[cc1020_rxlen] = shiftbuf.b2;
      }
    }

    cc1020_rxlen++;
	 
    if(cc1020_rxlen == HDR_SIZE) {
      pktlen = ((struct cc1020_header *)cc1020_rxbuf)->length;
      if(pktlen == 0 || pktlen > sizeof (cc1020_rxbuf)) {
        cc1020_rxlen = 0;
        CC1020_SET_OPSTATE(CC1020_RX | CC1020_RX_SEARCHING);
      }
    } else if(cc1020_rxlen > HDR_SIZE) {
      if(cc1020_rxlen == pktlen) {
        /* Disable interrupts while processing the packet. */
        DISABLE_RX_IRQ();
        CC1020_SET_OPSTATE(CC1020_RX | CC1020_RX_PROCESSING);
        _BIC_SR_IRQ(LPM3_bits);
        process_poll(&cc1020_receiver_process);
      }
    }
  } 
}

static void
cc1020_write_reg(uint8_t addr, uint8_t adata)
{
  unsigned int i;
  uint8_t data;

  data = addr << 1;
  PSEL_ON;

  /* Send address bits  */
  for(i = 0; i < 7; i++) {
    PCLK_LOW;
    if(data & 0x80) {
      PDI_HIGH;
    } else {
      PDI_LOW;
    }
    data = data << 1;
    PCLK_HIGH;
    nop();
  }

  /* Send read/write bit  */
  /* Ignore bit in data, always use 1  */
  PCLK_LOW;
  PDI_HIGH;
  PCLK_HIGH;
  nop();
  data = adata;

  /* Send data bits  */
  for(i = 0; i < 8; i++) {
    PCLK_LOW;
    if(data & 0x80) {
      PDI_HIGH;
    } else {
      PDI_LOW;
    }
    data = data << 1;
    PCLK_HIGH;
    nop();
  }

  PCLK_LOW;
  PSEL_OFF;
}

static uint8_t
cc1020_read_reg(uint8_t addr)
{
  unsigned int i;
  uint8_t data;

  data = addr << 1;
  PSEL_ON;

  /* Send address bits  */
  for(i = 0; i < 7; i++) {
    PCLK_LOW;
    if(data & 0x80) {
      PDI_HIGH;
    } else {
      PDI_LOW;
    }
    data = data << 1;
    PCLK_HIGH;
    nop();
  }

  /* Send read/write bit  */
  /* Ignore bit in data, always use 0  */
  PCLK_LOW;
  PDI_LOW;
  PCLK_HIGH;
  nop();
  PCLK_LOW;

  /* Receive data bits        */
  for(i = 0; i < 8; i++) {
    nop();
    PCLK_HIGH;
    nop();
    data = data << 1;
    if(PDO) {
      data++;
    }
    PCLK_LOW;
  }

  PSEL_OFF;

  return data;
}

static void
cc1020_load_config(const uint8_t * config)
{
  int i;

  for(i = 0; i < 0x28; i++)
    cc1020_write_reg(i, config[i]);
}

static void
cc1020_reset(void)
{
  /* Reset CC1020 */
  cc1020_write_reg(CC1020_MAIN, 0x0FU & ~0x01U);

  /* Bring CC1020 out of reset */
  cc1020_write_reg(CC1020_MAIN, 0x1F);
}

static int
cc1020_calibrate(void)
{
  unsigned int timeout_cnt;

  /* Turn off PA to avoid spurs during calibration in TX mode */
  cc1020_write_reg(CC1020_PA_POWER, 0x00);

  /* Start calibration */
  cc1020_write_reg(CC1020_CALIBRATE, 0xB5);
  while((cc1020_read_reg(CC1020_STATUS) & CAL_COMPLETE) == 0);

  /* Monitor lock */
  for(timeout_cnt = LOCK_TIMEOUT; timeout_cnt > 0; timeout_cnt--) {
    if(cc1020_read_reg(CC1020_STATUS) & LOCK_CONTINUOUS) {
    	break;
    }
  }

  /* Restore PA_POWER */
  cc1020_write_reg(CC1020_PA_POWER, cc1020_pa_power);

  /* Return state of LOCK_CONTINUOUS bit */
  return (cc1020_read_reg(CC1020_STATUS) & LOCK_CONTINUOUS) == LOCK_CONTINUOUS;
}

static int
cc1020_lock(void)
{
  char lock_status;
  int i;

  /* Monitor LOCK, lasts 420 - 510 cycles @ 4505600 = 93 us - 113 us */
  for(i = LOCK_TIMEOUT; i > 0; i--) {
    lock_status = cc1020_read_reg(CC1020_STATUS) & LOCK_CONTINUOUS;
    if(lock_status) {
      break;
    }
  }

  if(lock_status == LOCK_CONTINUOUS) {
    return LOCK_OK;
  }

  return cc1020_calibrate() ? LOCK_RECAL_OK : LOCK_NOK;
}

static int
cc1020_setupRX(int analog)
{
  char lock_status;

  /* Switch into RX, switch to freq. reg A */
  cc1020_write_reg(CC1020_MAIN, 0x01);
  lock_status = cc1020_lock();

  /* Switch RX part of CC1020 on */
  cc1020_write_reg(CC1020_INTERFACE, 0x02);

  /* Return LOCK status to application */
  return lock_status;
}

static int
cc1020_setupTX(int analog)
{
  char lock_status;

  /* Switch into TX, switch to freq. reg B */
  cc1020_write_reg(CC1020_MAIN, 0xC1);
  lock_status = cc1020_lock();

  /* Restore PA_POWER */
  cc1020_write_reg(CC1020_PA_POWER, cc1020_pa_power);

  /* Turn OFF DCLK squelch in TX */
  cc1020_write_reg(CC1020_INTERFACE, 0x01);

  /* Return LOCK status to application */
  return lock_status;
}

static void
cc1020_setupPD(void)
{
  /*
   *  Power down components and reset all registers except MAIN
   *  to their default values.
   */
  cc1020_write_reg(CC1020_MAIN,
        RESET_N | BIAS_PD | FS_PD | XOSC_PD | PD_MODE_1);

  /* Turn off the power amplifier. */
  cc1020_write_reg(CC1020_PA_POWER, 0x00);

  cc1020_write_reg(CC1020_POWERDOWN, 0x1F);
}

static void
cc1020_wakeupRX(int analog)
{
  /* Turn on crystal oscillator core. */
  cc1020_write_reg(CC1020_MAIN, 0x1B);

  /* Setup bias current adjustment. */
  cc1020_write_reg(CC1020_ANALOG, analog);

  /*
   * Wait for the crystal oscillator to stabilize.
   * This typically takes 2-5 ms.
   */
  MS_DELAY(5);

  /* Turn on bias generator. */
  cc1020_write_reg(CC1020_MAIN, 0x19);

  /* Turn on frequency synthesizer. */
  cc1020_write_reg(CC1020_MAIN, 0x11);
}

static void
cc1020_wakeupTX(int analog)
{
  /* Turn on crystal oscillator core. */
  cc1020_write_reg(CC1020_MAIN, 0xDB);

  /* Setup bias current adjustment. */
  cc1020_write_reg(CC1020_ANALOG, analog);

  /*
   * Wait for the crystal oscillator to stabilize.
   * This typically takes 2-5 ms.
   */
  MS_DELAY(5);

  /* Turn on bias generator. */
  cc1020_write_reg(CC1020_MAIN, 0xD9);

  /* Turn on frequency synthesizer. */
  MS_DELAY(1);
  cc1020_write_reg(CC1020_MAIN, 0xD1);
}