/* $NetBSD: xpbus.c,v 1.1 2022/06/10 21:42:23 tsutsui Exp $ */
/*-
* Copyright (c) 2016 Izumi Tsutsui. All rights reserved.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
*/
/*
* LUNA's Hitachi HD647180 "XP" I/O processor
*/
/*
* Specification of interrupts from XP to the host is confirmed
* by Kenji Aoyama, in xptty(4) driver for OpenBSD/luna88k:
* https://gist.github.com/ao-kenji/790b0822e46a50ea63131cfa8d9110e7
* and CP/M BIOS for HD647180 on LUNA:
* https://gist.github.com/ao-kenji/4f1e2b010f3b2b41ab07f3a8a3cc7484
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: xpbus.c,v 1.1 2022/06/10 21:42:23 tsutsui Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/atomic.h>
#include <sys/device.h>
#include <machine/autoconf.h>
#include <machine/board.h>
#include <luna68k/dev/xpbusvar.h>
#include <luna68k/dev/xplxfirm.h>
/*
* PIO 0 port C is connected to XP's reset line
*
* XXX: PIO port functions should be shared with machdep.c for DIP SWs
*/
#define PIO_ADDR OBIO_PIO0_BASE
#define PORT_A 0
#define PORT_B 1
#define PORT_C 2
#define CTRL 3
/* PIO0 Port C bit definition */
#define XP_INT1_REQ 0 /* INTR B */
/* unused */ /* IBF B */
#define XP_INT1_ENA 2 /* INTE B */
#define XP_INT5_REQ 3 /* INTR A */
#define XP_INT5_ENA 4 /* INTE A */
/* unused */ /* IBF A */
#define PARITY 6 /* PC6 output to enable parity error */
#define XP_RESET 7 /* PC7 output to reset HD647180 XP */
/* Port control for PC6 and PC7 */
#define ON 1
#define OFF 0
struct xpbus_softc {
device_t sc_dev;
};
static const struct xpbus_attach_args xpdevs[] = {
{ "xp" },
{ "psgpam" },
};
static int xpbus_match(device_t, cfdata_t, void *);
static void xpbus_attach(device_t, device_t, void *);
CFATTACH_DECL_NEW(xpbus, sizeof(struct xpbus_softc),
xpbus_match, xpbus_attach, NULL, NULL);
static bool xpbus_matched;
/*
* xpbus acquired device sharing bitmap
*/
static volatile unsigned int xp_acquired;
/* XP SHM dirty flag */
static bool xp_shm_dirty = true;
static int
xpbus_match(device_t parent, cfdata_t cf, void *aux)
{
struct mainbus_attach_args *ma = aux;
/* only one XP processor */
if (xpbus_matched)
return 0;
if (ma->ma_addr != XP_SHM_BASE)
return 0;
xpbus_matched = true;
return 1;
}
static void
xpbus_attach(device_t parent, device_t self, void *aux)
{
struct xpbus_softc *sc = device_private(self);
struct xpbus_attach_args xa;
int i;
sc->sc_dev = self;
aprint_normal("\n");
for (i = 0; i < __arraycount(xpdevs); i++) {
xa = xpdevs[i];
config_found(self, &xa, NULL, CFARGS_NONE);
}
}
/*
* acquire xpbus from child devices
* if success, return non-zero acquired map
* if fail, return 0
*/
u_int
xp_acquire(int xplx_devid, u_int excl)
{
for (;;) {
unsigned int before, after;
before = xp_acquired;
if (before & XP_ACQ_EXCL)
return 0;
if (before & (1 << xplx_devid))
return 0;
after = before | (1 << xplx_devid) | excl;
if (atomic_cas_uint(&xp_acquired, before, after) == before) {
return after & ~(excl);
}
}
}
/* release xpbus by child devices */
void
xp_release(int xplx_devid)
{
for (;;) {
unsigned int before, after;
before = xp_acquired;
after = before & ~(1 << xplx_devid) & ~XP_ACQ_EXCL;
if (atomic_cas_uint(&xp_acquired, before, after) == before) {
return;
}
}
}
/* set the xp_shm_dirty flag */
void
xp_set_shm_dirty(void)
{
xp_shm_dirty = true;
}
/* reload firmware if xp_shm_dirty */
void
xp_ensure_firmware(void)
{
if (xp_shm_dirty) {
/* firmware transfer */
xp_cpu_reset_hold();
delay(100);
memcpy((void *)XP_SHM_BASE, xplx, xplx_size);
/* XXX maybe not necessary */
delay(100);
xp_cpu_reset_release();
xp_shm_dirty = false;
}
}
/* PIO PORTC write */
uint8_t
put_pio0c(uint8_t bit, uint8_t set)
{
volatile uint8_t * const pio0 = (uint8_t *)PIO_ADDR;
pio0[CTRL] = (bit << 1) | (set & 0x01);
return pio0[PORT_C];
}
/* hold XP RESET signal */
void
xp_cpu_reset_hold(void)
{
put_pio0c(XP_RESET, ON);
}
/* release XP RESET signal */
void
xp_cpu_reset_release(void)
{
put_pio0c(XP_RESET, OFF);
}
/* one-shot XP RESET signal */
void
xp_cpu_reset(void)
{
xp_cpu_reset_hold();
delay(100);
xp_cpu_reset_release();
}
/* enable XP to Host interrupt 1 */
void
xp_intr1_enable(void)
{
put_pio0c(XP_INT1_ENA, ON);
}
/* disable XP to Host interrupt 1 */
void
xp_intr1_disable(void)
{
put_pio0c(XP_INT1_ENA, OFF);
}
/* interrupt 1 ack */
void
xp_intr1_acknowledge(void)
{
/* reset the interrupt request: read PIO0 port A */
/* XXX: probably */
*(volatile uint8_t *)OBIO_PIO0A;
/* XXX: just a guess */
*(volatile uint8_t *)OBIO_PIO0B;
}
/* enable XP to Host interrupt 5 */
void
xp_intr5_enable(void)
{
put_pio0c(XP_INT5_ENA, ON);
}
/* disable XP to Host interrupt 5 */
void
xp_intr5_disable(void)
{
put_pio0c(XP_INT5_ENA, OFF);
}
/* interrupt 5 ack */
void
xp_intr5_acknowledge(void)
{
/* reset the interrupt request: read PIO0 port A */
(void)*(volatile uint8_t *)OBIO_PIO0A;
}
/* get XP shared memory pointer */
void *
xp_shmptr(int offset)
{
return (uint8_t *)XP_SHM_BASE + offset;
}
/* read 1 byte */
int
xp_readmem8(int offset)
{
return *((volatile uint8_t *)xp_shmptr(offset));
}
/* read 1 16bitLE */
int
xp_readmem16le(int offset)
{
return le16toh(*(volatile uint16_t *)xp_shmptr(offset));
}
/* write 1 byte */
void
xp_writemem8(int offset, int v)
{
*(volatile uint8_t *)(xp_shmptr(offset)) = v;
}
/* write 1 16bitLE */
void
xp_writemem16le(int offset, int v)
{
*((volatile uint16_t *)xp_shmptr(offset)) = htole16((uint16_t)v);
}