/****************************************************************************
 * drivers/mtd/hamming.c
 *
 *   Copyright (C) 2013 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * This logic was taken directly from Atmel sample code with only
 * modifications for better integration with NuttX.  The Atmel sample
 * code has a BSD compatible license that requires this copyright notice:
 *
 *   Copyright (c) 2011, Atmel Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. Neither the names NuttX nor Atmel 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#include <nuttx/mtd/nand_config.h>

#include <stdint.h>
#include <assert.h>
#include <debug.h>

#include <nuttx/mtd/hamming.h>

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: hamming_bitsinbyte
 *
 * Description:
 *   Counts the number of bits set to '1' in the given byte.
 *
 * Input Parameters:
 *   bytes - The byte to use.
 *
 * Returned Values:
 *   Returns the number of bits set to '1' in the given byte.
 *
 ****************************************************************************/

static unsigned int hamming_bitsinbyte(uint8_t byte)
{
  unsigned int count = 0;

  while (byte != 0)
    {
      if ((byte & 1) != 0)
        {
          count++;
        }
      byte >>= 1;
    }

  return count;
}

/****************************************************************************
 * Name: hamming_bitsincode256
 *
 * Description:
 *   Counts the number of bits set to '1' in the given hamming code.
 *
 * Input Parameters:
 *   code - Hamming code
 *
 * Returned Values:
 *   Returns the number of bits set to '1' in the given hamming code.
 *
 ****************************************************************************/

static uint8_t hamming_bitsincode256(FAR uint8_t *code)
{
  return hamming_bitsinbyte(code[0]) +
         hamming_bitsinbyte(code[1]) +
         hamming_bitsinbyte(code[2]);
}

/****************************************************************************
 * Name: hamming_compute256
 *
 * Description:
 *   Calculates the 22-bit hamming code for a 256-bytes block of data.
 *
 * Input Parameters:
 *   data - Data buffer to calculate code
 *   code - Pointer to a buffer where the code should be stored
 *
 * Returned Values:
 *   None
 *
 ****************************************************************************/

static void hamming_compute256(FAR const uint8_t *data, FAR uint8_t *code)
{
  uint8_t colsum = 0;
  uint8_t evenline = 0;
  uint8_t oddline = 0;
  uint8_t evencol = 0;
  uint8_t oddcol = 0;
  int i;

  /* Xor all bytes together to get the column sum;
   * At the same time, calculate the even and odd line codes
   */

  for (i = 0; i < 256; i++)
    {
      colsum ^= data[i];

      /* If the xor sum of the byte is 0, then this byte has no incidence on
       * the computed code; so check if the sum is 1.
       */

      if ((hamming_bitsinbyte(data[i]) & 1) == 1)
        {
          /* Parity groups are formed by forcing a particular index bit to 0
           * (even) or 1 (odd).
           * Example on one byte:
           *
           * bits (dec)  7   6   5   4   3   2   1   0
           *      (bin) 111 110 101 100 011 010 001 000
           *                            '---'---'---'----------.
           *                                                   |
           * groups P4' ooooooooooooooo eeeeeeeeeeeeeee P4     |
           *        P2' ooooooo eeeeeee ooooooo eeeeeee P2     |
           *        P1' ooo eee ooo eee ooo eee ooo eee P1     |
           *                                                   |
           * We can see that:                                  |
           *  - P4  -> bit 2 of index is 0 --------------------'
           *  - P4' -> bit 2 of index is 1.
           *  - P2  -> bit 1 of index if 0.
           *  - etc...
           * We deduce that a bit position has an impact on all even Px if
           * the log2(x)nth bit of its index is 0
           *     ex: log2(4) = 2, bit2 of the index must be 0 (-> 0 1 2 3)
           * and on all odd Px' if the log2(x)nth bit of its index is 1
           *     ex: log2(2) = 1, bit1 of the index must be 1 (-> 0 1 4 5)
           *
           * As such, we calculate all the possible Px and Px' values at the
           * same time in two variables, evenline and oddline, such as
           *     evenline bits: P128  P64  P32  P16  P8  P4  P2  P1
           *     oddline  bits: P128' P64' P32' P16' P8' P4' P2' P1'
           */

          evenline ^= (255 - i);
          oddline ^= i;
        }
    }

  /* At this point, we have the line parities, and the column sum. First, We
   * must caculate the parity group values on the column sum.
   */

  for (i = 0; i < 8; i++)
    {
      if (colsum & 1)
        {
          evencol ^= (7 - i);
          oddcol ^= i;
        }

      colsum >>= 1;
    }

  /* Now, we must interleave the parity values, to obtain the following layout:
   * Code[0] = Line1
   * Code[1] = Line2
   * Code[2] = Column
   * Line = Px' Px P(x-1)- P(x-1) ...
   * Column = P4' P4 P2' P2 P1' P1 PadBit PadBit
   */

  code[0] = 0;
  code[1] = 0;
  code[2] = 0;

  for (i = 0; i < 4; i++)
    {
      code[0] <<= 2;
      code[1] <<= 2;
      code[2] <<= 2;

      /* Line 1 */

      if ((oddline & 0x80) != 0)
        {
          code[0] |= 2;
        }

      if ((evenline & 0x80) != 0)
        {
          code[0] |= 1;
        }

      /* Line 2 */

      if ((oddline & 0x08) != 0)
        {
          code[1] |= 2;
        }

      if ((evenline & 0x08) != 0)
        {
          code[1] |= 1;
        }

      /* Column */

      if ((oddcol & 0x04) != 0)
        {
          code[2] |= 2;
        }

      if ((evencol & 0x04) != 0)
        {
          code[2] |= 1;
        }

      oddline <<= 1;
      evenline <<= 1;
      oddcol <<= 1;
      evencol <<= 1;
    }

  /* Invert codes (linux compatibility) */

  code[0] = (~(uint32_t)code[0]);
  code[1] = (~(uint32_t)code[1]);
  code[2] = (~(uint32_t)code[2]);
}

/****************************************************************************
 * Name: hamming_verify256
 *
 * Description:
 *   Verifies and corrects a 256-bytes block of data using the given 22-bits
 *   hamming code.
 *
 * Input Parameters:
 *   data     - Data buffer to check
 *   original - Hamming code to use for verifying the data
 *
 * Returned Values:
 *   Zero on success, otherwise returns a HAMMING_ERROR_ code.
 *
 ****************************************************************************/

static int hamming_verify256(FAR uint8_t *data, FAR const uint8_t *original)
{
  /* Calculate new code */

  uint8_t computed[3];
  uint8_t correction[3];

  hamming_compute256(data, computed);

  /* Xor both codes together */

  correction[0] = computed[0] ^ original[0];
  correction[1] = computed[1] ^ original[1];
  correction[2] = computed[2] ^ original[2];

  /* If all bytes are 0, there is no error */

  if ((correction[0] == 0) && (correction[1] == 0) && (correction[2] == 0))
    {
      return 0;
    }

  /* There are bit errors */

  fvdbg("Read:       %02x %02x %02x\n",
        original[0], original[1], original[2]);
  fvdbg("Computed:   %02x %02x %02x\n",
        computed[0], computed[1], computed[2]);
  fvdbg("Correction: %02x %02x %02x\n",
        correction[0], correction[1], correction[2]);

  /* If there is a single bit error, there are 11 bits set to 1 */

  if (hamming_bitsincode256(correction) == 11)
    {
      uint8_t byte;
      uint8_t bit;

      /* Get byte and bit indexes */

      byte  =  correction[0]       & 0x80;
      byte |= (correction[0] << 1) & 0x40;
      byte |= (correction[0] << 2) & 0x20;
      byte |= (correction[0] << 3) & 0x10;

      byte |= (correction[1] >> 4) & 0x08;
      byte |= (correction[1] >> 3) & 0x04;
      byte |= (correction[1] >> 2) & 0x02;
      byte |= (correction[1] >> 1) & 0x01;

      bit   = (correction[2] >> 5) & 0x04;
      bit  |= (correction[2] >> 4) & 0x02;
      bit  |= (correction[2] >> 3) & 0x01;

      /* Correct bit */

      fdbg("Correcting byte %d at bit %d\n", byte, bit);
      data[byte] ^= (1 << bit);

      return HAMMING_ERROR_SINGLEBIT;
    }

  /* Check if ECC has been corrupted */

  if (hamming_bitsincode256(correction) == 1)
    {
      fdbg("ERROR: ECC has been correupted\n");
      return HAMMING_ERROR_ECC;
    }

  /* Otherwise, there are multiple bit errors */

  else
    {
      fdbg("ERROR: Multiple bit errors\n");
      return HAMMING_ERROR_MULTIPLEBITS;
    }
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: hamming_compute256x
 *
 * Description:
 *   Computes 3-bytes hamming codes for a data block whose size is multiple
 *   of 256 bytes. Each 256 bytes block gets its own code.
 *
 * Input Parameters:
 *   data - Data to compute code for
 *   size - Data size in bytes
 *   code - Codes buffer
 *
 * Returned Values:
 *   None
 *
 ****************************************************************************/

void hamming_compute256x(FAR const uint8_t *data, size_t size, uint8_t *code)
{
  ssize_t remaining = (ssize_t)size;
  DEBUGASSERT((size & 0xff) == 0);

  /* Loop, computing the Hamming code on each 256 byte chunk of data */

  while (remaining > 0)
    {
      hamming_compute256(data, code);

      /* Setup for the next 256 byte chunk */

      data      += 256;
      code      += 3;
      remaining -= 256;
    }
}

/****************************************************************************
 * Name: hamming_verify256x
 *
 * Description:
 *   Verifies 3-bytes hamming codes for a data block whose size is multiple
 *   of 256 bytes. Each 256-bytes block is verified with its own code.
 *
 * Input Parameters:
 *   data - Data buffer to verify
 *   size - Size of the data in bytes
 *   code - Original codes
 *
 * Returned Values:
 *   Return 0 if the data is correct, HAMMING_ERROR_SINGLEBIT if one or more
 *   block(s) have had a single bit corrected, or either HAMMING_ERROR_ECC
 *   or HAMMING_ERROR_MULTIPLEBITS.
 *
 ****************************************************************************/

int hamming_verify256x(FAR uint8_t *data, size_t size, FAR const uint8_t *code)
{
  ssize_t remaining = (ssize_t)size;
  int result = HAMMING_SUCCESS;
  int ret;

  DEBUGASSERT((size & 0xff) == 0);

  /* Loop, verifying each 256 byte chunk of data */

  while (remaining > 0)
    {
      result = hamming_verify256(data, code);
      if (result != HAMMING_SUCCESS)
        {
          /* Check for the case of a single bit error that was corrected */

          if (result == HAMMING_ERROR_SINGLEBIT)
            {
              /* Report the error, but continue verifying */

              ret = HAMMING_ERROR_SINGLEBIT;
            }
          else
            {
              /* A bad error occurred, abort the verification and return the
               * error code
               */

              return result;
            }
        }

      /* Setup for the next 256 byte chunk */

      data      += 256;
      code      += 3;
      remaining -= 256;
    }

  return ret;
}
