/* Blackfin Direct Memory Access (DMA) Controller model.

   Copyright (C) 2010-2017 Free Software Foundation, Inc.
   Contributed by Analog Devices, Inc.

   This file is part of simulators.

   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 3 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, see <http://www.gnu.org/licenses/>.  */

#include "config.h"

#include "sim-main.h"
#include "sim-hw.h"
#include "devices.h"
#include "hw-device.h"
#include "dv-bfin_dma.h"
#include "dv-bfin_dmac.h"

struct bfin_dmac
{
  /* This top portion matches common dv_bfin struct.  */
  bu32 base;
  struct hw *dma_master;
  bool acked;

  const char * const *pmap;
  unsigned int pmap_count;
};

struct hw *
bfin_dmac_get_peer (struct hw *dma, bu16 pmap)
{
  struct hw *ret, *me;
  struct bfin_dmac *dmac;
  char peer[100];

  me = hw_parent (dma);
  dmac = hw_data (me);
  if (pmap & CTYPE)
    {
      /* MDMA channel.  */
      unsigned int chan_num = dv_get_bus_num (dma);
      if (chan_num & 1)
	chan_num &= ~1;
      else
	chan_num |= 1;
      sprintf (peer, "%s/bfin_dma@%u", hw_path (me), chan_num);
    }
  else
    {
      unsigned int idx = pmap >> 12;
      if (idx >= dmac->pmap_count)
	hw_abort (me, "Invalid DMA peripheral_map %#x", pmap);
      else
	sprintf (peer, "/core/bfin_%s", dmac->pmap[idx]);
    }

  ret = hw_tree_find_device (me, peer);
  if (!ret)
    hw_abort (me, "Unable to locate peer for %s (pmap:%#x %s)",
	      hw_name (dma), pmap, peer);
  return ret;
}

bu16
bfin_dmac_default_pmap (struct hw *dma)
{
  unsigned int chan_num = dv_get_bus_num (dma);

  if (chan_num < BFIN_DMAC_MDMA_BASE)
    return (chan_num % 12) << 12;
  else
    return CTYPE;	/* MDMA */
}

static const char * const bfin_dmac_50x_pmap[] =
{
  "ppi@0", "rsi", "sport@0", "sport@0", "sport@1", "sport@1",
  "spi@0", "spi@1", "uart2@0", "uart2@0", "uart2@1", "uart2@1",
};

/* XXX: Need to figure out how to handle portmuxed DMA channels.  */
static const struct hw_port_descriptor bfin_dmac_50x_ports[] =
{
  { "ppi@0",       0, 0, input_port, },
  { "rsi",         1, 0, input_port, },
  { "sport@0_rx",  2, 0, input_port, },
  { "sport@0_tx",  3, 0, input_port, },
  { "sport@1_tx",  4, 0, input_port, },
  { "sport@1_rx",  5, 0, input_port, },
  { "spi@0",       6, 0, input_port, },
  { "spi@1",       7, 0, input_port, },
  { "uart2@0_rx",  8, 0, input_port, },
  { "uart2@0_tx",  9, 0, input_port, },
  { "uart2@1_rx", 10, 0, input_port, },
  { "uart2@1_tx", 11, 0, input_port, },
  { NULL, 0, 0, 0, },
};

static const char * const bfin_dmac_51x_pmap[] =
{
  "ppi@0", "emac", "emac", "sport@0", "sport@0", "sport@1",
  "sport@1", "spi@0", "uart@0", "uart@0", "uart@1", "uart@1",
};

/* XXX: Need to figure out how to handle portmuxed DMA channels.  */
static const struct hw_port_descriptor bfin_dmac_51x_ports[] =
{
  { "ppi@0",       0, 0, input_port, },
  { "emac_rx",     1, 0, input_port, },
  { "emac_tx",     2, 0, input_port, },
  { "sport@0_rx",  3, 0, input_port, },
  { "sport@0_tx",  4, 0, input_port, },
/*{ "rsi",         4, 0, input_port, },*/
  { "sport@1_tx",  5, 0, input_port, },
/*{ "spi@1",       5, 0, input_port, },*/
  { "sport@1_rx",  6, 0, input_port, },
  { "spi@0",       7, 0, input_port, },
  { "uart@0_rx",   8, 0, input_port, },
  { "uart@0_tx",   9, 0, input_port, },
  { "uart@1_rx",  10, 0, input_port, },
  { "uart@1_tx",  11, 0, input_port, },
  { NULL, 0, 0, 0, },
};

static const char * const bfin_dmac_52x_pmap[] =
{
  "ppi@0", "emac", "emac", "sport@0", "sport@0", "sport@1",
  "sport@1", "spi", "uart@0", "uart@0", "uart@1", "uart@1",
};

/* XXX: Need to figure out how to handle portmuxed DMA channels
        like PPI/NFC here which share DMA0.  */
static const struct hw_port_descriptor bfin_dmac_52x_ports[] =
{
  { "ppi@0",       0, 0, input_port, },
/*{ "nfc",         0, 0, input_port, },*/
  { "emac_rx",     1, 0, input_port, },
/*{ "hostdp",      1, 0, input_port, },*/
  { "emac_tx",     2, 0, input_port, },
/*{ "nfc",         2, 0, input_port, },*/
  { "sport@0_tx",  3, 0, input_port, },
  { "sport@0_rx",  4, 0, input_port, },
  { "sport@1_tx",  5, 0, input_port, },
  { "sport@1_rx",  6, 0, input_port, },
  { "spi",         7, 0, input_port, },
  { "uart@0_tx",   8, 0, input_port, },
  { "uart@0_rx",   9, 0, input_port, },
  { "uart@1_tx",  10, 0, input_port, },
  { "uart@1_rx",  11, 0, input_port, },
  { NULL, 0, 0, 0, },
};

static const char * const bfin_dmac_533_pmap[] =
{
  "ppi@0", "sport@0", "sport@0", "sport@1", "sport@1", "spi",
  "uart@0", "uart@0",
};

static const struct hw_port_descriptor bfin_dmac_533_ports[] =
{
  { "ppi@0",       0, 0, input_port, },
  { "sport@0_tx",  1, 0, input_port, },
  { "sport@0_rx",  2, 0, input_port, },
  { "sport@1_tx",  3, 0, input_port, },
  { "sport@1_rx",  4, 0, input_port, },
  { "spi",         5, 0, input_port, },
  { "uart@0_tx",   6, 0, input_port, },
  { "uart@0_rx",   7, 0, input_port, },
  { NULL, 0, 0, 0, },
};

static const char * const bfin_dmac_537_pmap[] =
{
  "ppi@0", "emac", "emac", "sport@0", "sport@0", "sport@1",
  "sport@1", "spi", "uart@0", "uart@0", "uart@1", "uart@1",
};

static const struct hw_port_descriptor bfin_dmac_537_ports[] =
{
  { "ppi@0",       0, 0, input_port, },
  { "emac_rx",     1, 0, input_port, },
  { "emac_tx",     2, 0, input_port, },
  { "sport@0_tx",  3, 0, input_port, },
  { "sport@0_rx",  4, 0, input_port, },
  { "sport@1_tx",  5, 0, input_port, },
  { "sport@1_rx",  6, 0, input_port, },
  { "spi",         7, 0, input_port, },
  { "uart@0_tx",   8, 0, input_port, },
  { "uart@0_rx",   9, 0, input_port, },
  { "uart@1_tx",  10, 0, input_port, },
  { "uart@1_rx",  11, 0, input_port, },
  { NULL, 0, 0, 0, },
};

static const char * const bfin_dmac0_538_pmap[] =
{
  "ppi@0", "sport@0", "sport@0", "sport@1", "sport@1", "spi@0",
  "uart@0", "uart@0",
};

static const struct hw_port_descriptor bfin_dmac0_538_ports[] =
{
  { "ppi@0",       0, 0, input_port, },
  { "sport@0_rx",  1, 0, input_port, },
  { "sport@0_tx",  2, 0, input_port, },
  { "sport@1_rx",  3, 0, input_port, },
  { "sport@1_tx",  4, 0, input_port, },
  { "spi@0",       5, 0, input_port, },
  { "uart@0_rx",   6, 0, input_port, },
  { "uart@0_tx",   7, 0, input_port, },
  { NULL, 0, 0, 0, },
};

static const char * const bfin_dmac1_538_pmap[] =
{
  "sport@2", "sport@2", "sport@3", "sport@3", NULL, NULL,
  "spi@1", "spi@2", "uart@1", "uart@1", "uart@2", "uart@2",
};

static const struct hw_port_descriptor bfin_dmac1_538_ports[] =
{
  { "sport@2_rx",  0, 0, input_port, },
  { "sport@2_tx",  1, 0, input_port, },
  { "sport@3_rx",  2, 0, input_port, },
  { "sport@3_tx",  3, 0, input_port, },
  { "spi@1",       6, 0, input_port, },
  { "spi@2",       7, 0, input_port, },
  { "uart@1_rx",   8, 0, input_port, },
  { "uart@1_tx",   9, 0, input_port, },
  { "uart@2_rx",  10, 0, input_port, },
  { "uart@2_tx",  11, 0, input_port, },
  { NULL, 0, 0, 0, },
};

static const char * const bfin_dmac0_54x_pmap[] =
{
  "sport@0", "sport@0", "sport@1", "sport@1", "spi@0", "spi@1",
  "uart2@0", "uart2@0", "uart2@1", "uart2@1", "atapi", "atapi",
};

static const struct hw_port_descriptor bfin_dmac0_54x_ports[] =
{
  { "sport@0_rx",  0, 0, input_port, },
  { "sport@0_tx",  1, 0, input_port, },
  { "sport@1_rx",  2, 0, input_port, },
  { "sport@1_tx",  3, 0, input_port, },
  { "spi@0",       4, 0, input_port, },
  { "spi@1",       5, 0, input_port, },
  { "uart2@0_rx",  6, 0, input_port, },
  { "uart2@0_tx",  7, 0, input_port, },
  { "uart2@1_rx",  8, 0, input_port, },
  { "uart2@1_tx",  9, 0, input_port, },
  { "atapi",      10, 0, input_port, },
  { "atapi",      11, 0, input_port, },
  { NULL, 0, 0, 0, },
};

static const char * const bfin_dmac1_54x_pmap[] =
{
  "eppi@0", "eppi@1", "eppi@2", "pixc", "pixc", "pixc",
  "sport@2", "sport@2", "sport@3", "sport@3", "sdh",
  "spi@2", "uart2@2", "uart2@2", "uart2@3", "uart2@3",
};

static const struct hw_port_descriptor bfin_dmac1_54x_ports[] =
{
  { "eppi@0",      0, 0, input_port, },
  { "eppi@1",      1, 0, input_port, },
  { "eppi@2",      2, 0, input_port, },
  { "pixc",        3, 0, input_port, },
  { "pixc",        4, 0, input_port, },
  { "pixc",        5, 0, input_port, },
  { "sport@2_rx",  6, 0, input_port, },
  { "sport@2_tx",  7, 0, input_port, },
  { "sport@3_rx",  8, 0, input_port, },
  { "sport@3_tx",  9, 0, input_port, },
  { "sdh",        10, 0, input_port, },
/*{ "nfc",        10, 0, input_port, },*/
  { "spi@2",      11, 0, input_port, },
  { "uart2@2_rx", 12, 0, input_port, },
  { "uart2@2_tx", 13, 0, input_port, },
  { "uart2@3_rx", 14, 0, input_port, },
  { "uart2@3_tx", 15, 0, input_port, },
  { NULL, 0, 0, 0, },
};

static const char * const bfin_dmac0_561_pmap[] =
{
  "sport@0", "sport@0", "sport@1", "sport@1", "spi", "uart@0", "uart@0",
};

static const struct hw_port_descriptor bfin_dmac0_561_ports[] =
{
  { "sport@0_rx",  0, 0, input_port, },
  { "sport@0_tx",  1, 0, input_port, },
  { "sport@1_rx",  2, 0, input_port, },
  { "sport@1_tx",  3, 0, input_port, },
  { "spi@0",       4, 0, input_port, },
  { "uart@0_rx",   5, 0, input_port, },
  { "uart@0_tx",   6, 0, input_port, },
  { NULL, 0, 0, 0, },
};

static const char * const bfin_dmac1_561_pmap[] =
{
  "ppi@0", "ppi@1",
};

static const struct hw_port_descriptor bfin_dmac1_561_ports[] =
{
  { "ppi@0",       0, 0, input_port, },
  { "ppi@1",       1, 0, input_port, },
  { NULL, 0, 0, 0, },
};

static const char * const bfin_dmac_59x_pmap[] =
{
  "ppi@0", "sport@0", "sport@0", "sport@1", "sport@1", "spi@0",
  "spi@1", "uart@0", "uart@0",
};

static const struct hw_port_descriptor bfin_dmac_59x_ports[] =
{
  { "ppi@0",       0, 0, input_port, },
  { "sport@0_tx",  1, 0, input_port, },
  { "sport@0_rx",  2, 0, input_port, },
  { "sport@1_tx",  3, 0, input_port, },
  { "sport@1_rx",  4, 0, input_port, },
  { "spi@0",       5, 0, input_port, },
  { "spi@1",       6, 0, input_port, },
  { "uart@0_rx",   7, 0, input_port, },
  { "uart@0_tx",   8, 0, input_port, },
  { NULL, 0, 0, 0, },
};

static void
bfin_dmac_port_event (struct hw *me, int my_port, struct hw *source,
		      int source_port, int level)
{
  SIM_DESC sd = hw_system (me);
  struct bfin_dmac *dmac = hw_data (me);
  struct hw *dma = hw_child (me);

  while (dma)
    {
      bu16 pmap;
      sim_hw_io_read_buffer (sd, dma, &pmap, 0, 0x2c, sizeof (pmap));
      pmap >>= 12;
      if (pmap == my_port)
	break;
      dma = hw_sibling (dma);
    }

  if (!dma)
    hw_abort (me, "no valid dma mapping found for %s", dmac->pmap[my_port]);

  /* Have the DMA channel raise its interrupt to the SIC.  */
  hw_port_event (dma, 0, 1);
}

static void
bfin_dmac_finish (struct hw *me)
{
  struct bfin_dmac *dmac;
  unsigned int dmac_num = dv_get_bus_num (me);

  dmac = HW_ZALLOC (me, struct bfin_dmac);

  set_hw_data (me, dmac);
  set_hw_port_event (me, bfin_dmac_port_event);

  /* Initialize the DMA Controller.  */
  if (hw_find_property (me, "type") == NULL)
    hw_abort (me, "Missing \"type\" property");

  switch (hw_find_integer_property (me, "type"))
    {
    case 500 ... 509:
      if (dmac_num != 0)
	hw_abort (me, "this Blackfin only has a DMAC0");
      dmac->pmap = bfin_dmac_50x_pmap;
      dmac->pmap_count = ARRAY_SIZE (bfin_dmac_50x_pmap);
      set_hw_ports (me, bfin_dmac_50x_ports);
      break;
    case 510 ... 519:
      if (dmac_num != 0)
	hw_abort (me, "this Blackfin only has a DMAC0");
      dmac->pmap = bfin_dmac_51x_pmap;
      dmac->pmap_count = ARRAY_SIZE (bfin_dmac_51x_pmap);
      set_hw_ports (me, bfin_dmac_51x_ports);
      break;
    case 522 ... 527:
      if (dmac_num != 0)
	hw_abort (me, "this Blackfin only has a DMAC0");
      dmac->pmap = bfin_dmac_52x_pmap;
      dmac->pmap_count = ARRAY_SIZE (bfin_dmac_52x_pmap);
      set_hw_ports (me, bfin_dmac_52x_ports);
      break;
    case 531 ... 533:
      if (dmac_num != 0)
	hw_abort (me, "this Blackfin only has a DMAC0");
      dmac->pmap = bfin_dmac_533_pmap;
      dmac->pmap_count = ARRAY_SIZE (bfin_dmac_533_pmap);
      set_hw_ports (me, bfin_dmac_533_ports);
      break;
    case 534:
    case 536:
    case 537:
      if (dmac_num != 0)
	hw_abort (me, "this Blackfin only has a DMAC0");
      dmac->pmap = bfin_dmac_537_pmap;
      dmac->pmap_count = ARRAY_SIZE (bfin_dmac_537_pmap);
      set_hw_ports (me, bfin_dmac_537_ports);
      break;
    case 538 ... 539:
      switch (dmac_num)
	{
	case 0:
	  dmac->pmap = bfin_dmac0_538_pmap;
	  dmac->pmap_count = ARRAY_SIZE (bfin_dmac0_538_pmap);
	  set_hw_ports (me, bfin_dmac0_538_ports);
	  break;
	case 1:
	  dmac->pmap = bfin_dmac1_538_pmap;
	  dmac->pmap_count = ARRAY_SIZE (bfin_dmac1_538_pmap);
	  set_hw_ports (me, bfin_dmac1_538_ports);
	  break;
	default:
	  hw_abort (me, "this Blackfin only has a DMAC0 & DMAC1");
	}
      break;
    case 540 ... 549:
      switch (dmac_num)
	{
	case 0:
	  dmac->pmap = bfin_dmac0_54x_pmap;
	  dmac->pmap_count = ARRAY_SIZE (bfin_dmac0_54x_pmap);
	  set_hw_ports (me, bfin_dmac0_54x_ports);
	  break;
	case 1:
	  dmac->pmap = bfin_dmac1_54x_pmap;
	  dmac->pmap_count = ARRAY_SIZE (bfin_dmac1_54x_pmap);
	  set_hw_ports (me, bfin_dmac1_54x_ports);
	  break;
	default:
	  hw_abort (me, "this Blackfin only has a DMAC0 & DMAC1");
	}
      break;
    case 561:
      switch (dmac_num)
	{
	case 0:
	  dmac->pmap = bfin_dmac0_561_pmap;
	  dmac->pmap_count = ARRAY_SIZE (bfin_dmac0_561_pmap);
	  set_hw_ports (me, bfin_dmac0_561_ports);
	  break;
	case 1:
	  dmac->pmap = bfin_dmac1_561_pmap;
	  dmac->pmap_count = ARRAY_SIZE (bfin_dmac1_561_pmap);
	  set_hw_ports (me, bfin_dmac1_561_ports);
	  break;
	default:
	  hw_abort (me, "this Blackfin only has a DMAC0 & DMAC1");
	}
      break;
    case 590 ... 599:
      if (dmac_num != 0)
	hw_abort (me, "this Blackfin only has a DMAC0");
      dmac->pmap = bfin_dmac_59x_pmap;
      dmac->pmap_count = ARRAY_SIZE (bfin_dmac_59x_pmap);
      set_hw_ports (me, bfin_dmac_59x_ports);
      break;
    default:
      hw_abort (me, "no support for DMAC on this Blackfin model yet");
    }
}

const struct hw_descriptor dv_bfin_dmac_descriptor[] =
{
  {"bfin_dmac", bfin_dmac_finish,},
  {NULL, NULL},
};
