#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: psc.c,v 1.1 2026/06/27 13:28:35 rkujawa Exp $");
#include <sys/param.h>
#include <sys/device.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/conf.h>
#include <sys/tty.h>
#include <sys/file.h>
#include <sys/kauth.h>
#include <sys/intr.h>
#include <sys/kmem.h>
#include <sys/bus.h>
#include <dev/cons.h>
#include <dev/ofw/openfirm.h>
#include <machine/autoconf.h>
#include <powerpc/mpc5200/obiovar.h>
#include <powerpc/mpc5200/mpc5200reg.h>
#include <powerpc/mpc5200/pscreg.h>
#include <powerpc/mpc5200/pscvar.h>
#include <powerpc/mpc5200/cdmvar.h>
#ifndef PSC_CONSOLE_SPEED
#define PSC_CONSOLE_SPEED 115200
#endif
#define PSCUNIT(x) minor(x)
#define PSC_READ_1(sc, r) bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (r))
#define PSC_READ_2(sc, r) bus_space_read_2((sc)->sc_iot, (sc)->sc_ioh, (r))
#define PSC_WRITE_1(sc, r, v) bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (r), (v))
#define PSC_WRITE_2(sc, r, v) bus_space_write_2((sc)->sc_iot, (sc)->sc_ioh, (r), (v))
static int psc_match(device_t, cfdata_t, void *);
static void psc_attach(device_t, device_t, void *);
static u_int psc_baud_divisor(uint32_t, int);
static void psc_set_baud(bus_space_tag_t, bus_space_handle_t, int);
static int pscintr(void *);
static void pscsoft(void *);
static void psc_rxsoft(struct psc_softc *, struct tty *);
CFATTACH_DECL_NEW(psc, sizeof(struct psc_softc),
psc_match, psc_attach, NULL, NULL);
extern struct cfdriver psc_cd;
dev_type_open(pscopen);
dev_type_close(pscclose);
dev_type_read(pscread);
dev_type_write(pscwrite);
dev_type_ioctl(pscioctl);
dev_type_stop(pscstop);
dev_type_tty(psctty);
dev_type_poll(pscpoll);
const struct cdevsw psc_cdevsw = {
.d_open = pscopen,
.d_close = pscclose,
.d_read = pscread,
.d_write = pscwrite,
.d_ioctl = pscioctl,
.d_stop = pscstop,
.d_tty = psctty,
.d_poll = pscpoll,
.d_mmap = nommap,
.d_kqfilter = ttykqfilter,
.d_discard = nodiscard,
.d_flag = D_TTY,
};
static int pscparam(struct tty *, struct termios *);
static void pscstart(struct tty *);
static struct {
bool cs_mapped;
volatile uint8_t *cs_base;
} psc_console;
#define PSC_CN_SR(cs) (*(volatile uint16_t *)((cs)->cs_base + PSC_SR))
#define PSC_CN_TB(cs) (*((cs)->cs_base + PSC_TB))
#define PSC_CN_RB(cs) (*((cs)->cs_base + PSC_RB))
static void psccnprobe(struct consdev *);
static void psccninit(struct consdev *);
static int psccngetc(dev_t);
static void psccnputc(dev_t, int);
static void psccnpollc(dev_t, int);
struct consdev psccons = {
.cn_probe = psccnprobe,
.cn_init = psccninit,
.cn_getc = psccngetc,
.cn_putc = psccnputc,
.cn_pollc = psccnpollc,
.cn_dev = NODEV,
.cn_pri = CN_REMOTE,
};
static bool
psc_is_uart(int node)
{
char compat[64];
int len;
len = OF_getprop(node, "compatible", compat, sizeof(compat));
if (len > 0 &&
(strcmp(compat, "mpc5200-psc-uart") == 0 ||
strcmp(compat, "mpc5200b-psc-uart") == 0))
return true;
return false;
}
static int
psc_match(device_t parent, cfdata_t cf, void *aux)
{
struct obio_attach_args *oba = aux;
if (strcmp(oba->obio_name, "serial") == 0)
return 1;
if (psc_is_uart(oba->obio_node))
return 1;
return 0;
}
static void
psc_attach(device_t parent, device_t self, void *aux)
{
struct psc_softc *sc = device_private(self);
struct obio_attach_args *oba = aux;
struct tty *tp;
bus_size_t size;
int ist;
sc->sc_dev = self;
sc->sc_iot = oba->obio_bst;
sc->sc_node = oba->obio_node;
size = oba->obio_size != 0 ? oba->obio_size : MPC5200_PSC_SIZE;
if (bus_space_map(sc->sc_iot, oba->obio_addr, size, 0,
&sc->sc_ioh) != 0) {
aprint_error(": can't map registers\n");
return;
}
sc->sc_console = (sc->sc_node == console_node);
if (sc->sc_console) {
uint32_t ipb = mpc5200_cdm_get_ipb_freq();
u_int div = psc_baud_divisor(ipb, PSC_CONSOLE_SPEED);
aprint_normal(": console, IPB %u.%u MHz, %d baud (divisor %u)",
ipb / 1000000, (ipb / 100000) % 10, PSC_CONSOLE_SPEED, div);
psc_set_baud(sc->sc_iot, sc->sc_ioh, PSC_CONSOLE_SPEED);
}
aprint_normal("\n");
mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_HIGH);
sc->sc_imr = 0;
PSC_WRITE_2(sc, PSC_IMR, sc->sc_imr);
PSC_WRITE_1(sc, PSC_CR, CMD_TX_DISABLE | CMD_RX_DISABLE);
{
uint8_t mr1, mr2;
PSC_WRITE_1(sc, PSC_CR, CMD_RESET_MR);
mr1 = PSC_READ_1(sc, PSC_MR1);
mr2 = PSC_READ_1(sc, PSC_MR2);
mr1 &= ~MR1_FFULL;
PSC_WRITE_1(sc, PSC_CR, CMD_RESET_MR);
PSC_WRITE_1(sc, PSC_MR1, mr1);
PSC_WRITE_1(sc, PSC_MR2, mr2);
}
PSC_WRITE_1(sc, PSC_CR, CMD_RESET_ERR);
PSC_WRITE_1(sc, PSC_CR, CMD_TX_ENABLE | CMD_RX_ENABLE);
sc->sc_si = softint_establish(SOFTINT_SERIAL, pscsoft, sc);
if (sc->sc_si == NULL) {
aprint_error_dev(self, "can't establish soft interrupt\n");
return;
}
if (obio_decode_interrupt(oba->obio_node, 0, &sc->sc_irq, &ist)) {
sc->sc_ih = intr_establish(sc->sc_irq, ist, IPL_HIGH,
pscintr, sc);
if (sc->sc_ih == NULL)
aprint_error_dev(self, "can't establish interrupt\n");
}
tp = tty_alloc();
tp->t_oproc = pscstart;
tp->t_param = pscparam;
sc->sc_tty = tp;
sc->sc_rbput = sc->sc_rbget = 0;
tty_attach(tp);
if (sc->sc_console && cn_tab == &psccons) {
int maj = cdevsw_lookup_major(&psc_cdevsw);
tp->t_dev = cn_tab->cn_dev =
makedev(maj, device_unit(self));
}
}
static u_int
psc_baud_divisor(uint32_t ipb, int baud)
{
u_int div;
if (baud <= 0)
return 1;
div = (ipb + (PSC_BAUD_PRESCALE / 2) * baud) /
(PSC_BAUD_PRESCALE * baud);
if (div < 1)
div = 1;
if (div > 0xffff)
div = 0xffff;
return div;
}
static void
psc_set_baud(bus_space_tag_t iot, bus_space_handle_t ioh, int baud)
{
u_int div = psc_baud_divisor(mpc5200_cdm_get_ipb_freq(), baud);
int timo;
for (timo = 100000; timo > 0; timo--) {
if (bus_space_read_2(iot, ioh, PSC_SR) & SR_TXEMP)
break;
}
bus_space_write_1(iot, ioh, PSC_CR, CMD_TX_DISABLE | CMD_RX_DISABLE);
bus_space_write_2(iot, ioh, PSC_CSR, CSR_UART_CT);
bus_space_write_1(iot, ioh, PSC_CTUR, (div >> 8) & 0xff);
bus_space_write_1(iot, ioh, PSC_CTLR, div & 0xff);
bus_space_write_1(iot, ioh, PSC_CR, CMD_TX_ENABLE | CMD_RX_ENABLE);
}
static int
pscintr(void *arg)
{
struct psc_softc *sc = arg;
uint16_t isr, sr;
int handled = 0;
mutex_spin_enter(&sc->sc_lock);
isr = PSC_READ_2(sc, PSC_ISR);
if (isr & (INT_RXRDY | INT_ERROR | INT_ORERR)) {
sr = PSC_READ_2(sc, PSC_SR);
while (sr & SR_RXRDY) {
uint8_t c = PSC_READ_1(sc, PSC_RB);
u_int next = (sc->sc_rbput + 1) & PSC_RING_MASK;
if (next != sc->sc_rbget) {
sc->sc_rbuf[sc->sc_rbput] = c;
sc->sc_rbput = next;
}
sr = PSC_READ_2(sc, PSC_SR);
}
if (sr & SR_ORERR)
PSC_WRITE_1(sc, PSC_CR, CMD_RESET_ERR);
sc->sc_rx_ready = true;
handled = 1;
}
sr = PSC_READ_2(sc, PSC_SR);
if (sr & SR_TXRDY) {
while (sc->sc_tbc > 0 && (PSC_READ_2(sc, PSC_SR) & SR_TXRDY)) {
PSC_WRITE_1(sc, PSC_TB, *sc->sc_tba);
sc->sc_tba++;
sc->sc_tbc--;
}
if (sc->sc_tbc == 0) {
if (sc->sc_imr & INT_TXRDY) {
sc->sc_imr &= ~INT_TXRDY;
PSC_WRITE_2(sc, PSC_IMR, sc->sc_imr);
}
if (sc->sc_tx_busy) {
sc->sc_tx_busy = false;
sc->sc_tx_done = true;
handled = 1;
}
}
}
mutex_spin_exit(&sc->sc_lock);
if (sc->sc_rx_ready || sc->sc_tx_done)
softint_schedule(sc->sc_si);
return handled;
}
static void
psc_rxsoft(struct psc_softc *sc, struct tty *tp)
{
int (*rint)(int, struct tty *) = tp->t_linesw->l_rint;
while (sc->sc_rbget != sc->sc_rbput) {
uint8_t c = sc->sc_rbuf[sc->sc_rbget];
if ((*rint)(c, tp) == -1)
break;
sc->sc_rbget = (sc->sc_rbget + 1) & PSC_RING_MASK;
}
}
static void
pscsoft(void *arg)
{
struct psc_softc *sc = arg;
struct tty *tp = sc->sc_tty;
if (tp == NULL)
return;
if (sc->sc_rx_ready) {
sc->sc_rx_ready = false;
psc_rxsoft(sc, tp);
}
if (sc->sc_tx_done) {
sc->sc_tx_done = false;
CLR(tp->t_state, TS_BUSY);
if (ISSET(tp->t_state, TS_FLUSH))
CLR(tp->t_state, TS_FLUSH);
else
ndflush(&tp->t_outq,
(int)(sc->sc_tba - tp->t_outq.c_cf));
(*tp->t_linesw->l_start)(tp);
}
}
static void
pscstart(struct tty *tp)
{
struct psc_softc *sc = device_lookup_private(&psc_cd, PSCUNIT(tp->t_dev));
int s;
if (sc == NULL)
return;
s = spltty();
if (ISSET(tp->t_state, TS_BUSY | TS_TIMEOUT | TS_TTSTOP))
goto out;
if (!ttypull(tp))
goto out;
SET(tp->t_state, TS_BUSY);
mutex_spin_enter(&sc->sc_lock);
sc->sc_tba = tp->t_outq.c_cf;
sc->sc_tbc = ndqb(&tp->t_outq, 0);
sc->sc_tx_busy = true;
if (!(sc->sc_imr & INT_TXRDY)) {
sc->sc_imr |= INT_TXRDY;
PSC_WRITE_2(sc, PSC_IMR, sc->sc_imr);
}
mutex_spin_exit(&sc->sc_lock);
out:
splx(s);
}
static int
pscparam(struct tty *tp, struct termios *t)
{
struct psc_softc *sc = device_lookup_private(&psc_cd, PSCUNIT(tp->t_dev));
if (sc == NULL)
return ENXIO;
tp->t_ispeed = t->c_ispeed;
tp->t_ospeed = t->c_ospeed;
tp->t_cflag = t->c_cflag;
return 0;
}
int
pscopen(dev_t dev, int flag, int mode, struct lwp *l)
{
struct psc_softc *sc = device_lookup_private(&psc_cd, PSCUNIT(dev));
struct tty *tp;
int error, s;
if (sc == NULL)
return ENXIO;
tp = sc->sc_tty;
if (kauth_authorize_device_tty(l->l_cred, KAUTH_DEVICE_TTY_OPEN, tp))
return EBUSY;
s = spltty();
if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
struct termios t;
tp->t_dev = dev;
mutex_spin_enter(&sc->sc_lock);
sc->sc_imr |= INT_RXRDY;
PSC_WRITE_2(sc, PSC_IMR, sc->sc_imr);
mutex_spin_exit(&sc->sc_lock);
t.c_ospeed = t.c_ispeed = PSC_CONSOLE_SPEED;
t.c_cflag = (CREAD | CS8 | HUPCL | CLOCAL);
tp->t_ospeed = 0;
(void)pscparam(tp, &t);
tp->t_iflag = TTYDEF_IFLAG;
tp->t_oflag = TTYDEF_OFLAG;
tp->t_lflag = TTYDEF_LFLAG;
ttychars(tp);
ttsetwater(tp);
}
splx(s);
error = ttyopen(tp, 0, ISSET(flag, O_NONBLOCK));
if (error)
return error;
return (*tp->t_linesw->l_open)(dev, tp);
}
int
pscclose(dev_t dev, int flag, int mode, struct lwp *l)
{
struct psc_softc *sc = device_lookup_private(&psc_cd, PSCUNIT(dev));
struct tty *tp = sc->sc_tty;
if (!ISSET(tp->t_state, TS_ISOPEN))
return 0;
(*tp->t_linesw->l_close)(tp, flag);
ttyclose(tp);
if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
if (!sc->sc_console) {
mutex_spin_enter(&sc->sc_lock);
sc->sc_imr &= ~INT_RXRDY;
PSC_WRITE_2(sc, PSC_IMR, sc->sc_imr);
mutex_spin_exit(&sc->sc_lock);
}
}
return 0;
}
int
pscread(dev_t dev, struct uio *uio, int flag)
{
struct psc_softc *sc = device_lookup_private(&psc_cd, PSCUNIT(dev));
struct tty *tp = sc->sc_tty;
return (*tp->t_linesw->l_read)(tp, uio, flag);
}
int
pscwrite(dev_t dev, struct uio *uio, int flag)
{
struct psc_softc *sc = device_lookup_private(&psc_cd, PSCUNIT(dev));
struct tty *tp = sc->sc_tty;
return (*tp->t_linesw->l_write)(tp, uio, flag);
}
int
pscpoll(dev_t dev, int events, struct lwp *l)
{
struct psc_softc *sc = device_lookup_private(&psc_cd, PSCUNIT(dev));
struct tty *tp = sc->sc_tty;
return (*tp->t_linesw->l_poll)(tp, events, l);
}
struct tty *
psctty(dev_t dev)
{
struct psc_softc *sc = device_lookup_private(&psc_cd, PSCUNIT(dev));
return sc->sc_tty;
}
int
pscioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
{
struct psc_softc *sc = device_lookup_private(&psc_cd, PSCUNIT(dev));
struct tty *tp = sc->sc_tty;
int error;
error = (*tp->t_linesw->l_ioctl)(tp, cmd, data, flag, l);
if (error != EPASSTHROUGH)
return error;
return ttioctl(tp, cmd, data, flag, l);
}
void
pscstop(struct tty *tp, int flag)
{
struct psc_softc *sc = device_lookup_private(&psc_cd, PSCUNIT(tp->t_dev));
if (sc == NULL)
return;
mutex_spin_enter(&sc->sc_lock);
if (ISSET(tp->t_state, TS_BUSY)) {
sc->sc_tbc = 0;
if (!ISSET(tp->t_state, TS_TTSTOP))
SET(tp->t_state, TS_FLUSH);
}
mutex_spin_exit(&sc->sc_lock);
}
static void
psccnprobe(struct consdev *cp)
{
}
static void
psccninit(struct consdev *cp)
{
psc_console.cs_base =
(volatile uint8_t *)(MPC5200_MBAR_DEFAULT + MPC5200_REG_PSC1);
psc_console.cs_mapped = true;
}
static int
psccngetc(dev_t dev)
{
if (!psc_console.cs_mapped)
return -1;
if ((PSC_CN_SR(&psc_console) & SR_RXRDY) == 0)
return -1;
return PSC_CN_RB(&psc_console);
}
static void
psccnputc(dev_t dev, int c)
{
if (!psc_console.cs_mapped)
return;
if (c == '\n')
psccnputc(dev, '\r');
while ((PSC_CN_SR(&psc_console) & SR_TXRDY) == 0)
continue;
PSC_CN_TB(&psc_console) = c;
}
static void
psccnpollc(dev_t dev, int on)
{
}