text: /*
   *   OpenBIOS Sparc OBIO driver
   *
   *   (C) 2004 Stefan Reinauer <stepan@openbios.org>
   *   (C) 2005 Ed Schouten <ed@fxq.nl>
   *
   *   This program is free software; you can redistribute it and/or
   *   modify it under the terms of the GNU General Public License
   *   version 2
   *
   */
  
  #include "config.h"
  #include "libopenbios/bindings.h"
  #include "kernel/kernel.h"
  #include "libc/byteorder.h"
  #include "libc/vsprintf.h"
  
  #include "drivers/drivers.h"
  #include "arch/common/nvram.h"
  #include "libopenbios/ofmem.h"
  #include "obio.h"
  #include "escc.h"
  
  #define	PROMDEV_KBD	0		/* input from keyboard */
  #define	PROMDEV_SCREEN	0		/* output to screen */
  #define	PROMDEV_TTYA	1		/* in/out to ttya */
  
  /* DECLARE data structures for the nodes.  */
  DECLARE_UNNAMED_NODE( ob_obio, INSTALL_OPEN, sizeof(int) );
  
  void
  ob_new_obio_device(const char *name, const char *type)
  {
      push_str("/obio");
      fword("find-device");
      fword("new-device");
  
      push_str(name);
      fword("device-name");
  
      if (type) {
          push_str(type);
          fword("device-type");
      }
  }
  
  static unsigned long
  map_reg(uint64_t base, uint64_t offset, unsigned long size, int map,
          int phys_hi)
  {
      PUSH(phys_hi);
      fword("encode-int");
      PUSH(offset);
      fword("encode-int");
      fword("encode+");
      PUSH(size);
      fword("encode-int");
      fword("encode+");
      push_str("reg");
      fword("property");
  
      if (map) {
          unsigned long addr;
  
          addr = (unsigned long)ofmem_map_io(base + offset, size);
  
          PUSH(addr);
          fword("encode-int");
          push_str("address");
          fword("property");
          return addr;
      }
      return 0;
  }
  
  unsigned long
  ob_reg(uint64_t base, uint64_t offset, unsigned long size, int map)
  {
      return map_reg(base, offset, size, map, 0);
  }
  
  void
  ob_intr(int intr)
  {
      PUSH(intr);
      fword("encode-int");
      PUSH(0);
      fword("encode-int");
      fword("encode+");
      push_str("intr");
      fword("property");
  }
  
  void
  ob_eccmemctl_init(uint64_t base)
  {
      uint32_t version, *regs;
      const char *mc_type;
  
      push_str("/");
      fword("find-device");
      fword("new-device");
  
      push_str("eccmemctl");
      fword("device-name");
  
      PUSH(0x20);
      fword("encode-int");
      push_str("width");
      fword("property");
  
      regs = (uint32_t *)map_reg(ECC_BASE, 0, ECC_SIZE, 1, ECC_BASE >> 32);
  
      version = regs[0];
      switch (version) {
      case 0x00000000:
          mc_type = "MCC";
          break;
      case 0x10000000:
          mc_type = "EMC";
          break;
      default:
      case 0x20000000:
          mc_type = "SMC";
          break;
      }
      push_str(mc_type);
      fword("encode-string");
      push_str("mc-type");
      fword("property");
  
      fword("finish-device");
  }
  
  static unsigned char *nvram;
  
  #define NVRAM_OB_START   (0)
  #define NVRAM_OB_SIZE    ((NVRAM_IDPROM - NVRAM_OB_START) & ~15)
  
  void
  arch_nvram_get(char *data)
  {
      memcpy(data, &nvram[NVRAM_OB_START], NVRAM_OB_SIZE);
  }
  
  void
  arch_nvram_put(char *data)
  {
      memcpy(&nvram[NVRAM_OB_START], data, NVRAM_OB_SIZE);
  }
  
  int
  arch_nvram_size(void)
  {
      return NVRAM_OB_SIZE;
  }
  
  void
  ss5_init(uint64_t base)
  {
      ob_new_obio_device("slavioconfig", NULL);
  
      ob_reg(base, SLAVIO_SCONFIG, SCONFIG_REGS, 0);
  
      fword("finish-device");
  }
  
  static void
  ob_nvram_init(uint64_t base, uint64_t offset)
  {
      ob_new_obio_device("eeprom", NULL);
  
      nvram = (unsigned char *)ob_reg(base, offset, NVRAM_SIZE, 1);
  
      PUSH((unsigned long)nvram);
      fword("encode-int");
      push_str("address");
      fword("property");
  
      push_str("mk48t08");
      fword("model");
  
      fword("finish-device");
  
      // Add /idprom
      push_str("/");
      fword("find-device");
  
      PUSH((long)&nvram[NVRAM_IDPROM]);
      PUSH(32);
      fword("encode-bytes");
      push_str("idprom");
      fword("property");
  }
  
  static void
  ob_fd_init(uint64_t base, uint64_t offset, int intr)
  {
      unsigned long addr;
  
      ob_new_obio_device("SUNW,fdtwo", "block");
  
      addr = ob_reg(base, offset, FD_REGS, 1);
  
      ob_intr(intr);
  
      fword("is-deblocker");
  
      ob_floppy_init("/obio", "SUNW,fdtwo", 0, addr);
  
      fword("finish-device");
  }
  
  static void
  ob_auxio_init(uint64_t base, uint64_t offset)
  {
      ob_new_obio_device("auxio", NULL);
  
      ob_reg(base, offset, AUXIO_REGS, 1);
  
      fword("finish-device");
  }
  
  volatile unsigned char *power_reg;
  volatile unsigned int *reset_reg;
  
  static void
  sparc32_reset_all(void)
  {
      *reset_reg = 1;
  }
  
  // AUX 2 (Software Powerdown Control) and reset
  static void
  ob_aux2_reset_init(uint64_t base, uint64_t offset, int intr)
  {
      ob_new_obio_device("power", NULL);
  
      power_reg = (void *)ob_reg(base, offset, AUXIO2_REGS, 1);
  
      // Not in device tree
      reset_reg = (unsigned int *)ofmem_map_io(base + (uint64_t)SLAVIO_RESET, RESET_REGS);
  
      bind_func("sparc32-reset-all", sparc32_reset_all);
      push_str("' sparc32-reset-all to reset-all");
      fword("eval");
  
      ob_intr(intr);
  
      fword("finish-device");
  }
  
  volatile struct sun4m_timer_regs *counter_regs;
  
  static void
  ob_counter_init(uint64_t base, unsigned long offset, int ncpu)
  {
      int i;
  
      ob_new_obio_device("counter", NULL);
  
      for (i = 0; i < ncpu; i++) {
          PUSH(0);
          fword("encode-int");
          if (i != 0) fword("encode+");
          PUSH(offset + (i * PAGE_SIZE));
          fword("encode-int");
          fword("encode+");
          PUSH(COUNTER_REGS);
          fword("encode-int");
          fword("encode+");
      }
  
      PUSH(0);
      fword("encode-int");
      fword("encode+");
      PUSH(offset + 0x10000);
      fword("encode-int");
      fword("encode+");
      PUSH(COUNTER_REGS);
      fword("encode-int");
      fword("encode+");
  
      push_str("reg");
      fword("property");
  
  
      counter_regs = (struct sun4m_timer_regs *)ofmem_map_io(base + (uint64_t)offset, sizeof(*counter_regs));
      counter_regs->cfg = 0xfffffffe;
      counter_regs->l10_timer_limit = 0;
      counter_regs->cpu_timers[0].l14_timer_limit = 0x9c4000;    /* see comment in obio.h */
      counter_regs->cpu_timers[0].cntrl = 1;
  
      for (i = 0; i < ncpu; i++) {
          PUSH((unsigned long)&counter_regs->cpu_timers[i]);
          fword("encode-int");
          if (i != 0)
              fword("encode+");
      }
      PUSH((unsigned long)&counter_regs->l10_timer_limit);
      fword("encode-int");
      fword("encode+");
      push_str("address");
      fword("property");
  
      fword("finish-device");
  }
  
  static volatile struct sun4m_intregs *intregs;
  
  static void
  ob_interrupt_init(uint64_t base, unsigned long offset, int ncpu)
  {
      int i;
  
      ob_new_obio_device("interrupt", NULL);
  
      for (i = 0; i < ncpu; i++) {
          PUSH(0);
          fword("encode-int");
          if (i != 0) fword("encode+");
          PUSH(offset + (i * PAGE_SIZE));
          fword("encode-int");
          fword("encode+");
          PUSH(INTERRUPT_REGS);
          fword("encode-int");
          fword("encode+");
      }
  
      PUSH(0);
      fword("encode-int");
      fword("encode+");
      PUSH(offset + 0x10000);
      fword("encode-int");
      fword("encode+");
      PUSH(INTERRUPT_REGS);
      fword("encode-int");
      fword("encode+");
  
      push_str("reg");
      fword("property");
  
      intregs = (struct sun4m_intregs *)ofmem_map_io(base | (uint64_t)offset, sizeof(*intregs));
      intregs->clear = ~SUN4M_INT_MASKALL;
      intregs->cpu_intregs[0].clear = ~0x17fff;
  
      for (i = 0; i < ncpu; i++) {
          PUSH((unsigned long)&intregs->cpu_intregs[i]);
          fword("encode-int");
          if (i != 0)
              fword("encode+");
      }
      PUSH((unsigned long)&intregs->tbt);
      fword("encode-int");
      fword("encode+");
      push_str("address");
      fword("property");
  
      fword("finish-device");
  }
  
  /* SMP CPU boot structure */
  struct smp_cfg {
      uint32_t smp_ctx;
      uint32_t smp_ctxtbl;
      uint32_t smp_entry;
      uint32_t valid;
  };
  
  static struct smp_cfg *smp_header;
  
  int
  start_cpu(unsigned int pc, unsigned int context_ptr, unsigned int context, int cpu)
  {
      if (!cpu)
          return -1;
  
      cpu &= 7;
  
      smp_header->smp_entry = pc;
      smp_header->smp_ctxtbl = context_ptr;
      smp_header->smp_ctx = context;
      smp_header->valid = cpu;
  
      intregs->cpu_intregs[cpu].set = SUN4M_SOFT_INT(14);
  
      return 0;
  }
  
  static void
  ob_smp_init(unsigned long mem_size)
  {
      // See arch/sparc32/entry.S for memory layout
      smp_header = (struct smp_cfg *)ofmem_map_io((uint64_t)(mem_size - 0x100),
                                            sizeof(struct smp_cfg));
  }
  
  static void
  ob_obio_open(__attribute__((unused))int *idx)
  {
  	int ret=1;
  	RET ( -ret );
  }
  
  static void
  ob_obio_close(__attribute__((unused))int *idx)
  {
  	selfword("close-deblocker");
  }
  
  static void
  ob_obio_initialize(__attribute__((unused))int *idx)
  {
      push_str("/");
      fword("find-device");
      fword("new-device");
  
      push_str("obio");
      fword("device-name");
  
      push_str("hierarchical");
      fword("device-type");
  
      PUSH(2);
      fword("encode-int");
      push_str("#address-cells");
      fword("property");
  
      PUSH(1);
      fword("encode-int");
      push_str("#size-cells");
      fword("property");
  
      fword("finish-device");
  }
  
  static void
  ob_set_obio_ranges(uint64_t base)
  {
      push_str("/obio");
      fword("find-device");
      PUSH(0);
      fword("encode-int");
      PUSH(0);
      fword("encode-int");
      fword("encode+");
      PUSH(base >> 32);
      fword("encode-int");
      fword("encode+");
      PUSH(base & 0xffffffff);
      fword("encode-int");
      fword("encode+");
      PUSH(SLAVIO_SIZE);
      fword("encode-int");
      fword("encode+");
      push_str("ranges");
      fword("property");
  }
  
  static void
  ob_obio_decodeunit(__attribute__((unused)) int *idx)
  {
      fword("decode-unit-sbus");
  }
  
  
  static void
  ob_obio_encodeunit(__attribute__((unused)) int *idx)
  {
      fword("encode-unit-sbus");
  }
  
  NODE_METHODS(ob_obio) = {
  	{ NULL,			ob_obio_initialize	},
  	{ "open",		ob_obio_open		},
  	{ "close",		ob_obio_close		},
  	{ "encode-unit",	ob_obio_encodeunit	},
  	{ "decode-unit",	ob_obio_decodeunit	},
  };
  
  
  int
  ob_obio_init(uint64_t slavio_base, unsigned long fd_offset,
               unsigned long counter_offset, unsigned long intr_offset,
               int intr_ncpu, unsigned long aux1_offset, unsigned long aux2_offset,
               unsigned long mem_size)
  {
  
      // All devices were integrated to NCR89C105, see
      // http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C105.txt
  
      //printk("Initializing OBIO devices...\n");
  #if 0 // XXX
      REGISTER_NAMED_NODE(ob_obio, "/obio");
      device_end();
  #endif
      ob_set_obio_ranges(slavio_base);
  
      // Zilog Z8530 serial ports, see http://www.zilog.com
      // Must be before zs@0,0 or Linux won't boot
      ob_zs_init(slavio_base, SLAVIO_ZS1, ZS_INTR, 0, 0);
  
      ob_zs_init(slavio_base, SLAVIO_ZS, ZS_INTR, 1, 1);
  
      // M48T08 NVRAM, see http://www.st.com
      ob_nvram_init(slavio_base, SLAVIO_NVRAM);
  
      // 82078 FDC
      if (fd_offset != (unsigned long) -1)
          ob_fd_init(slavio_base, fd_offset, FD_INTR);
  
      ob_auxio_init(slavio_base, aux1_offset);
  
      if (aux2_offset != (unsigned long) -1)
          ob_aux2_reset_init(slavio_base, aux2_offset, AUXIO2_INTR);
  
      ob_counter_init(slavio_base, counter_offset, intr_ncpu);
  
      ob_interrupt_init(slavio_base, intr_offset, intr_ncpu);
  
      ob_smp_init(mem_size);
  
      return 0;
  }
  