freebsd-dev/sys/arm/versatile/pl050.c
Pedro F. Giffuni af3dc4a7ca sys/arm: further adoption of SPDX licensing ID tags.
Mainly focus on files that use BSD 2-Clause license, however the tool I
was using misidentified many licenses so this was mostly a manual - error
prone - task.

The Software Package Data Exchange (SPDX) group provides a specification
to make it easier for automated tools to detect and summarize well known
opensource licenses. We are gradually adopting the specification, noting
that the tags are considered only advisory and do not, in any way,
superceed or replace the license texts.
2017-11-27 15:04:10 +00:00

744 lines
17 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2012 Oleksandr Tymoshenko <gonzo@freebsd.org>
* All rights reserved.
*
* Based on dev/usb/input/ukbd.c
*
* 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 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 AUTHOR 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.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/rman.h>
#include <sys/proc.h>
#include <sys/sched.h>
#include <sys/kdb.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <machine/intr.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <sys/ioccom.h>
#include <sys/filio.h>
#include <sys/tty.h>
#include <sys/kbio.h>
#include <dev/kbd/kbdreg.h>
#include <machine/bus.h>
#include <dev/kbd/kbdtables.h>
#define KMI_LOCK() mtx_lock(&Giant)
#define KMI_UNLOCK() mtx_unlock(&Giant)
#ifdef INVARIANTS
/*
* Assert that the lock is held in all contexts
* where the code can be executed.
*/
#define KMI_LOCK_ASSERT() mtx_assert(&Giant, MA_OWNED)
/*
* Assert that the lock is held in the contexts
* where it really has to be so.
*/
#define KMI_CTX_LOCK_ASSERT() \
do { \
if (!kdb_active && panicstr == NULL) \
mtx_assert(&Giant, MA_OWNED); \
} while (0)
#else
#define KMI_LOCK_ASSERT() (void)0
#define KMI_CTX_LOCK_ASSERT() (void)0
#endif
#define KMICR 0x00
#define KMICR_TYPE_NONPS2 (1 << 5)
#define KMICR_RXINTREN (1 << 4)
#define KMICR_TXINTREN (1 << 3)
#define KMICR_EN (1 << 2)
#define KMICR_FKMID (1 << 1)
#define KMICR_FKMIC (1 << 0)
#define KMISTAT 0x04
#define KMISTAT_TXEMPTY (1 << 6)
#define KMISTAT_TXBUSY (1 << 5)
#define KMISTAT_RXFULL (1 << 4)
#define KMISTAT_RXBUSY (1 << 3)
#define KMISTAT_RXPARITY (1 << 2)
#define KMISTAT_KMIC (1 << 1)
#define KMISTAT_KMID (1 << 0)
#define KMIDATA 0x08
#define KMICLKDIV 0x0C
#define KMIIR 0x10
#define KMIIR_TXINTR (1 << 1)
#define KMIIR_RXINTR (1 << 0)
#define KMI_DRIVER_NAME "kmi"
#define KMI_NFKEY (sizeof(fkey_tab)/sizeof(fkey_tab[0])) /* units */
#define SET_SCANCODE_SET 0xf0
struct kmi_softc {
device_t sc_dev;
keyboard_t sc_kbd;
keymap_t sc_keymap;
accentmap_t sc_accmap;
fkeytab_t sc_fkeymap[KMI_NFKEY];
struct resource* sc_mem_res;
struct resource* sc_irq_res;
void* sc_intr_hl;
int sc_mode; /* input mode (K_XLATE,K_RAW,K_CODE) */
int sc_state; /* shift/lock key state */
int sc_accents; /* accent key index (> 0) */
uint32_t sc_flags; /* flags */
#define KMI_FLAG_COMPOSE 0x00000001
#define KMI_FLAG_POLLING 0x00000002
struct thread *sc_poll_thread;
};
/* Read/Write macros for Timer used as timecounter */
#define pl050_kmi_read_4(sc, reg) \
bus_read_4((sc)->sc_mem_res, (reg))
#define pl050_kmi_write_4(sc, reg, val) \
bus_write_4((sc)->sc_mem_res, (reg), (val))
/* prototypes */
static void kmi_set_leds(struct kmi_softc *, uint8_t);
static int kmi_set_typematic(keyboard_t *, int);
static uint32_t kmi_read_char(keyboard_t *, int);
static void kmi_clear_state(keyboard_t *);
static int kmi_ioctl(keyboard_t *, u_long, caddr_t);
static int kmi_enable(keyboard_t *);
static int kmi_disable(keyboard_t *);
static int kmi_attached = 0;
/* early keyboard probe, not supported */
static int
kmi_configure(int flags)
{
return (0);
}
/* detect a keyboard, not used */
static int
kmi_probe(int unit, void *arg, int flags)
{
return (ENXIO);
}
/* reset and initialize the device, not used */
static int
kmi_init(int unit, keyboard_t **kbdp, void *arg, int flags)
{
return (ENXIO);
}
/* test the interface to the device, not used */
static int
kmi_test_if(keyboard_t *kbd)
{
return (0);
}
/* finish using this keyboard, not used */
static int
kmi_term(keyboard_t *kbd)
{
return (ENXIO);
}
/* keyboard interrupt routine, not used */
static int
kmi_intr(keyboard_t *kbd, void *arg)
{
return (0);
}
/* lock the access to the keyboard, not used */
static int
kmi_lock(keyboard_t *kbd, int lock)
{
return (1);
}
/*
* Enable the access to the device; until this function is called,
* the client cannot read from the keyboard.
*/
static int
kmi_enable(keyboard_t *kbd)
{
KMI_LOCK();
KBD_ACTIVATE(kbd);
KMI_UNLOCK();
return (0);
}
/* disallow the access to the device */
static int
kmi_disable(keyboard_t *kbd)
{
KMI_LOCK();
KBD_DEACTIVATE(kbd);
KMI_UNLOCK();
return (0);
}
/* check if data is waiting */
static int
kmi_check(keyboard_t *kbd)
{
struct kmi_softc *sc = kbd->kb_data;
uint32_t reg;
KMI_CTX_LOCK_ASSERT();
if (!KBD_IS_ACTIVE(kbd))
return (0);
reg = pl050_kmi_read_4(sc, KMIIR);
return (reg & KMIIR_RXINTR);
}
/* check if char is waiting */
static int
kmi_check_char_locked(keyboard_t *kbd)
{
KMI_CTX_LOCK_ASSERT();
if (!KBD_IS_ACTIVE(kbd))
return (0);
return (kmi_check(kbd));
}
static int
kmi_check_char(keyboard_t *kbd)
{
int result;
KMI_LOCK();
result = kmi_check_char_locked(kbd);
KMI_UNLOCK();
return (result);
}
/* read one byte from the keyboard if it's allowed */
/* Currently unused. */
static int
kmi_read(keyboard_t *kbd, int wait)
{
KMI_CTX_LOCK_ASSERT();
if (!KBD_IS_ACTIVE(kbd))
return (-1);
++(kbd->kb_count);
printf("Implement ME: %s\n", __func__);
return (0);
}
/* read char from the keyboard */
static uint32_t
kmi_read_char_locked(keyboard_t *kbd, int wait)
{
struct kmi_softc *sc = kbd->kb_data;
uint32_t reg, data;
KMI_CTX_LOCK_ASSERT();
if (!KBD_IS_ACTIVE(kbd))
return (NOKEY);
reg = pl050_kmi_read_4(sc, KMIIR);
if (reg & KMIIR_RXINTR) {
data = pl050_kmi_read_4(sc, KMIDATA);
return (data);
}
++kbd->kb_count;
return (NOKEY);
}
/* Currently wait is always false. */
static uint32_t
kmi_read_char(keyboard_t *kbd, int wait)
{
uint32_t keycode;
KMI_LOCK();
keycode = kmi_read_char_locked(kbd, wait);
KMI_UNLOCK();
return (keycode);
}
/* some useful control functions */
static int
kmi_ioctl_locked(keyboard_t *kbd, u_long cmd, caddr_t arg)
{
struct kmi_softc *sc = kbd->kb_data;
int i;
#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
int ival;
#endif
KMI_LOCK_ASSERT();
switch (cmd) {
case KDGKBMODE: /* get keyboard mode */
*(int *)arg = sc->sc_mode;
break;
#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
case _IO('K', 7):
ival = IOCPARM_IVAL(arg);
arg = (caddr_t)&ival;
/* FALLTHROUGH */
#endif
case KDSKBMODE: /* set keyboard mode */
switch (*(int *)arg) {
case K_XLATE:
if (sc->sc_mode != K_XLATE) {
/* make lock key state and LED state match */
sc->sc_state &= ~LOCK_MASK;
sc->sc_state |= KBD_LED_VAL(kbd);
}
/* FALLTHROUGH */
case K_RAW:
case K_CODE:
if (sc->sc_mode != *(int *)arg) {
if ((sc->sc_flags & KMI_FLAG_POLLING) == 0)
kmi_clear_state(kbd);
sc->sc_mode = *(int *)arg;
}
break;
default:
return (EINVAL);
}
break;
case KDGETLED: /* get keyboard LED */
*(int *)arg = KBD_LED_VAL(kbd);
break;
#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
case _IO('K', 66):
ival = IOCPARM_IVAL(arg);
arg = (caddr_t)&ival;
/* FALLTHROUGH */
#endif
case KDSETLED: /* set keyboard LED */
/* NOTE: lock key state in "sc_state" won't be changed */
if (*(int *)arg & ~LOCK_MASK)
return (EINVAL);
i = *(int *)arg;
/* replace CAPS LED with ALTGR LED for ALTGR keyboards */
if (sc->sc_mode == K_XLATE &&
kbd->kb_keymap->n_keys > ALTGR_OFFSET) {
if (i & ALKED)
i |= CLKED;
else
i &= ~CLKED;
}
if (KBD_HAS_DEVICE(kbd))
kmi_set_leds(sc, i);
KBD_LED_VAL(kbd) = *(int *)arg;
break;
case KDGKBSTATE: /* get lock key state */
*(int *)arg = sc->sc_state & LOCK_MASK;
break;
#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
case _IO('K', 20):
ival = IOCPARM_IVAL(arg);
arg = (caddr_t)&ival;
/* FALLTHROUGH */
#endif
case KDSKBSTATE: /* set lock key state */
if (*(int *)arg & ~LOCK_MASK) {
return (EINVAL);
}
sc->sc_state &= ~LOCK_MASK;
sc->sc_state |= *(int *)arg;
/* set LEDs and quit */
return (kmi_ioctl(kbd, KDSETLED, arg));
case KDSETREPEAT: /* set keyboard repeat rate (new
* interface) */
if (!KBD_HAS_DEVICE(kbd)) {
return (0);
}
if (((int *)arg)[1] < 0) {
return (EINVAL);
}
if (((int *)arg)[0] < 0) {
return (EINVAL);
}
if (((int *)arg)[0] < 200) /* fastest possible value */
kbd->kb_delay1 = 200;
else
kbd->kb_delay1 = ((int *)arg)[0];
kbd->kb_delay2 = ((int *)arg)[1];
return (0);
#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
case _IO('K', 67):
ival = IOCPARM_IVAL(arg);
arg = (caddr_t)&ival;
/* FALLTHROUGH */
#endif
case KDSETRAD: /* set keyboard repeat rate (old
* interface) */
return (kmi_set_typematic(kbd, *(int *)arg));
case PIO_KEYMAP: /* set keyboard translation table */
case OPIO_KEYMAP: /* set keyboard translation table
* (compat) */
case PIO_KEYMAPENT: /* set keyboard translation table
* entry */
case PIO_DEADKEYMAP: /* set accent key translation table */
sc->sc_accents = 0;
/* FALLTHROUGH */
default:
return (genkbd_commonioctl(kbd, cmd, arg));
}
return (0);
}
static int
kmi_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg)
{
int result;
/*
* XXX KDGKBSTATE, KDSKBSTATE and KDSETLED can be called from any
* context where printf(9) can be called, which among other things
* includes interrupt filters and threads with any kinds of locks
* already held. For this reason it would be dangerous to acquire
* the Giant here unconditionally. On the other hand we have to
* have it to handle the ioctl.
* So we make our best effort to auto-detect whether we can grab
* the Giant or not. Blame syscons(4) for this.
*/
switch (cmd) {
case KDGKBSTATE:
case KDSKBSTATE:
case KDSETLED:
if (!mtx_owned(&Giant) && !SCHEDULER_STOPPED())
return (EDEADLK); /* best I could come up with */
/* FALLTHROUGH */
default:
KMI_LOCK();
result = kmi_ioctl_locked(kbd, cmd, arg);
KMI_UNLOCK();
return (result);
}
}
/* clear the internal state of the keyboard */
static void
kmi_clear_state(keyboard_t *kbd)
{
struct kmi_softc *sc = kbd->kb_data;
KMI_CTX_LOCK_ASSERT();
sc->sc_flags &= ~(KMI_FLAG_COMPOSE | KMI_FLAG_POLLING);
sc->sc_state &= LOCK_MASK; /* preserve locking key state */
sc->sc_accents = 0;
}
/* save the internal state, not used */
static int
kmi_get_state(keyboard_t *kbd, void *buf, size_t len)
{
return (len == 0) ? 1 : -1;
}
/* set the internal state, not used */
static int
kmi_set_state(keyboard_t *kbd, void *buf, size_t len)
{
return (EINVAL);
}
static int
kmi_poll(keyboard_t *kbd, int on)
{
struct kmi_softc *sc = kbd->kb_data;
KMI_LOCK();
if (on) {
sc->sc_flags |= KMI_FLAG_POLLING;
sc->sc_poll_thread = curthread;
} else {
sc->sc_flags &= ~KMI_FLAG_POLLING;
}
KMI_UNLOCK();
return (0);
}
/* local functions */
static void
kmi_set_leds(struct kmi_softc *sc, uint8_t leds)
{
KMI_LOCK_ASSERT();
/* start transfer, if not already started */
printf("Implement me: %s\n", __func__);
}
static int
kmi_set_typematic(keyboard_t *kbd, int code)
{
static const int delays[] = {250, 500, 750, 1000};
static const int rates[] = {34, 38, 42, 46, 50, 55, 59, 63,
68, 76, 84, 92, 100, 110, 118, 126,
136, 152, 168, 184, 200, 220, 236, 252,
272, 304, 336, 368, 400, 440, 472, 504};
if (code & ~0x7f) {
return (EINVAL);
}
kbd->kb_delay1 = delays[(code >> 5) & 3];
kbd->kb_delay2 = rates[code & 0x1f];
return (0);
}
static keyboard_switch_t kmisw = {
.probe = &kmi_probe,
.init = &kmi_init,
.term = &kmi_term,
.intr = &kmi_intr,
.test_if = &kmi_test_if,
.enable = &kmi_enable,
.disable = &kmi_disable,
.read = &kmi_read,
.check = &kmi_check,
.read_char = &kmi_read_char,
.check_char = &kmi_check_char,
.ioctl = &kmi_ioctl,
.lock = &kmi_lock,
.clear_state = &kmi_clear_state,
.get_state = &kmi_get_state,
.set_state = &kmi_set_state,
.get_fkeystr = &genkbd_get_fkeystr,
.poll = &kmi_poll,
.diag = &genkbd_diag,
};
KEYBOARD_DRIVER(kmi, kmisw, kmi_configure);
static void
pl050_kmi_intr(void *arg)
{
struct kmi_softc *sc = arg;
uint32_t c;
KMI_CTX_LOCK_ASSERT();
if ((sc->sc_flags & KMI_FLAG_POLLING) != 0)
return;
if (KBD_IS_ACTIVE(&sc->sc_kbd) &&
KBD_IS_BUSY(&sc->sc_kbd)) {
/* let the callback function process the input */
(sc->sc_kbd.kb_callback.kc_func) (&sc->sc_kbd, KBDIO_KEYINPUT,
sc->sc_kbd.kb_callback.kc_arg);
} else {
/* read and discard the input, no one is waiting for it */
do {
c = kmi_read_char_locked(&sc->sc_kbd, 0);
} while (c != NOKEY);
}
}
static int
pl050_kmi_probe(device_t dev)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
/*
* PL050 is plain PS2 port that pushes bytes to/from computer
* VersatilePB has two such ports and QEMU simulates keyboard
* connected to port #0 and mouse connected to port #1. This
* information can't be obtained from device tree so we just
* hardcode this knowledge here. We attach keyboard driver to
* port #0 and ignore port #1
*/
if (kmi_attached)
return (ENXIO);
if (ofw_bus_is_compatible(dev, "arm,pl050")) {
device_set_desc(dev, "PL050 Keyboard/Mouse Interface");
return (BUS_PROBE_DEFAULT);
}
return (ENXIO);
}
static int
pl050_kmi_attach(device_t dev)
{
struct kmi_softc *sc = device_get_softc(dev);
keyboard_t *kbd;
int rid;
int i;
uint32_t ack;
sc->sc_dev = dev;
kbd = &sc->sc_kbd;
rid = 0;
sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
if (sc->sc_mem_res == NULL) {
device_printf(dev, "could not allocate memory resource\n");
return (ENXIO);
}
/* Request the IRQ resources */
sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
if (sc->sc_irq_res == NULL) {
device_printf(dev, "Error: could not allocate irq resources\n");
return (ENXIO);
}
/* Setup and enable the timer */
if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_CLK,
NULL, pl050_kmi_intr, sc,
&sc->sc_intr_hl) != 0) {
bus_release_resource(dev, SYS_RES_IRQ, rid,
sc->sc_irq_res);
device_printf(dev, "Unable to setup the clock irq handler.\n");
return (ENXIO);
}
/* TODO: clock & divisor */
pl050_kmi_write_4(sc, KMICR, KMICR_EN);
pl050_kmi_write_4(sc, KMIDATA, SET_SCANCODE_SET);
/* read out ACK */
ack = pl050_kmi_read_4(sc, KMIDATA);
/* Set Scan Code set 1 (XT) */
pl050_kmi_write_4(sc, KMIDATA, 1);
/* read out ACK */
ack = pl050_kmi_read_4(sc, KMIDATA);
pl050_kmi_write_4(sc, KMICR, KMICR_EN | KMICR_RXINTREN);
kbd_init_struct(kbd, KMI_DRIVER_NAME, KB_OTHER,
device_get_unit(dev), 0, 0, 0);
kbd->kb_data = (void *)sc;
sc->sc_keymap = key_map;
sc->sc_accmap = accent_map;
for (i = 0; i < KMI_NFKEY; i++) {
sc->sc_fkeymap[i] = fkey_tab[i];
}
kbd_set_maps(kbd, &sc->sc_keymap, &sc->sc_accmap,
sc->sc_fkeymap, KMI_NFKEY);
KBD_FOUND_DEVICE(kbd);
kmi_clear_state(kbd);
KBD_PROBE_DONE(kbd);
KBD_INIT_DONE(kbd);
if (kbd_register(kbd) < 0) {
goto detach;
}
KBD_CONFIG_DONE(kbd);
#ifdef KBD_INSTALL_CDEV
if (kbd_attach(kbd)) {
goto detach;
}
#endif
if (bootverbose) {
genkbd_diag(kbd, bootverbose);
}
kmi_attached = 1;
return (0);
detach:
return (ENXIO);
}
static device_method_t pl050_kmi_methods[] = {
DEVMETHOD(device_probe, pl050_kmi_probe),
DEVMETHOD(device_attach, pl050_kmi_attach),
{ 0, 0 }
};
static driver_t pl050_kmi_driver = {
"kmi",
pl050_kmi_methods,
sizeof(struct kmi_softc),
};
static devclass_t pl050_kmi_devclass;
DRIVER_MODULE(pl050_kmi, simplebus, pl050_kmi_driver, pl050_kmi_devclass, 0, 0);